diff options
21 files changed, 701 insertions, 440 deletions
diff --git a/components/layout_2020/dom_traversal.rs b/components/layout_2020/dom_traversal.rs index 056103d7205..56ee72d67ca 100644 --- a/components/layout_2020/dom_traversal.rs +++ b/components/layout_2020/dom_traversal.rs @@ -17,7 +17,6 @@ use std::sync::Arc; use style::dom::TNode; use style::properties::ComputedValues; use style::selector_parser::PseudoElement; -use style::values::computed::Length; #[derive(Clone, Copy)] pub enum WhichPseudoElement { @@ -299,7 +298,10 @@ impl Drop for BoxSlot<'_> { pub(crate) trait NodeExt<'dom>: 'dom + Copy + LayoutNode + Send + Sync { fn is_element(self) -> bool; fn as_text(self) -> Option<String>; - fn as_image(self) -> Option<(Option<Arc<NetImage>>, Vec2<Length>)>; + + /// Returns the image if it’s loaded, and its size in image pixels + /// adjusted for `image_density`. + fn as_image(self) -> Option<(Option<Arc<NetImage>>, Vec2<f64>)>; fn first_child(self) -> Option<Self>; fn next_sibling(self) -> Option<Self>; fn parent_node(self) -> Option<Self>; @@ -328,7 +330,7 @@ where } } - fn as_image(self) -> Option<(Option<Arc<NetImage>>, Vec2<Length>)> { + fn as_image(self) -> Option<(Option<Arc<NetImage>>, Vec2<f64>)> { let node = self.to_threadsafe(); let (resource, metadata) = node.image_data()?; let (width, height) = resource @@ -336,14 +338,14 @@ where .map(|image| (image.width, image.height)) .or_else(|| metadata.map(|metadata| (metadata.width, metadata.height))) .unwrap_or((0, 0)); - let (mut width, mut height) = (width as f32, height as f32); + let (mut width, mut height) = (width as f64, height as f64); if let Some(density) = node.image_density().filter(|density| *density != 1.) { - width = (width as f64 / density) as f32; - height = (height as f64 / density) as f32; + width = width / density; + height = height / density; } let size = Vec2 { - x: Length::new(width), - y: Length::new(height), + x: width, + y: height, }; Some((resource, size)) } diff --git a/components/layout_2020/flow/construct.rs b/components/layout_2020/flow/construct.rs index c02dcd08fd7..896293831bc 100644 --- a/components/layout_2020/flow/construct.rs +++ b/components/layout_2020/flow/construct.rs @@ -393,7 +393,7 @@ where style.clone(), display_inside, contents, - ContentSizesRequest::inline_if(style.inline_size_is_auto()), + ContentSizesRequest::inline_if(!style.inline_size_is_length()), ), )) }; @@ -590,7 +590,7 @@ where &style, ContentSizesRequest::inline_if( max_assign_in_flow_outer_content_sizes_to.is_some() && - style.inline_size_is_auto(), + !style.inline_size_is_length(), ), ); if let Some(to) = max_assign_in_flow_outer_content_sizes_to { @@ -607,7 +607,7 @@ where } => { let content_sizes = ContentSizesRequest::inline_if( max_assign_in_flow_outer_content_sizes_to.is_some() && - style.inline_size_is_auto(), + !style.inline_size_is_length(), ); let contents = IndependentFormattingContext::construct( context, diff --git a/components/layout_2020/flow/float.rs b/components/layout_2020/flow/float.rs index 2acc2095004..df9cbbaf514 100644 --- a/components/layout_2020/flow/float.rs +++ b/components/layout_2020/flow/float.rs @@ -33,7 +33,7 @@ impl FloatBox { display_inside: DisplayInside, contents: Contents<impl NodeExt<'dom>>, ) -> Self { - let content_sizes = ContentSizesRequest::inline_if(style.inline_size_is_auto()); + let content_sizes = ContentSizesRequest::inline_if(!style.inline_size_is_length()); Self { contents: IndependentFormattingContext::construct( context, diff --git a/components/layout_2020/flow/inline.rs b/components/layout_2020/flow/inline.rs index 0cacfb794a7..c4b760275d9 100644 --- a/components/layout_2020/flow/inline.rs +++ b/components/layout_2020/flow/inline.rs @@ -465,9 +465,7 @@ fn layout_atomic<'box_tree>( let fragment = match atomic.as_replaced() { Ok(replaced) => { - // FIXME: implement https://drafts.csswg.org/css2/visudet.html#inline-replaced-width - // and https://drafts.csswg.org/css2/visudet.html#inline-replaced-height - let size = Vec2::zero(); + let size = replaced.used_size_as_if_inline_element(ifc.containing_block, &atomic.style); let fragments = replaced.make_fragments(&atomic.style, size.clone()); let content_rect = Rect { start_corner, size }; BoxFragment { @@ -482,10 +480,29 @@ fn layout_atomic<'box_tree>( }, Err(non_replaced) => { let box_size = atomic.style.box_size(); - let inline_size = box_size.inline.percentage_relative_to(cbis).auto_is(|| { - let available_size = cbis - pbm.inline_sum(); - atomic.content_sizes.shrink_to_fit(available_size) - }); + let max_box_size = atomic + .style + .max_box_size() + .percentages_relative_to(ifc.containing_block); + let min_box_size = atomic + .style + .min_box_size() + .percentages_relative_to(ifc.containing_block) + .auto_is(Length::zero); + + // https://drafts.csswg.org/css2/visudet.html#inlineblock-width + let tentative_inline_size = + box_size.inline.percentage_relative_to(cbis).auto_is(|| { + let available_size = cbis - pbm.inline_sum(); + atomic.content_sizes.shrink_to_fit(available_size) + }); + + // https://drafts.csswg.org/css2/visudet.html#min-max-widths + // In this case “applying the rules above again” with a non-auto inline-size + // always results in that size. + let inline_size = tentative_inline_size + .clamp_between_extremums(min_box_size.inline, max_box_size.inline); + let block_size = box_size .block .maybe_percentage_relative_to(ifc.containing_block.block_size.non_auto()); @@ -508,7 +525,16 @@ fn layout_atomic<'box_tree>( dummy_tree_rank, ifc.absolutely_positioned_fragments, ); - let block_size = block_size.auto_is(|| independent_layout.content_block_size); + + // https://drafts.csswg.org/css2/visudet.html#block-root-margin + let tentative_block_size = block_size.auto_is(|| independent_layout.content_block_size); + + // https://drafts.csswg.org/css2/visudet.html#min-max-heights + // In this case “applying the rules above again” with a non-auto block-size + // always results in that size. + let block_size = tentative_block_size + .clamp_between_extremums(min_box_size.block, max_box_size.block); + let content_rect = Rect { start_corner, size: Vec2 { diff --git a/components/layout_2020/flow/mod.rs b/components/layout_2020/flow/mod.rs index 7138ffe3eb0..ed578c825ef 100644 --- a/components/layout_2020/flow/mod.rs +++ b/components/layout_2020/flow/mod.rs @@ -21,8 +21,7 @@ use rayon_croissant::ParallelIteratorExt; use servo_arc::Arc; use style::computed_values::position::T as Position; use style::properties::ComputedValues; -use style::values::computed::{Length, LengthOrAuto, LengthPercentage, LengthPercentageOrAuto}; -use style::values::generics::length::MaxSize; +use style::values::computed::{Length, LengthOrAuto}; use style::Zero; mod construct; @@ -365,10 +364,14 @@ fn layout_in_flow_non_replaced_block_level<'a>( let pb = &padding + &border; let pb_inline_sum = pb.inline_sum(); - let box_size = percent_resolved_box_size(style.box_size(), containing_block); - let max_box_size = percent_resolved_max_box_size(style.max_box_size(), containing_block); - let min_box_size = - percent_resolved_box_size(style.min_box_size(), containing_block).auto_is(Length::zero); + let box_size = style.box_size().percentages_relative_to(containing_block); + let max_box_size = style + .max_box_size() + .percentages_relative_to(containing_block); + let min_box_size = style + .min_box_size() + .percentages_relative_to(containing_block) + .auto_is(Length::zero); // https://drafts.csswg.org/css2/visudet.html#min-max-widths let solve_inline_margins = |inline_size| { @@ -411,7 +414,7 @@ fn layout_in_flow_non_replaced_block_level<'a>( // https://drafts.csswg.org/css2/visudet.html#min-max-heights let mut block_size = box_size.block; if let LengthOrAuto::LengthPercentage(ref mut block_size) = block_size { - *block_size = clamp_between_extremums(*block_size, min_box_size.block, max_box_size.block); + *block_size = block_size.clamp_between_extremums(min_box_size.block, max_box_size.block); } let containing_block_for_children = ContainingBlock { @@ -475,11 +478,9 @@ fn layout_in_flow_non_replaced_block_level<'a>( .collapsed_through; let relative_adjustement = relative_adjustement(style, inline_size, block_size); let block_size = block_size.auto_is(|| { - clamp_between_extremums( - flow_layout.content_block_size, - min_box_size.block, - max_box_size.block, - ) + flow_layout + .content_block_size + .clamp_between_extremums(min_box_size.block, max_box_size.block) }); let content_rect = Rect { start_corner: Vec2 { @@ -520,140 +521,20 @@ fn layout_in_flow_replaced_block_level<'a>( style: &Arc<ComputedValues>, replaced: &ReplacedContent, ) -> BoxFragment { + let size = replaced.used_size_as_if_inline_element(containing_block, style); + let cbis = containing_block.inline_size; let padding = style.padding().percentages_relative_to(cbis); let border = style.border_width(); let computed_margin = style.margin().percentages_relative_to(cbis); let pb = &padding + &border; - let mode = style.writing_mode; - // FIXME(nox): We shouldn't pretend we always have a fully known intrinsic size. - let intrinsic_size = replaced.intrinsic_size.size_to_flow_relative(mode); - // FIXME(nox): This can divide by zero. - let intrinsic_ratio = intrinsic_size.inline.px() / intrinsic_size.block.px(); - - let box_size = percent_resolved_box_size(style.box_size(), containing_block); - let min_box_size = - percent_resolved_box_size(style.min_box_size(), containing_block).auto_is(Length::zero); - let max_box_size = percent_resolved_max_box_size(style.max_box_size(), containing_block); - - let clamp = |inline_size, block_size| { - ( - clamp_between_extremums(inline_size, min_box_size.inline, max_box_size.inline), - clamp_between_extremums(block_size, min_box_size.block, max_box_size.block), - ) - }; - // https://drafts.csswg.org/css2/visudet.html#min-max-widths - // https://drafts.csswg.org/css2/visudet.html#min-max-heights - let (inline_size, block_size) = match (box_size.inline, box_size.block) { - (LengthOrAuto::LengthPercentage(inline), LengthOrAuto::LengthPercentage(block)) => { - clamp(inline, block) - }, - (LengthOrAuto::LengthPercentage(inline), LengthOrAuto::Auto) => { - clamp(inline, inline / intrinsic_ratio) - }, - (LengthOrAuto::Auto, LengthOrAuto::LengthPercentage(block)) => { - clamp(block * intrinsic_ratio, block) - }, - (LengthOrAuto::Auto, LengthOrAuto::Auto) => { - enum Violation { - None, - Below(Length), - Above(Length), - } - let violation = |size, min_size, mut max_size: Option<Length>| { - if let Some(max) = max_size.as_mut() { - max.max_assign(min_size); - } - if size < min_size { - return Violation::Below(min_size); - } - match max_size { - Some(max_size) if size > max_size => Violation::Above(max_size), - _ => Violation::None, - } - }; - match ( - violation( - intrinsic_size.inline, - min_box_size.inline, - max_box_size.inline, - ), - violation(intrinsic_size.block, min_box_size.block, max_box_size.block), - ) { - // Row 1. - (Violation::None, Violation::None) => (intrinsic_size.inline, intrinsic_size.block), - // Row 2. - (Violation::Above(max_inline_size), Violation::None) => { - let block_size = (max_inline_size / intrinsic_ratio).max(min_box_size.block); - (max_inline_size, block_size) - }, - // Row 3. - (Violation::Below(min_inline_size), Violation::None) => { - let block_size = - clamp_below_max(min_inline_size / intrinsic_ratio, max_box_size.block); - (min_inline_size, block_size) - }, - // Row 4. - (Violation::None, Violation::Above(max_block_size)) => { - let inline_size = (max_block_size * intrinsic_ratio).max(min_box_size.inline); - (inline_size, max_block_size) - }, - // Row 5. - (Violation::None, Violation::Below(min_block_size)) => { - let inline_size = - clamp_below_max(min_block_size * intrinsic_ratio, max_box_size.inline); - (inline_size, min_block_size) - }, - // Rows 6-7. - (Violation::Above(max_inline_size), Violation::Above(max_block_size)) => { - if max_inline_size.px() / intrinsic_size.inline.px() <= - max_block_size.px() / intrinsic_size.block.px() - { - // Row 6. - let block_size = - (max_inline_size / intrinsic_ratio).max(min_box_size.block); - (max_inline_size, block_size) - } else { - // Row 7. - let inline_size = - (max_block_size * intrinsic_ratio).max(min_box_size.inline); - (inline_size, max_block_size) - } - }, - // Rows 8-9. - (Violation::Below(min_inline_size), Violation::Below(min_block_size)) => { - if min_inline_size.px() / intrinsic_size.inline.px() <= - min_block_size.px() / intrinsic_size.block.px() - { - // Row 8. - let inline_size = - clamp_below_max(min_block_size * intrinsic_ratio, max_box_size.inline); - (inline_size, min_block_size) - } else { - // Row 9. - let block_size = - clamp_below_max(min_inline_size / intrinsic_ratio, max_box_size.block); - (min_inline_size, block_size) - } - }, - // Row 10. - (Violation::Below(min_inline_size), Violation::Above(max_block_size)) => { - (min_inline_size, max_block_size) - }, - // Row 11. - (Violation::Above(max_inline_size), Violation::Below(min_block_size)) => { - (max_inline_size, min_block_size) - }, - } - }, - }; let (margin_inline_start, margin_inline_end) = solve_inline_margins_for_in_flow_block_level( containing_block, pb.inline_sum(), computed_margin.inline_start, computed_margin.inline_end, - inline_size, + size.inline, ); let margin = Sides { inline_start: margin_inline_start, @@ -661,15 +542,11 @@ fn layout_in_flow_replaced_block_level<'a>( block_start: computed_margin.block_start.auto_is(Length::zero), block_end: computed_margin.block_end.auto_is(Length::zero), }; - let size = Vec2 { - block: block_size, - inline: inline_size, - }; let fragments = replaced.make_fragments(style, size.clone()); let relative_adjustement = relative_adjustement( style, - inline_size, - LengthOrAuto::LengthPercentage(block_size), + size.inline, + LengthOrAuto::LengthPercentage(size.block), ); let content_rect = Rect { start_corner: Vec2 { @@ -703,45 +580,3 @@ fn solve_inline_margins_for_in_flow_block_level( (LengthOrAuto::LengthPercentage(start), _) => (start, inline_margins - start), } } - -fn clamp_between_extremums(size: Length, min_size: Length, max_size: Option<Length>) -> Length { - clamp_below_max(size, max_size).max(min_size) -} - -fn clamp_below_max(size: Length, max_size: Option<Length>) -> Length { - max_size.map_or(size, |max_size| size.min(max_size)) -} - -fn percent_resolved_box_size( - box_size: Vec2<LengthPercentageOrAuto>, - containing_block: &ContainingBlock, -) -> Vec2<LengthOrAuto> { - Vec2 { - inline: box_size - .inline - .percentage_relative_to(containing_block.inline_size), - block: box_size - .block - .maybe_percentage_relative_to(containing_block.block_size.non_auto()), - } -} - -fn percent_resolved_max_box_size( - max_box_size: Vec2<MaxSize<LengthPercentage>>, - containing_block: &ContainingBlock, -) -> Vec2<Option<Length>> { - Vec2 { - inline: match max_box_size.inline { - MaxSize::LengthPercentage(max_inline_size) => { - Some(max_inline_size.percentage_relative_to(containing_block.inline_size)) - }, - MaxSize::None => None, - }, - block: match max_box_size.block { - MaxSize::LengthPercentage(max_block_size) => { - max_block_size.maybe_percentage_relative_to(containing_block.block_size.non_auto()) - }, - MaxSize::None => None, - }, - } -} diff --git a/components/layout_2020/flow/root.rs b/components/layout_2020/flow/root.rs index f1c301771f9..2415d18e0ca 100644 --- a/components/layout_2020/flow/root.rs +++ b/components/layout_2020/flow/root.rs @@ -16,12 +16,12 @@ use crate::positioned::AbsolutelyPositionedBox; use crate::replaced::ReplacedContent; use crate::sizing::ContentSizesRequest; use crate::style_ext::{Display, DisplayGeneratingBox, DisplayInside}; -use crate::{ContainingBlock, DefiniteContainingBlock}; +use crate::DefiniteContainingBlock; use rayon::iter::{IntoParallelRefIterator, ParallelExtend, ParallelIterator}; use script_layout_interface::wrapper_traits::LayoutNode; use servo_arc::Arc; use style::properties::ComputedValues; -use style::values::computed::{Length, LengthOrAuto}; +use style::values::computed::Length; use style::Zero; use style_traits::CSSPixel; @@ -99,31 +99,25 @@ impl BoxTreeRoot { viewport: geom::Size<CSSPixel>, ) -> FragmentTreeRoot { let style = ComputedValues::initial_values(); - let initial_containing_block_size = Vec2 { - inline: Length::new(viewport.width), - block: Length::new(viewport.height), - }; - - let initial_containing_block = ContainingBlock { - inline_size: initial_containing_block_size.inline, - block_size: LengthOrAuto::LengthPercentage(initial_containing_block_size.block), + let initial_containing_block = DefiniteContainingBlock { + size: Vec2 { + inline: Length::new(viewport.width), + block: Length::new(viewport.height), + }, // FIXME: use the document’s mode: // https://drafts.csswg.org/css-writing-modes/#principal-flow style, }; + let dummy_tree_rank = 0; let mut absolutely_positioned_fragments = vec![]; let mut independent_layout = self.0.layout( layout_context, - &initial_containing_block, + &(&initial_containing_block).into(), dummy_tree_rank, &mut absolutely_positioned_fragments, ); - let initial_containing_block = DefiniteContainingBlock { - size: initial_containing_block_size, - style, - }; independent_layout.fragments.par_extend( absolutely_positioned_fragments .par_iter() diff --git a/components/layout_2020/formatting_contexts.rs b/components/layout_2020/formatting_contexts.rs index d7477182437..eb90d62c926 100644 --- a/components/layout_2020/formatting_contexts.rs +++ b/components/layout_2020/formatting_contexts.rs @@ -29,6 +29,8 @@ pub(crate) struct IndependentFormattingContext { pub(crate) struct IndependentLayout { pub fragments: Vec<Fragment>, + + /// https://drafts.csswg.org/css2/visudet.html#root-height pub content_block_size: Length, } @@ -57,31 +59,30 @@ impl IndependentFormattingContext { contents: Contents<impl NodeExt<'dom>>, content_sizes: ContentSizesRequest, ) -> Self { - use self::IndependentFormattingContextContents as Contents; - let (contents, content_sizes) = match contents.try_into() { + match contents.try_into() { Ok(non_replaced) => match display_inside { DisplayInside::Flow | DisplayInside::FlowRoot => { - let (bfc, box_content_sizes) = BlockFormattingContext::construct( + let (bfc, content_sizes) = BlockFormattingContext::construct( context, &style, non_replaced, content_sizes, ); - (Contents::Flow(bfc), box_content_sizes) + Self { + style, + content_sizes, + contents: IndependentFormattingContextContents::Flow(bfc), + } }, }, Err(replaced) => { - // The `content_sizes` field is not used by layout code: - ( - Contents::Replaced(replaced), - BoxContentSizes::NoneWereRequested, - ) + let content_sizes = content_sizes.compute(|| replaced.inline_content_sizes(&style)); + Self { + style, + content_sizes, + contents: IndependentFormattingContextContents::Replaced(replaced), + } }, - }; - Self { - style, - contents, - content_sizes, } } diff --git a/components/layout_2020/geom.rs b/components/layout_2020/geom.rs index 2eafb78051e..562fad5c4f6 100644 --- a/components/layout_2020/geom.rs +++ b/components/layout_2020/geom.rs @@ -2,11 +2,13 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ +use crate::ContainingBlock; use std::fmt; use std::ops::{Add, AddAssign, Sub}; use style::logical_geometry::{BlockFlowDirection, InlineBaseDirection}; use style::logical_geometry::{PhysicalCorner, WritingMode}; use style::values::computed::{Length, LengthOrAuto, LengthPercentage, LengthPercentageOrAuto}; +use style::values::generics::length::MaxSize; use style::Zero; use style_traits::CSSPixel; @@ -151,6 +153,39 @@ impl flow_relative::Vec2<LengthOrAuto> { } } +impl flow_relative::Vec2<LengthPercentageOrAuto> { + pub fn percentages_relative_to( + &self, + containing_block: &ContainingBlock, + ) -> flow_relative::Vec2<LengthOrAuto> { + flow_relative::Vec2 { + inline: self + .inline + .percentage_relative_to(containing_block.inline_size), + block: self + .block + .maybe_percentage_relative_to(containing_block.block_size.non_auto()), + } + } +} + +impl flow_relative::Vec2<MaxSize<LengthPercentage>> { + pub fn percentages_relative_to( + &self, + containing_block: &ContainingBlock, + ) -> flow_relative::Vec2<Option<Length>> { + flow_relative::Vec2 { + inline: self + .inline + .to_option() + .map(|lp| lp.percentage_relative_to(containing_block.inline_size)), + block: self.block.to_option().and_then(|olp| { + olp.maybe_percentage_relative_to(containing_block.block_size.non_auto()) + }), + } + } +} + impl flow_relative::Rect<Length> { pub fn zero() -> Self { Self { diff --git a/components/layout_2020/lib.rs b/components/layout_2020/lib.rs index f0c56afa6f1..8e923f870d7 100644 --- a/components/layout_2020/lib.rs +++ b/components/layout_2020/lib.rs @@ -44,6 +44,16 @@ struct DefiniteContainingBlock<'a> { style: &'a ComputedValues, } +impl<'a> From<&'_ DefiniteContainingBlock<'a>> for ContainingBlock<'a> { + fn from(definite: &DefiniteContainingBlock<'a>) -> Self { + ContainingBlock { + inline_size: definite.size.inline, + block_size: LengthOrAuto::LengthPercentage(definite.size.block), + style: definite.style, + } + } +} + /// https://drafts.csswg.org/css2/visuren.html#relative-positioning fn relative_adjustement( style: &ComputedValues, diff --git a/components/layout_2020/positioned.rs b/components/layout_2020/positioned.rs index 7aed850a5ca..9459f3c3f84 100644 --- a/components/layout_2020/positioned.rs +++ b/components/layout_2020/positioned.rs @@ -30,19 +30,24 @@ pub(crate) struct AbsolutelyPositionedFragment<'box_> { /// static positions when going up the tree. pub(crate) tree_rank: usize, - pub(crate) inline_start: AbsoluteBoxOffsets<LengthPercentage>, - inline_size: LengthPercentageOrAuto, - - pub(crate) block_start: AbsoluteBoxOffsets<LengthPercentage>, - block_size: LengthPercentageOrAuto, + box_offsets: Vec2<AbsoluteBoxOffsets>, } #[derive(Clone, Copy, Debug)] -pub(crate) enum AbsoluteBoxOffsets<NonStatic> { - StaticStart { start: Length }, - Start { start: NonStatic }, - End { end: NonStatic }, - Both { start: NonStatic, end: NonStatic }, +pub(crate) enum AbsoluteBoxOffsets { + StaticStart { + start: Length, + }, + Start { + start: LengthPercentage, + }, + End { + end: LengthPercentage, + }, + Both { + start: LengthPercentage, + end: LengthPercentage, + }, } impl AbsolutelyPositionedBox { @@ -55,7 +60,7 @@ impl AbsolutelyPositionedBox { // "Shrink-to-fit" in https://drafts.csswg.org/css2/visudet.html#abs-non-replaced-width let content_sizes = ContentSizesRequest::inline_if( // If inline-size is non-auto, that value is used without shrink-to-fit - style.inline_size_is_auto() && + !style.inline_size_is_length() && // If it is, then the only case where shrink-to-fit is *not* used is // if both offsets are non-auto, leaving inline-size as the only variable // in the constraint equation. @@ -77,18 +82,11 @@ impl AbsolutelyPositionedBox { initial_start_corner: Vec2<Length>, tree_rank: usize, ) -> AbsolutelyPositionedFragment { - let style = &self.contents.style; - let box_offsets = style.box_offsets(); - let box_size = style.box_size(); - - let inline_size = box_size.inline; - let block_size = box_size.block; - fn absolute_box_offsets( initial_static_start: Length, start: LengthPercentageOrAuto, end: LengthPercentageOrAuto, - ) -> AbsoluteBoxOffsets<LengthPercentage> { + ) -> AbsoluteBoxOffsets { match (start.non_auto(), end.non_auto()) { (None, None) => AbsoluteBoxOffsets::StaticStart { start: initial_static_start, @@ -99,24 +97,22 @@ impl AbsolutelyPositionedBox { } } - let inline_start = absolute_box_offsets( - initial_start_corner.inline, - box_offsets.inline_start, - box_offsets.inline_end, - ); - let block_start = absolute_box_offsets( - initial_start_corner.block, - box_offsets.block_start, - box_offsets.block_end, - ); - + let box_offsets = self.contents.style.box_offsets(); AbsolutelyPositionedFragment { absolutely_positioned_box: self, tree_rank, - inline_start, - inline_size, - block_start, - block_size, + box_offsets: Vec2 { + inline: absolute_box_offsets( + initial_start_corner.inline, + box_offsets.inline_start, + box_offsets.inline_end, + ), + block: absolute_box_offsets( + initial_start_corner.block, + box_offsets.block_start, + box_offsets.block_end, + ), + }, } } } @@ -162,168 +158,90 @@ impl<'a> AbsolutelyPositionedFragment<'a> { let cbis = containing_block.size.inline; let cbbs = containing_block.size.block; + let size; + let replaced_used_size; + match self.absolutely_positioned_box.contents.as_replaced() { + Ok(replaced) => { + // https://drafts.csswg.org/css2/visudet.html#abs-replaced-width + // https://drafts.csswg.org/css2/visudet.html#abs-replaced-height + let u = replaced.used_size_as_if_inline_element(&containing_block.into(), style); + size = Vec2 { + inline: LengthOrAuto::LengthPercentage(u.inline), + block: LengthOrAuto::LengthPercentage(u.block), + }; + replaced_used_size = Some(u); + }, + Err(_non_replaced) => { + let box_size = style.box_size(); + size = Vec2 { + inline: box_size.inline.percentage_relative_to(cbis), + block: box_size.block.percentage_relative_to(cbbs), + }; + replaced_used_size = None; + }, + } + let padding = style.padding().percentages_relative_to(cbis); let border = style.border_width(); let computed_margin = style.margin().percentages_relative_to(cbis); let pb = &padding + &border; - enum Anchor { - Start(Length), - End(Length), - } - - fn solve_axis( - containing_size: Length, - padding_border_sum: Length, - computed_margin_start: LengthOrAuto, - computed_margin_end: LengthOrAuto, - solve_margins: impl FnOnce(Length) -> (Length, Length), - box_offsets: AbsoluteBoxOffsets<LengthPercentage>, - size: LengthPercentageOrAuto, - ) -> (Anchor, LengthOrAuto, Length, Length) { - let size = size.percentage_relative_to(containing_size); - match box_offsets { - AbsoluteBoxOffsets::StaticStart { start } => ( - Anchor::Start(start), - size, - computed_margin_start.auto_is(Length::zero), - computed_margin_end.auto_is(Length::zero), - ), - AbsoluteBoxOffsets::Start { start } => ( - Anchor::Start(start.percentage_relative_to(containing_size)), - size, - computed_margin_start.auto_is(Length::zero), - computed_margin_end.auto_is(Length::zero), - ), - AbsoluteBoxOffsets::End { end } => ( - Anchor::End(end.percentage_relative_to(containing_size)), - size, - computed_margin_start.auto_is(Length::zero), - computed_margin_end.auto_is(Length::zero), - ), - AbsoluteBoxOffsets::Both { start, end } => { - let start = start.percentage_relative_to(containing_size); - let end = end.percentage_relative_to(containing_size); - - let mut margin_start = computed_margin_start.auto_is(Length::zero); - let mut margin_end = computed_margin_end.auto_is(Length::zero); - - let size = if let LengthOrAuto::LengthPercentage(size) = size { - let margins = containing_size - start - end - padding_border_sum - size; - match (computed_margin_start, computed_margin_end) { - (LengthOrAuto::Auto, LengthOrAuto::Auto) => { - let (s, e) = solve_margins(margins); - margin_start = s; - margin_end = e; - }, - (LengthOrAuto::Auto, LengthOrAuto::LengthPercentage(end)) => { - margin_start = margins - end; - }, - (LengthOrAuto::LengthPercentage(start), LengthOrAuto::Auto) => { - margin_end = margins - start; - }, - ( - LengthOrAuto::LengthPercentage(_), - LengthOrAuto::LengthPercentage(_), - ) => {}, - } - size - } else { - // FIXME(nox): What happens if that is negative? - containing_size - - start - - end - - padding_border_sum - - margin_start - - margin_end - }; - ( - Anchor::Start(start), - LengthOrAuto::LengthPercentage(size), - margin_start, - margin_end, - ) - }, - } - } - - let (inline_anchor, inline_size, margin_inline_start, margin_inline_end) = solve_axis( + let inline_axis = solve_axis( cbis, pb.inline_sum(), computed_margin.inline_start, computed_margin.inline_end, - |margins| { - if margins.px() >= 0. { - (margins / 2., margins / 2.) - } else { - (Length::zero(), margins) - } - }, - self.inline_start, - self.inline_size, + /* avoid_negative_margin_start */ true, + self.box_offsets.inline, + size.inline, ); - let (block_anchor, block_size, margin_block_start, margin_block_end) = solve_axis( + let block_axis = solve_axis( cbis, pb.block_sum(), computed_margin.block_start, computed_margin.block_end, - |margins| (margins / 2., margins / 2.), - self.block_start, - self.block_size, + /* avoid_negative_margin_start */ false, + self.box_offsets.block, + size.block, ); let margin = Sides { - inline_start: margin_inline_start, - inline_end: margin_inline_end, - block_start: margin_block_start, - block_end: margin_block_end, + inline_start: inline_axis.margin_start, + inline_end: inline_axis.margin_end, + block_start: block_axis.margin_start, + block_end: block_axis.margin_end, }; - let inline_size = inline_size.auto_is(|| { - let available_size = match inline_anchor { - Anchor::Start(start) => cbis - start - pb.inline_sum() - margin.inline_sum(), - Anchor::End(end) => cbis - end - pb.inline_sum() - margin.inline_sum(), - }; - - if self - .absolutely_positioned_box - .contents - .as_replaced() - .is_ok() - { - // FIXME: implement https://drafts.csswg.org/css2/visudet.html#abs-replaced-width - available_size - } else { - self.absolutely_positioned_box - .contents - .content_sizes - .shrink_to_fit(available_size) - } - }); - let mut absolutely_positioned_fragments = Vec::new(); - let mut independent_layout = match self.absolutely_positioned_box.contents.as_replaced() { + let (size, mut fragments) = match self.absolutely_positioned_box.contents.as_replaced() { Ok(replaced) => { - // FIXME: implement https://drafts.csswg.org/css2/visudet.html#abs-replaced-width - // and https://drafts.csswg.org/css2/visudet.html#abs-replaced-height - let block_size = block_size.auto_is(Length::zero); - let fragments = replaced.make_fragments( - &self.absolutely_positioned_box.contents.style, - Vec2 { - inline: inline_size, - block: block_size, - }, - ); - crate::formatting_contexts::IndependentLayout { - fragments, - content_block_size: block_size, - } + // https://drafts.csswg.org/css2/visudet.html#abs-replaced-width + // https://drafts.csswg.org/css2/visudet.html#abs-replaced-height + let style = &self.absolutely_positioned_box.contents.style; + let size = replaced_used_size.unwrap(); + let fragments = replaced.make_fragments(style, size.clone()); + (size, fragments) }, Err(non_replaced) => { + // https://drafts.csswg.org/css2/visudet.html#abs-non-replaced-width + // https://drafts.csswg.org/css2/visudet.html#abs-non-replaced-height + let inline_size = inline_axis.size.auto_is(|| { + let available_size = match inline_axis.anchor { + Anchor::Start(start) => { + cbis - start - pb.inline_sum() - margin.inline_sum() + }, + Anchor::End(end) => cbis - end - pb.inline_sum() - margin.inline_sum(), + }; + self.absolutely_positioned_box + .contents + .content_sizes + .shrink_to_fit(available_size) + }); + let containing_block_for_children = ContainingBlock { inline_size, - block_size, + block_size: block_axis.size, style, }; // https://drafts.csswg.org/css-writing-modes/#orthogonal-flows @@ -333,24 +251,30 @@ impl<'a> AbsolutelyPositionedFragment<'a> { "Mixed writing modes are not supported yet" ); let dummy_tree_rank = 0; - non_replaced.layout( + let independent_layout = non_replaced.layout( layout_context, &containing_block_for_children, dummy_tree_rank, &mut absolutely_positioned_fragments, - ) + ); + + let size = Vec2 { + inline: inline_size, + block: block_axis + .size + .auto_is(|| independent_layout.content_block_size), + }; + (size, independent_layout.fragments) }, }; - let inline_start = match inline_anchor { + let inline_start = match inline_axis.anchor { Anchor::Start(start) => start + pb.inline_start + margin.inline_start, - Anchor::End(end) => cbbs - end - pb.inline_end - margin.inline_end - inline_size, + Anchor::End(end) => cbbs - end - pb.inline_end - margin.inline_end - size.inline, }; - - let block_size = block_size.auto_is(|| independent_layout.content_block_size); - let block_start = match block_anchor { + let block_start = match block_axis.anchor { Anchor::Start(start) => start + pb.block_start + margin.block_start, - Anchor::End(end) => cbbs - end - pb.block_end - margin.block_end - block_size, + Anchor::End(end) => cbbs - end - pb.block_end - margin.block_end - size.block, }; let content_rect = Rect { @@ -358,16 +282,13 @@ impl<'a> AbsolutelyPositionedFragment<'a> { inline: inline_start, block: block_start, }, - size: Vec2 { - inline: inline_size, - block: block_size, - }, + size, }; AbsolutelyPositionedFragment::in_positioned_containing_block( layout_context, &absolutely_positioned_fragments, - &mut independent_layout.fragments, + &mut fragments, &content_rect.size, &padding, style, @@ -375,7 +296,7 @@ impl<'a> AbsolutelyPositionedFragment<'a> { Fragment::Box(BoxFragment { style: style.clone(), - children: independent_layout.fragments, + children: fragments, content_rect, padding, border, @@ -385,6 +306,110 @@ impl<'a> AbsolutelyPositionedFragment<'a> { } } +enum Anchor { + Start(Length), + End(Length), +} + +struct AxisResult { + anchor: Anchor, + size: LengthOrAuto, + margin_start: Length, + margin_end: Length, +} + +/// This unifies some of the parts in common in: +/// +/// * https://drafts.csswg.org/css2/visudet.html#abs-non-replaced-width +/// * https://drafts.csswg.org/css2/visudet.html#abs-non-replaced-height +/// +/// … and: +/// +/// * https://drafts.csswg.org/css2/visudet.html#abs-replaced-width +/// * https://drafts.csswg.org/css2/visudet.html#abs-replaced-height +/// +/// In the replaced case, `size` is never `Auto`. +fn solve_axis( + containing_size: Length, + padding_border_sum: Length, + computed_margin_start: LengthOrAuto, + computed_margin_end: LengthOrAuto, + avoid_negative_margin_start: bool, + box_offsets: AbsoluteBoxOffsets, + size: LengthOrAuto, +) -> AxisResult { + match box_offsets { + AbsoluteBoxOffsets::StaticStart { start } => AxisResult { + anchor: Anchor::Start(start), + size, + margin_start: computed_margin_start.auto_is(Length::zero), + margin_end: computed_margin_end.auto_is(Length::zero), + }, + AbsoluteBoxOffsets::Start { start } => AxisResult { + anchor: Anchor::Start(start.percentage_relative_to(containing_size)), + size, + margin_start: computed_margin_start.auto_is(Length::zero), + margin_end: computed_margin_end.auto_is(Length::zero), + }, + AbsoluteBoxOffsets::End { end } => AxisResult { + anchor: Anchor::End(end.percentage_relative_to(containing_size)), + size, + margin_start: computed_margin_start.auto_is(Length::zero), + margin_end: computed_margin_end.auto_is(Length::zero), + }, + AbsoluteBoxOffsets::Both { start, end } => { + let start = start.percentage_relative_to(containing_size); + let end = end.percentage_relative_to(containing_size); + + let margin_start; + let margin_end; + let used_size; + if let LengthOrAuto::LengthPercentage(s) = size { + used_size = s; + let margins = containing_size - start - end - padding_border_sum - s; + match (computed_margin_start, computed_margin_end) { + (LengthOrAuto::Auto, LengthOrAuto::Auto) => { + if avoid_negative_margin_start && margins < Length::zero() { + margin_start = Length::zero(); + margin_end = margins; + } else { + margin_start = margins / 2.; + margin_end = margins / 2.; + } + }, + (LengthOrAuto::Auto, LengthOrAuto::LengthPercentage(end)) => { + margin_start = margins - end; + margin_end = end; + }, + (LengthOrAuto::LengthPercentage(start), LengthOrAuto::Auto) => { + margin_start = start; + margin_end = margins - start; + }, + ( + LengthOrAuto::LengthPercentage(start), + LengthOrAuto::LengthPercentage(end), + ) => { + margin_start = start; + margin_end = end; + }, + } + } else { + margin_start = computed_margin_start.auto_is(Length::zero); + margin_end = computed_margin_end.auto_is(Length::zero); + // FIXME(nox): What happens if that is negative? + used_size = + containing_size - start - end - padding_border_sum - margin_start - margin_end + }; + AxisResult { + anchor: Anchor::Start(start), + size: LengthOrAuto::LengthPercentage(used_size), + margin_start, + margin_end, + } + }, + } +} + pub(crate) fn adjust_static_positions( absolutely_positioned_fragments: &mut [AbsolutelyPositionedFragment], child_fragments: &mut [Fragment], @@ -399,11 +424,11 @@ pub(crate) fn adjust_static_positions( abspos_fragment.tree_rank = tree_rank_in_parent; - if let AbsoluteBoxOffsets::StaticStart { start } = &mut abspos_fragment.inline_start { + if let AbsoluteBoxOffsets::StaticStart { start } = &mut abspos_fragment.box_offsets.inline { *start += child_fragment_rect.start_corner.inline; } - if let AbsoluteBoxOffsets::StaticStart { start } = &mut abspos_fragment.block_start { + if let AbsoluteBoxOffsets::StaticStart { start } = &mut abspos_fragment.box_offsets.block { *start += child_fragment_rect.start_corner.block; } } diff --git a/components/layout_2020/replaced.rs b/components/layout_2020/replaced.rs index 8a28cbf763c..72071dbf684 100644 --- a/components/layout_2020/replaced.rs +++ b/components/layout_2020/replaced.rs @@ -4,17 +4,37 @@ use crate::dom_traversal::NodeExt; use crate::fragments::{Fragment, ImageFragment}; -use crate::geom::{flow_relative, physical}; +use crate::geom::flow_relative::{Rect, Vec2}; +use crate::geom::physical; +use crate::sizing::ContentSizes; +use crate::style_ext::ComputedValuesExt; +use crate::ContainingBlock; use net_traits::image::base::Image; use servo_arc::Arc as ServoArc; use std::sync::Arc; use style::properties::ComputedValues; -use style::values::computed::Length; +use style::values::computed::{Length, LengthOrAuto}; +use style::values::CSSFloat; +use style::Zero; #[derive(Debug)] pub(crate) struct ReplacedContent { pub kind: ReplacedContentKind, - pub intrinsic_size: physical::Vec2<Length>, + + /// * Raster images always have an instrinsic width and height, with 1 image pixel = 1px. + /// The intrinsic ratio should be based on dividing those. + /// See https://github.com/w3c/csswg-drafts/issues/4572 for the case where either is zero. + /// PNG specifically disallows this but I (SimonSapin) am not sure about other formats. + /// + /// * Form controls have both intrinsic width and height **but no intrinsic ratio**. + /// See https://github.com/w3c/csswg-drafts/issues/1044 and + /// https://drafts.csswg.org/css-images/#intrinsic-dimensions “In general, […]” + /// + /// * For SVG, see https://svgwg.org/svg2-draft/coords.html#SizingSVGInCSS + /// and again https://github.com/w3c/csswg-drafts/issues/4572. + intrinsic_width: Option<Length>, + intrinsic_height: Option<Length>, + intrinsic_ratio: Option<CSSFloat>, } #[derive(Debug)] @@ -24,19 +44,65 @@ pub(crate) enum ReplacedContentKind { impl ReplacedContent { pub fn for_element<'dom>(element: impl NodeExt<'dom>) -> Option<Self> { - if let Some((image, intrinsic_size)) = element.as_image() { + if let Some((image, intrinsic_size_in_dots)) = element.as_image() { + // FIXME: should 'image-resolution' (when implemented) be used *instead* of + // `script::dom::htmlimageelement::ImageRequest::current_pixel_density`? + + // https://drafts.csswg.org/css-images-4/#the-image-resolution + let dppx = 1.0; + + let width = (intrinsic_size_in_dots.x as CSSFloat) / dppx; + let height = (intrinsic_size_in_dots.y as CSSFloat) / dppx; return Some(Self { kind: ReplacedContentKind::Image(image), - intrinsic_size, + intrinsic_width: Some(Length::new(width)), + intrinsic_height: Some(Length::new(height)), + // FIXME https://github.com/w3c/csswg-drafts/issues/4572 + intrinsic_ratio: Some(width / height), }); } None } + fn flow_relative_intrinsic_size(&self, style: &ComputedValues) -> Vec2<Option<Length>> { + let intrinsic_size = physical::Vec2 { + x: self.intrinsic_width, + y: self.intrinsic_height, + }; + intrinsic_size.size_to_flow_relative(style.writing_mode) + } + + fn inline_size_over_block_size_intrinsic_ratio( + &self, + style: &ComputedValues, + ) -> Option<CSSFloat> { + self.intrinsic_ratio.map(|width_over_height| { + if style.writing_mode.is_vertical() { + 1. / width_over_height + } else { + width_over_height + } + }) + } + + pub fn inline_content_sizes(&self, style: &ComputedValues) -> ContentSizes { + // FIXME: min/max-content of replaced elements is not defined in + // https://dbaron.org/css/intrinsic/ + // This seems sensible? + let inline = self + .flow_relative_intrinsic_size(style) + .inline + .unwrap_or(Length::zero()); + ContentSizes { + min_content: inline, + max_content: inline, + } + } + pub fn make_fragments<'a>( &'a self, style: &ServoArc<ComputedValues>, - size: flow_relative::Vec2<Length>, + size: Vec2<Length>, ) -> Vec<Fragment> { match &self.kind { ReplacedContentKind::Image(image) => image @@ -45,8 +111,8 @@ impl ReplacedContent { .map(|image_key| { Fragment::Image(ImageFragment { style: style.clone(), - rect: flow_relative::Rect { - start_corner: flow_relative::Vec2::zero(), + rect: Rect { + start_corner: Vec2::zero(), size, }, image_key, @@ -56,4 +122,214 @@ impl ReplacedContent { .collect(), } } + + /// https://drafts.csswg.org/css2/visudet.html#inline-replaced-width + /// https://drafts.csswg.org/css2/visudet.html#inline-replaced-height + /// + /// Also used in other cases, for example + /// https://drafts.csswg.org/css2/visudet.html#block-replaced-width + pub fn used_size_as_if_inline_element( + &self, + containing_block: &ContainingBlock, + style: &ComputedValues, + ) -> Vec2<Length> { + let mode = style.writing_mode; + let intrinsic_size = self.flow_relative_intrinsic_size(style); + let intrinsic_ratio = self.inline_size_over_block_size_intrinsic_ratio(style); + + let box_size = style.box_size().percentages_relative_to(containing_block); + let min_box_size = style + .min_box_size() + .percentages_relative_to(containing_block) + .auto_is(Length::zero); + let max_box_size = style + .max_box_size() + .percentages_relative_to(containing_block); + + let default_object_size = || { + // FIXME: + // “If 300px is too wide to fit the device, UAs should use the width of + // the largest rectangle that has a 2:1 ratio and fits the device instead.” + // “height of the largest rectangle that has a 2:1 ratio, has a height not greater + // than 150px, and has a width not greater than the device width.” + physical::Vec2 { + x: Length::new(300.), + y: Length::new(150.), + } + .size_to_flow_relative(mode) + }; + let clamp = |inline_size: Length, block_size: Length| Vec2 { + inline: inline_size.clamp_between_extremums(min_box_size.inline, max_box_size.inline), + block: block_size.clamp_between_extremums(min_box_size.block, max_box_size.block), + }; + // https://drafts.csswg.org/css2/visudet.html#min-max-widths + // https://drafts.csswg.org/css2/visudet.html#min-max-heights + match (box_size.inline, box_size.block) { + (LengthOrAuto::LengthPercentage(inline), LengthOrAuto::LengthPercentage(block)) => { + clamp(inline, block) + }, + (LengthOrAuto::LengthPercentage(inline), LengthOrAuto::Auto) => { + let block = if let Some(i_over_b) = intrinsic_ratio { + inline / i_over_b + } else if let Some(block) = intrinsic_size.block { + block + } else { + default_object_size().block + }; + clamp(inline, block) + }, + (LengthOrAuto::Auto, LengthOrAuto::LengthPercentage(block)) => { + let inline = if let Some(i_over_b) = intrinsic_ratio { + block * i_over_b + } else if let Some(inline) = intrinsic_size.inline { + inline + } else { + default_object_size().inline + }; + clamp(inline, block) + }, + (LengthOrAuto::Auto, LengthOrAuto::Auto) => { + let inline_size = + match (intrinsic_size.inline, intrinsic_size.block, intrinsic_ratio) { + (Some(inline), _, _) => inline, + (None, Some(block), Some(i_over_b)) => { + // “used height” in CSS 2 is always gonna be the intrinsic one, + // since it is available. + block * i_over_b + }, + // FIXME + // + // “If 'height' and 'width' both have computed values of 'auto' + // and the element has an intrinsic ratio but no intrinsic height or width, + // […]” + // + // In this `match` expression this would be an additional arm here: + // + // ``` + // (Vec2 { inline: None, block: None }, Some(_)) => {…} + // ``` + // + // “[…] then the used value of 'width' is undefined in CSS 2. + // However, it is suggested that, if the containing block's width + // does not itself depend on the replaced element's width, + // then the used value of 'width' is calculated from the constraint + // equation used for block-level, non-replaced elements in normal flow.” + _ => default_object_size().inline, + }; + let block_size = if let Some(block) = intrinsic_size.block { + block + } else if let Some(i_over_b) = intrinsic_ratio { + // “used width” in CSS 2 is what we just computed above + inline_size / i_over_b + } else { + default_object_size().block + }; + + let i_over_b = if let Some(i_over_b) = intrinsic_ratio { + i_over_b + } else { + return clamp(inline_size, block_size); + }; + + // https://drafts.csswg.org/css2/visudet.html#min-max-widths + // “However, for replaced elements with an intrinsic ratio and both + // 'width' and 'height' specified as 'auto', the algorithm is as follows” + enum Violation { + None, + Below(Length), + Above(Length), + } + let violation = |size, min_size, mut max_size: Option<Length>| { + if let Some(max) = max_size.as_mut() { + max.max_assign(min_size); + } + if size < min_size { + return Violation::Below(min_size); + } + match max_size { + Some(max_size) if size > max_size => Violation::Above(max_size), + _ => Violation::None, + } + }; + match ( + violation(inline_size, min_box_size.inline, max_box_size.inline), + violation(block_size, min_box_size.block, max_box_size.block), + ) { + // Row 1. + (Violation::None, Violation::None) => Vec2 { + inline: inline_size, + block: block_size, + }, + // Row 2. + (Violation::Above(max_inline_size), Violation::None) => Vec2 { + inline: max_inline_size, + block: (max_inline_size / i_over_b).max(min_box_size.block), + }, + // Row 3. + (Violation::Below(min_inline_size), Violation::None) => Vec2 { + inline: min_inline_size, + block: (min_inline_size / i_over_b).clamp_below_max(max_box_size.block), + }, + // Row 4. + (Violation::None, Violation::Above(max_block_size)) => Vec2 { + inline: (max_block_size * i_over_b).max(min_box_size.inline), + block: max_block_size, + }, + // Row 5. + (Violation::None, Violation::Below(min_block_size)) => Vec2 { + inline: (min_block_size * i_over_b).clamp_below_max(max_box_size.inline), + block: min_block_size, + }, + // Rows 6-7. + (Violation::Above(max_inline_size), Violation::Above(max_block_size)) => { + if max_inline_size.px() / inline_size.px() <= + max_block_size.px() / block_size.px() + { + // Row 6. + Vec2 { + inline: max_inline_size, + block: (max_inline_size / i_over_b).max(min_box_size.block), + } + } else { + // Row 7. + Vec2 { + inline: (max_block_size * i_over_b).max(min_box_size.inline), + block: max_block_size, + } + } + }, + // Rows 8-9. + (Violation::Below(min_inline_size), Violation::Below(min_block_size)) => { + if min_inline_size.px() / inline_size.px() <= + min_block_size.px() / block_size.px() + { + // Row 8. + Vec2 { + inline: (min_block_size * i_over_b) + .clamp_below_max(max_box_size.inline), + block: min_block_size, + } + } else { + // Row 9. + Vec2 { + inline: min_inline_size, + block: (min_inline_size / i_over_b) + .clamp_below_max(max_box_size.block), + } + } + }, + // Row 10. + (Violation::Below(min_inline_size), Violation::Above(max_block_size)) => Vec2 { + inline: min_inline_size, + block: max_block_size, + }, + // Row 11. + (Violation::Above(max_inline_size), Violation::Below(min_block_size)) => Vec2 { + inline: max_inline_size, + block: min_block_size, + }, + } + }, + } + } } diff --git a/components/layout_2020/sizing.rs b/components/layout_2020/sizing.rs index 75c13b1dcb5..e3c74d3733f 100644 --- a/components/layout_2020/sizing.rs +++ b/components/layout_2020/sizing.rs @@ -107,17 +107,37 @@ impl BoxContentSizes { &self, style: &ComputedValues, ) -> (ContentSizes, Percentage) { - // FIXME: account for 'min-width', 'max-width', 'box-sizing' - + // FIXME: account for 'box-sizing' let inline_size = style.box_size().inline; + let min_inline_size = style + .min_box_size() + .inline + .percentage_relative_to(Length::zero()) + .auto_is(Length::zero); + let max_inline_size = style + .max_box_size() + .inline + .to_option() + .and_then(|lp| lp.as_length()); + let clamp = |l: Length| l.clamp_between_extremums(min_inline_size, max_inline_size); + // Percentages for 'width' are treated as 'auto' let inline_size = inline_size.map(|lp| lp.as_length()); // The (inner) min/max-content are only used for 'auto' let mut outer = match inline_size.non_auto().flatten() { - None => self.expect_inline().clone(), - Some(length) => ContentSizes { - min_content: length, - max_content: length, + None => { + let inner = self.expect_inline().clone(); + ContentSizes { + min_content: clamp(inner.min_content), + max_content: clamp(inner.max_content), + } + }, + Some(length) => { + let length = clamp(length); + ContentSizes { + min_content: length, + max_content: length, + } }, }; diff --git a/components/layout_2020/style_ext.rs b/components/layout_2020/style_ext.rs index 59c87fd633c..67d8bc78d7e 100644 --- a/components/layout_2020/style_ext.rs +++ b/components/layout_2020/style_ext.rs @@ -40,7 +40,7 @@ pub(crate) enum DisplayInside { } pub(crate) trait ComputedValuesExt { - fn inline_size_is_auto(&self) -> bool; + fn inline_size_is_length(&self) -> bool; fn inline_box_offsets_are_both_non_auto(&self) -> bool; fn box_offsets(&self) -> flow_relative::Sides<LengthPercentageOrAuto>; fn box_size(&self) -> flow_relative::Vec2<LengthPercentageOrAuto>; @@ -52,14 +52,14 @@ pub(crate) trait ComputedValuesExt { } impl ComputedValuesExt for ComputedValues { - fn inline_size_is_auto(&self) -> bool { + fn inline_size_is_length(&self) -> bool { let position = self.get_position(); let size = if self.writing_mode.is_horizontal() { position.width } else { position.height }; - size == Size::Auto + matches!(size, Size::LengthPercentage(lp) if lp.0.as_length().is_some()) } fn inline_box_offsets_are_both_non_auto(&self) -> bool { diff --git a/components/style/values/computed/length.rs b/components/style/values/computed/length.rs index 06631a354df..2423d447a6a 100644 --- a/components/style/values/computed/length.rs +++ b/components/style/values/computed/length.rs @@ -657,14 +657,14 @@ impl CSSPixelLength { /// Return the containing pixel value. #[inline] - pub fn px(&self) -> CSSFloat { + pub fn px(self) -> CSSFloat { self.0 } /// Return the length with app_unit i32 type. #[inline] - pub fn to_i32_au(&self) -> i32 { - Au::from(*self).0 + pub fn to_i32_au(self) -> i32 { + Au::from(self).0 } /// Return the absolute value of this length. @@ -692,9 +692,29 @@ impl CSSPixelLength { } /// Sets `self` to the maximum between `self` and `other`. + #[inline] pub fn max_assign(&mut self, other: Self) { *self = self.max(other); } + + /// Clamp the value to a lower bound and an optional upper bound. + /// + /// Can be used for example with `min-width` and `max-width`. + #[inline] + pub fn clamp_between_extremums(self, min_size: Self, max_size: Option<Self>) -> Self { + self.clamp_below_max(max_size).max(min_size) + } + + /// Clamp the value to an optional upper bound. + /// + /// Can be used for example with `max-width`. + #[inline] + pub fn clamp_below_max(self, max_size: Option<Self>) -> Self { + match max_size { + None => self, + Some(max_size) => self.min(max_size), + } + } } impl Zero for CSSPixelLength { diff --git a/components/style/values/generics/length.rs b/components/style/values/generics/length.rs index 4183f40a942..b2f34058283 100644 --- a/components/style/values/generics/length.rs +++ b/components/style/values/generics/length.rs @@ -207,6 +207,15 @@ impl<LengthPercentage> MaxSize<LengthPercentage> { pub fn none() -> Self { MaxSize::None } + + /// Convert + #[cfg(not(feature = "gecko"))] + pub fn to_option(self) -> Option<LengthPercentage> { + match self { + Self::LengthPercentage(lp) => Some(lp), + Self::None => None, + } + } } /// A generic `<length>` | `<number>` value for the `-moz-tab-size` property. diff --git a/tests/wpt/metadata-layout-2020/css/CSS2/box-display/containing-block-008.xht.ini b/tests/wpt/metadata-layout-2020/css/CSS2/box-display/containing-block-008.xht.ini new file mode 100644 index 00000000000..dd630be0f54 --- /dev/null +++ b/tests/wpt/metadata-layout-2020/css/CSS2/box-display/containing-block-008.xht.ini @@ -0,0 +1,2 @@ +[containing-block-008.xht] + expected: FAIL diff --git a/tests/wpt/metadata-layout-2020/css/CSS2/box-display/containing-block-009.xht.ini b/tests/wpt/metadata-layout-2020/css/CSS2/box-display/containing-block-009.xht.ini new file mode 100644 index 00000000000..4d8ca58286c --- /dev/null +++ b/tests/wpt/metadata-layout-2020/css/CSS2/box-display/containing-block-009.xht.ini @@ -0,0 +1,2 @@ +[containing-block-009.xht] + expected: FAIL diff --git a/tests/wpt/metadata-layout-2020/css/CSS2/box-display/containing-block-010.xht.ini b/tests/wpt/metadata-layout-2020/css/CSS2/box-display/containing-block-010.xht.ini new file mode 100644 index 00000000000..af40eef0a5d --- /dev/null +++ b/tests/wpt/metadata-layout-2020/css/CSS2/box-display/containing-block-010.xht.ini @@ -0,0 +1,2 @@ +[containing-block-010.xht] + expected: FAIL diff --git a/tests/wpt/metadata-layout-2020/css/CSS2/box-display/containing-block-027.xht.ini b/tests/wpt/metadata-layout-2020/css/CSS2/box-display/containing-block-027.xht.ini new file mode 100644 index 00000000000..3d51008fd8a --- /dev/null +++ b/tests/wpt/metadata-layout-2020/css/CSS2/box-display/containing-block-027.xht.ini @@ -0,0 +1,2 @@ +[containing-block-027.xht] + expected: FAIL diff --git a/tests/wpt/metadata-layout-2020/css/CSS2/box-display/containing-block-028.xht.ini b/tests/wpt/metadata-layout-2020/css/CSS2/box-display/containing-block-028.xht.ini new file mode 100644 index 00000000000..51d02b1875c --- /dev/null +++ b/tests/wpt/metadata-layout-2020/css/CSS2/box-display/containing-block-028.xht.ini @@ -0,0 +1,2 @@ +[containing-block-028.xht] + expected: FAIL diff --git a/tests/wpt/metadata-layout-2020/css/CSS2/box-display/display-change-001.xht.ini b/tests/wpt/metadata-layout-2020/css/CSS2/box-display/display-change-001.xht.ini deleted file mode 100644 index 6b39c7c47cb..00000000000 --- a/tests/wpt/metadata-layout-2020/css/CSS2/box-display/display-change-001.xht.ini +++ /dev/null @@ -1,2 +0,0 @@ -[display-change-001.xht] - expected: FAIL |