diff options
author | Nico Burns <nico@nicoburns.com> | 2024-11-22 09:21:01 +1300 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-11-21 20:21:01 +0000 |
commit | 6cbd89dbb0452f30477671ff72ff6c03b3fac097 (patch) | |
tree | cb2df73c07a5cddf6c05ccc9e11f4f1cb146bf2f /components | |
parent | 339062c890361017d91ec84121c833ce5ee43c84 (diff) | |
download | servo-6cbd89dbb0452f30477671ff72ff6c03b3fac097.tar.gz servo-6cbd89dbb0452f30477671ff72ff6c03b3fac097.zip |
Layout: Implement CSS Grid using `taffy` (#32619)
* Add layout.grid.enabled pref
Signed-off-by: Nico Burns <nico@nicoburns.com>
* Add taffy dependency
Signed-off-by: Nico Burns <nico@nicoburns.com>
* Import taffy <-> stylo conversion code from taffy_stylo crate
Signed-off-by: Nico Burns <nico@nicoburns.com>
* Add `Grid` variant to DisplayInside
Signed-off-by: Nico Burns <nico@nicoburns.com>
* Implement CSS Grid using Taffy
Signed-off-by: Nico Burns <nico@nicoburns.com>
Import full stylo_taffy crate
Signed-off-by: Nico Burns <nico@nicoburns.com>
Squashed PR feedback changes
Deduplicate is_document_only_whitespace
Signed-off-by: Nico Burns <nico@nicoburns.com>
Import taffy::AvailableSpace
Signed-off-by: Nico Burns <nico@nicoburns.com>
Rename FlexContext to TaffyContainerContext
Signed-off-by: Nico Burns <nico@nicoburns.com>
Eliminate references to flexbox in taffy/layout module
Signed-off-by: Nico Burns <nico@nicoburns.com>
Use constructors for geom types
Signed-off-by: Nico Burns <nico@nicoburns.com>
Remove comment about abspos elements splitting contiguous text runs
Signed-off-by: Nico Burns <nico@nicoburns.com>
Remove reference to flexbox in taffy/construct
Signed-off-by: Nico Burns <nico@nicoburns.com>
Deduplicate construction of flexbox/grid containers
Signed-off-by: Nico Burns <nico@nicoburns.com>
Make anonymous text runs InFlow
Signed-off-by: Nico Burns <nico@nicoburns.com>
Remove commented code
Signed-off-by: Nico Burns <nico@nicoburns.com>
Update comments
Signed-off-by: Nico Burns <nico@nicoburns.com>
Inline/vendor the stylo/taffy interop code
Signed-off-by: Nico Burns <nico@nicoburns.com>
* Update test expectations
Signed-off-by: Nico Burns <nico@nicoburns.com>
* Fix nits from PR review
Signed-off-by: Nico Burns <nico@nicoburns.com>
---------
Signed-off-by: Nico Burns <nico@nicoburns.com>
Diffstat (limited to 'components')
-rw-r--r-- | components/config/prefs.rs | 3 | ||||
-rw-r--r-- | components/layout_2020/Cargo.toml | 1 | ||||
-rw-r--r-- | components/layout_2020/construct_modern.rs (renamed from components/layout_2020/flexbox/construct.rs) | 205 | ||||
-rw-r--r-- | components/layout_2020/dom.rs | 2 | ||||
-rw-r--r-- | components/layout_2020/flexbox/mod.rs | 54 | ||||
-rw-r--r-- | components/layout_2020/flow/root.rs | 19 | ||||
-rw-r--r-- | components/layout_2020/formatting_contexts.rs | 17 | ||||
-rw-r--r-- | components/layout_2020/lib.rs | 2 | ||||
-rw-r--r-- | components/layout_2020/positioned.rs | 4 | ||||
-rw-r--r-- | components/layout_2020/style_ext.rs | 3 | ||||
-rw-r--r-- | components/layout_2020/taffy/layout.rs | 604 | ||||
-rw-r--r-- | components/layout_2020/taffy/mod.rs | 122 | ||||
-rw-r--r-- | components/layout_2020/taffy/stylo_taffy/convert.rs | 338 | ||||
-rw-r--r-- | components/layout_2020/taffy/stylo_taffy/mod.rs | 9 | ||||
-rw-r--r-- | components/layout_2020/taffy/stylo_taffy/wrapper.rs | 226 |
15 files changed, 1500 insertions, 109 deletions
diff --git a/components/config/prefs.rs b/components/config/prefs.rs index d525f7bf4fa..e76c6c3128b 100644 --- a/components/config/prefs.rs +++ b/components/config/prefs.rs @@ -547,6 +547,9 @@ mod gen { flexbox: { enabled: bool, }, + grid: { + enabled: bool, + }, legacy_layout: bool, #[serde(default = "default_layout_threads")] threads: i64, diff --git a/components/layout_2020/Cargo.toml b/components/layout_2020/Cargo.toml index bcd04e67f86..31a78ed2361 100644 --- a/components/layout_2020/Cargo.toml +++ b/components/layout_2020/Cargo.toml @@ -49,6 +49,7 @@ servo_geometry = { path = "../geometry" } servo_url = { path = "../url" } style = { workspace = true } style_traits = { workspace = true } +taffy = { workspace = true } tracing = { workspace = true, optional = true } unicode-bidi = { workspace = true } unicode-script = { workspace = true } diff --git a/components/layout_2020/flexbox/construct.rs b/components/layout_2020/construct_modern.rs index 260e5266b82..5ff9aa08216 100644 --- a/components/layout_2020/flexbox/construct.rs +++ b/components/layout_2020/construct_modern.rs @@ -2,80 +2,80 @@ * 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/. */ +//! Layout construction code that is shared between modern layout modes (Flexbox and CSS Grid) + use std::borrow::Cow; use rayon::iter::{IntoParallelIterator, ParallelIterator}; -use style::values::specified::text::TextDecorationLine; +use style::values::computed::TextDecorationLine; -use super::{FlexContainer, FlexItemBox, FlexLevelBox}; -use crate::cell::ArcRefCell; use crate::context::LayoutContext; -use crate::dom::{BoxSlot, LayoutBox, NodeExt}; -use crate::dom_traversal::{Contents, NodeAndStyleInfo, NonReplacedContents, TraversalHandler}; +use crate::dom::{BoxSlot, NodeExt}; +use crate::dom_traversal::{Contents, NodeAndStyleInfo, TraversalHandler}; use crate::flow::inline::construct::InlineFormattingContextBuilder; use crate::flow::{BlockContainer, BlockFormattingContext}; use crate::formatting_contexts::{ IndependentFormattingContext, NonReplacedFormattingContext, NonReplacedFormattingContextContents, }; -use crate::positioned::AbsolutelyPositionedBox; use crate::style_ext::DisplayGeneratingBox; -impl FlexContainer { - pub fn construct<'dom>( - context: &LayoutContext, - info: &NodeAndStyleInfo<impl NodeExt<'dom>>, - contents: NonReplacedContents, - propagated_text_decoration_line: TextDecorationLine, - ) -> Self { - let text_decoration_line = - propagated_text_decoration_line | info.style.clone_text_decoration_line(); - let mut builder = FlexContainerBuilder { - context, - info, - text_decoration_line, - contiguous_text_runs: Vec::new(), - jobs: Vec::new(), - has_text_runs: false, - }; - contents.traverse(context, info, &mut builder); - builder.finish() - } -} - /// <https://drafts.csswg.org/css-flexbox/#flex-items> -struct FlexContainerBuilder<'a, 'dom, Node> { +pub(crate) struct ModernContainerBuilder<'a, 'dom, Node> { context: &'a LayoutContext<'a>, info: &'a NodeAndStyleInfo<Node>, text_decoration_line: TextDecorationLine, - contiguous_text_runs: Vec<FlexTextRun<'dom, Node>>, + contiguous_text_runs: Vec<ModernContainerTextRun<'dom, Node>>, /// To be run in parallel with rayon in `finish` - jobs: Vec<FlexLevelJob<'dom, Node>>, + jobs: Vec<ModernContainerJob<'dom, Node>>, has_text_runs: bool, } -enum FlexLevelJob<'dom, Node> { - /// Or pseudo-element - Element { +enum ModernContainerJob<'dom, Node> { + ElementOrPseudoElement { info: NodeAndStyleInfo<Node>, display: DisplayGeneratingBox, contents: Contents, box_slot: BoxSlot<'dom>, }, - TextRuns(Vec<FlexTextRun<'dom, Node>>), + TextRuns(Vec<ModernContainerTextRun<'dom, Node>>), } -struct FlexTextRun<'dom, Node> { +struct ModernContainerTextRun<'dom, Node> { info: NodeAndStyleInfo<Node>, text: Cow<'dom, str>, } -impl<'a, 'dom, Node: 'dom> TraversalHandler<'dom, Node> for FlexContainerBuilder<'a, 'dom, Node> +impl<Node> ModernContainerTextRun<'_, Node> { + /// <https://drafts.csswg.org/css-text/#white-space> + fn is_only_document_white_space(&self) -> bool { + // FIXME: is this the right definition? See + // https://github.com/w3c/csswg-drafts/issues/5146 + // https://github.com/w3c/csswg-drafts/issues/5147 + self.text + .bytes() + .all(|byte| matches!(byte, b' ' | b'\n' | b'\t')) + } +} + +pub(crate) enum ModernItemKind { + InFlow, + OutOfFlow, +} + +pub(crate) struct ModernItem<'dom> { + pub kind: ModernItemKind, + pub order: i32, + pub box_slot: Option<BoxSlot<'dom>>, + pub formatting_context: IndependentFormattingContext, +} + +impl<'a, 'dom, Node: 'dom> TraversalHandler<'dom, Node> for ModernContainerBuilder<'a, 'dom, Node> where Node: NodeExt<'dom>, { fn handle_text(&mut self, info: &NodeAndStyleInfo<Node>, text: Cow<'dom, str>) { - self.contiguous_text_runs.push(FlexTextRun { + self.contiguous_text_runs.push(ModernContainerTextRun { info: info.clone(), text, }) @@ -89,12 +89,9 @@ where contents: Contents, box_slot: BoxSlot<'dom>, ) { - // FIXME: are text runs considered "contiguous" if they are only separated - // by an out-of-flow abspos element? - // (That is, are they wrapped in the same anonymous flex item, or each its own?) self.wrap_any_text_in_anonymous_block_container(); - self.jobs.push(FlexLevelJob::Element { + self.jobs.push(ModernContainerJob::ElementOrPseudoElement { info: info.clone(), display, contents, @@ -103,31 +100,39 @@ where } } -/// <https://drafts.csswg.org/css-text/#white-space> -fn is_only_document_white_space<Node>(run: &FlexTextRun<'_, Node>) -> bool { - // FIXME: is this the right definition? See - // https://github.com/w3c/csswg-drafts/issues/5146 - // https://github.com/w3c/csswg-drafts/issues/5147 - run.text - .bytes() - .all(|byte| matches!(byte, b' ' | b'\n' | b'\t')) -} - -impl<'a, 'dom, Node: 'dom> FlexContainerBuilder<'a, 'dom, Node> +impl<'a, 'dom, Node: 'dom> ModernContainerBuilder<'a, 'dom, Node> where Node: NodeExt<'dom>, { + pub fn new( + context: &'a LayoutContext<'a>, + info: &'a NodeAndStyleInfo<Node>, + text_decoration_line: TextDecorationLine, + ) -> Self { + ModernContainerBuilder { + context, + info, + text_decoration_line, + contiguous_text_runs: Vec::new(), + jobs: Vec::new(), + has_text_runs: false, + } + } + fn wrap_any_text_in_anonymous_block_container(&mut self) { let runs = std::mem::take(&mut self.contiguous_text_runs); - if runs.iter().all(is_only_document_white_space) { + if runs + .iter() + .all(ModernContainerTextRun::is_only_document_white_space) + { // There is no text run, or they all only contain document white space characters } else { - self.jobs.push(FlexLevelJob::TextRuns(runs)); + self.jobs.push(ModernContainerJob::TextRuns(runs)); self.has_text_runs = true; } } - fn finish(mut self) -> FlexContainer { + pub(crate) fn finish(mut self) -> Vec<ModernItem<'dom>> { self.wrap_any_text_in_anonymous_block_container(); let anonymous_style = if self.has_text_runs { @@ -145,10 +150,10 @@ where None }; - let mut children = std::mem::take(&mut self.jobs) + let mut children: Vec<ModernItem> = std::mem::take(&mut self.jobs) .into_par_iter() .filter_map(|job| match job { - FlexLevelJob::TextRuns(runs) => { + ModernContainerJob::TextRuns(runs) => { let mut inline_formatting_context_builder = InlineFormattingContextBuilder::new(); for flex_text_run in runs.into_iter() { @@ -176,62 +181,58 @@ where block_formatting_context, ), }; - - Some(ArcRefCell::new(FlexLevelBox::FlexItem(FlexItemBox { - independent_formatting_context: IndependentFormattingContext::NonReplaced( - non_replaced, - ), - block_content_size_cache: Default::default(), - }))) + let formatting_context = + IndependentFormattingContext::NonReplaced(non_replaced); + + Some(ModernItem { + kind: ModernItemKind::InFlow, + order: 0, + box_slot: None, + formatting_context, + }) }, - FlexLevelJob::Element { + ModernContainerJob::ElementOrPseudoElement { info, display, contents, box_slot, } => { - let display_inside = match display { - DisplayGeneratingBox::OutsideInside { inside, .. } => inside, - DisplayGeneratingBox::LayoutInternal(_) => display.display_inside(), - }; - let box_ = if info.style.get_box().position.is_absolutely_positioned() { - // https://drafts.csswg.org/css-flexbox/#abspos-items - ArcRefCell::new(FlexLevelBox::OutOfFlowAbsolutelyPositionedBox( - ArcRefCell::new(AbsolutelyPositionedBox::construct( - self.context, - &info, - display_inside, - contents, - )), - )) + let is_abspos = info.style.get_box().position.is_absolutely_positioned(); + let formatting_context = IndependentFormattingContext::construct( + self.context, + &info, + display.display_inside(), + contents, + // Text decorations are not propagated to any out-of-flow descendants. + if is_abspos { + TextDecorationLine::NONE + } else { + self.text_decoration_line + }, + ); + + if is_abspos { + Some(ModernItem { + kind: ModernItemKind::OutOfFlow, + order: 0, + box_slot: Some(box_slot), + formatting_context, + }) } else { - ArcRefCell::new(FlexLevelBox::FlexItem(FlexItemBox { - independent_formatting_context: IndependentFormattingContext::construct( - self.context, - &info, - display_inside, - contents, - self.text_decoration_line, - ), - block_content_size_cache: Default::default(), - })) - }; - box_slot.set(LayoutBox::FlexLevel(box_.clone())); - Some(box_) + Some(ModernItem { + kind: ModernItemKind::InFlow, + order: info.style.clone_order(), + box_slot: Some(box_slot), + formatting_context, + }) + } }, }) .collect::<Vec<_>>(); // https://drafts.csswg.org/css-flexbox/#order-modified-document-order - children.sort_by_key(|child| match &*child.borrow() { - FlexLevelBox::FlexItem(item) => item.style().clone_order(), - - // “Absolutely-positioned children of a flex container are treated - // as having order: 0 for the purpose of determining their painting order - // relative to flex items.” - FlexLevelBox::OutOfFlowAbsolutelyPositionedBox(_) => 0, - }); + children.sort_by_key(|child| child.order); - FlexContainer::new(&self.info.style, children) + children } } diff --git a/components/layout_2020/dom.rs b/components/layout_2020/dom.rs index e742b7e663a..1b561969121 100644 --- a/components/layout_2020/dom.rs +++ b/components/layout_2020/dom.rs @@ -27,6 +27,7 @@ use crate::flow::inline::InlineItem; use crate::flow::BlockLevelBox; use crate::geom::PhysicalSize; use crate::replaced::{CanvasInfo, CanvasSource}; +use crate::taffy::TaffyItemBox; /// The data that is stored in each DOM node that is used by layout. #[derive(Default)] @@ -44,6 +45,7 @@ pub(super) enum LayoutBox { InlineBox(ArcRefCell<InlineBox>), InlineLevel(ArcRefCell<InlineItem>), FlexLevel(ArcRefCell<FlexLevelBox>), + TaffyItemBox(ArcRefCell<TaffyItemBox>), } /// A wrapper for [`InnerDOMLayoutData`]. This is necessary to give the entire data diff --git a/components/layout_2020/flexbox/mod.rs b/components/layout_2020/flexbox/mod.rs index 0e6f7837425..ceca8bad161 100644 --- a/components/layout_2020/flexbox/mod.rs +++ b/components/layout_2020/flexbox/mod.rs @@ -13,13 +13,17 @@ use style::properties::longhands::flex_wrap::computed_value::T as FlexWrap; use style::properties::ComputedValues; use style::values::computed::{AlignContent, JustifyContent}; use style::values::specified::align::AlignFlags; +use style::values::specified::text::TextDecorationLine; use crate::cell::ArcRefCell; +use crate::construct_modern::{ModernContainerBuilder, ModernItemKind}; +use crate::context::LayoutContext; +use crate::dom::{LayoutBox, NodeExt}; +use crate::dom_traversal::{NodeAndStyleInfo, NonReplacedContents}; use crate::formatting_contexts::IndependentFormattingContext; use crate::fragment_tree::BaseFragmentInfo; use crate::positioned::AbsolutelyPositionedBox; -mod construct; mod geom; mod layout; @@ -95,14 +99,45 @@ pub(crate) struct FlexContainer { } impl FlexContainer { - pub(crate) fn new( - style: &ServoArc<ComputedValues>, - children: Vec<ArcRefCell<FlexLevelBox>>, + pub fn construct<'dom>( + context: &LayoutContext, + info: &NodeAndStyleInfo<impl NodeExt<'dom>>, + contents: NonReplacedContents, + propagated_text_decoration_line: TextDecorationLine, ) -> Self { + let text_decoration_line = + propagated_text_decoration_line | info.style.clone_text_decoration_line(); + + let mut builder = ModernContainerBuilder::new(context, info, text_decoration_line); + contents.traverse(context, info, &mut builder); + let items = builder.finish(); + + let children = items + .into_iter() + .map(|item| { + let box_ = match item.kind { + ModernItemKind::InFlow => ArcRefCell::new(FlexLevelBox::FlexItem( + FlexItemBox::new(item.formatting_context), + )), + ModernItemKind::OutOfFlow => { + let abs_pos_box = + ArcRefCell::new(AbsolutelyPositionedBox::new(item.formatting_context)); + ArcRefCell::new(FlexLevelBox::OutOfFlowAbsolutelyPositionedBox(abs_pos_box)) + }, + }; + + if let Some(box_slot) = item.box_slot { + box_slot.set(LayoutBox::FlexLevel(box_.clone())); + } + + box_ + }) + .collect(); + Self { children, - style: style.clone(), - config: FlexContainerConfig::new(style), + style: info.style.clone(), + config: FlexContainerConfig::new(&info.style), } } } @@ -127,6 +162,13 @@ impl std::fmt::Debug for FlexItemBox { } impl FlexItemBox { + fn new(independent_formatting_context: IndependentFormattingContext) -> Self { + Self { + independent_formatting_context, + block_content_size_cache: Default::default(), + } + } + fn style(&self) -> &ServoArc<ComputedValues> { self.independent_formatting_context.style() } diff --git a/components/layout_2020/flow/root.rs b/components/layout_2020/flow/root.rs index 885e9be2024..b1c62416ecc 100644 --- a/components/layout_2020/flow/root.rs +++ b/components/layout_2020/flow/root.rs @@ -30,6 +30,7 @@ use crate::geom::{LogicalVec2, PhysicalPoint, PhysicalRect, PhysicalSize}; use crate::positioned::{AbsolutelyPositionedBox, PositioningContext}; use crate::replaced::ReplacedContent; use crate::style_ext::{ComputedValuesExt, Display, DisplayGeneratingBox, DisplayInside}; +use crate::taffy::{TaffyItemBox, TaffyItemBoxInner}; use crate::DefiniteContainingBlock; #[derive(Serialize)] @@ -127,6 +128,7 @@ impl BoxTree { AbsolutelyPositionedBlockLevelBox(ArcRefCell<BlockLevelBox>), AbsolutelyPositionedInlineLevelBox(ArcRefCell<InlineItem>, usize), AbsolutelyPositionedFlexLevelBox(ArcRefCell<FlexLevelBox>), + AbsolutelyPositionedTaffyLevelBox(ArcRefCell<TaffyItemBox>), } fn update_point<'dom, Node>( @@ -203,6 +205,17 @@ impl BoxTree { }, _ => return None, }, + LayoutBox::TaffyItemBox(taffy_level_box) => match &taffy_level_box + .borrow() + .taffy_level_box + { + TaffyItemBoxInner::OutOfFlowAbsolutelyPositionedBox(_) + if box_style.position.is_absolutely_positioned() => + { + UpdatePoint::AbsolutelyPositionedTaffyLevelBox(taffy_level_box.clone()) + }, + _ => return None, + }, }; Some((primary_style.clone(), display_inside, update_point)) } @@ -238,6 +251,12 @@ impl BoxTree { out_of_flow_absolutely_positioned_box, ); }, + UpdatePoint::AbsolutelyPositionedTaffyLevelBox(taffy_level_box) => { + taffy_level_box.borrow_mut().taffy_level_box = + TaffyItemBoxInner::OutOfFlowAbsolutelyPositionedBox( + out_of_flow_absolutely_positioned_box, + ); + }, } return true; } diff --git a/components/layout_2020/formatting_contexts.rs b/components/layout_2020/formatting_contexts.rs index 8b638b028f7..494e9859983 100644 --- a/components/layout_2020/formatting_contexts.rs +++ b/components/layout_2020/formatting_contexts.rs @@ -23,6 +23,7 @@ use crate::replaced::ReplacedContent; use crate::sizing::{self, InlineContentSizesResult}; use crate::style_ext::{AspectRatio, DisplayInside}; use crate::table::Table; +use crate::taffy::TaffyContainer; use crate::{ ConstraintSpace, ContainingBlock, IndefiniteContainingBlock, LogicalVec2, SizeConstraint, }; @@ -58,6 +59,7 @@ pub(crate) struct ReplacedFormattingContext { pub(crate) enum NonReplacedFormattingContextContents { Flow(BlockFormattingContext), Flex(FlexContainer), + Grid(TaffyContainer), Table(Table), // Other layout modes go here } @@ -129,6 +131,14 @@ impl IndependentFormattingContext { ), ) }, + DisplayInside::Grid => { + NonReplacedFormattingContextContents::Grid(TaffyContainer::construct( + context, + node_and_style_info, + non_replaced_contents, + propagated_text_decoration_line, + )) + }, DisplayInside::Flex => { NonReplacedFormattingContextContents::Flex(FlexContainer::construct( context, @@ -268,6 +278,12 @@ impl NonReplacedFormattingContext { containing_block_for_children, containing_block, ), + NonReplacedFormattingContextContents::Grid(fc) => fc.layout( + layout_context, + positioning_context, + containing_block_for_children, + containing_block, + ), NonReplacedFormattingContextContents::Table(table) => table.layout( layout_context, positioning_context, @@ -331,6 +347,7 @@ impl NonReplacedFormattingContextContents { .contents .inline_content_sizes(layout_context, constraint_space), Self::Flex(inner) => inner.inline_content_sizes(layout_context, constraint_space), + Self::Grid(inner) => inner.inline_content_sizes(layout_context, constraint_space), Self::Table(table) => table.inline_content_sizes(layout_context, constraint_space), } } diff --git a/components/layout_2020/lib.rs b/components/layout_2020/lib.rs index 5a4dfc5e72b..341e5d3f320 100644 --- a/components/layout_2020/lib.rs +++ b/components/layout_2020/lib.rs @@ -14,8 +14,10 @@ pub mod flow; mod formatting_contexts; mod fragment_tree; pub mod geom; +mod taffy; #[macro_use] pub mod layout_debug; +mod construct_modern; mod lists; mod positioned; pub mod query; diff --git a/components/layout_2020/positioned.rs b/components/layout_2020/positioned.rs index 5a85733a603..2e4aebaf4c4 100644 --- a/components/layout_2020/positioned.rs +++ b/components/layout_2020/positioned.rs @@ -56,6 +56,10 @@ pub(crate) struct HoistedAbsolutelyPositionedBox { } impl AbsolutelyPositionedBox { + pub fn new(context: IndependentFormattingContext) -> Self { + Self { context } + } + pub fn construct<'dom>( context: &LayoutContext, node_info: &NodeAndStyleInfo<impl NodeExt<'dom>>, diff --git a/components/layout_2020/style_ext.rs b/components/layout_2020/style_ext.rs index 0e9e873c701..0db2d8d7f8f 100644 --- a/components/layout_2020/style_ext.rs +++ b/components/layout_2020/style_ext.rs @@ -91,6 +91,7 @@ pub(crate) enum DisplayInside { Flow { is_list_item: bool }, FlowRoot { is_list_item: bool }, Flex, + Grid, Table, } @@ -1064,7 +1065,7 @@ impl From<stylo::Display> for Display { is_list_item: packed.is_list_item(), }, stylo::DisplayInside::Flex => DisplayInside::Flex, - stylo::DisplayInside::Grid => todo!("Grid support is not yet implemented."), + stylo::DisplayInside::Grid => DisplayInside::Grid, // These should not be values of DisplayInside, but oh well stylo::DisplayInside::None => return Display::None, diff --git a/components/layout_2020/taffy/layout.rs b/components/layout_2020/taffy/layout.rs new file mode 100644 index 00000000000..e1d35380513 --- /dev/null +++ b/components/layout_2020/taffy/layout.rs @@ -0,0 +1,604 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * 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 app_units::Au; +use atomic_refcell::{AtomicRef, AtomicRefCell, AtomicRefMut}; +use style::properties::ComputedValues; +use style::values::generics::length::{GenericLengthPercentageOrAuto, LengthPercentageOrAuto}; +use style::values::specified::align::AlignFlags; +use style::values::specified::box_::DisplayInside; +use style::Zero; +use taffy::style_helpers::{TaffyMaxContent, TaffyMinContent}; +use taffy::{AvailableSpace, MaybeMath}; + +use super::{TaffyContainer, TaffyItemBox, TaffyItemBoxInner, TaffyStyloStyle}; +use crate::cell::ArcRefCell; +use crate::context::LayoutContext; +use crate::formatting_contexts::{Baselines, IndependentFormattingContext, IndependentLayout}; +use crate::fragment_tree::{BoxFragment, CollapsedBlockMargins, Fragment}; +use crate::geom::{ + LogicalSides, LogicalVec2, PhysicalPoint, PhysicalRect, PhysicalSides, PhysicalSize, Size, + SizeConstraint, +}; +use crate::positioned::{AbsolutelyPositionedBox, PositioningContext, PositioningContextLength}; +use crate::sizing::{ContentSizes, InlineContentSizesResult}; +use crate::style_ext::ComputedValuesExt; +use crate::{ConstraintSpace, ContainingBlock}; + +const DUMMY_NODE_ID: taffy::NodeId = taffy::NodeId::new(u64::MAX); + +fn resolve_content_size(constraint: AvailableSpace, content_sizes: ContentSizes) -> f32 { + match constraint { + AvailableSpace::Definite(limit) => { + let min = content_sizes.min_content.to_f32_px(); + let max = content_sizes.max_content.to_f32_px(); + limit.min(max).max(min) + }, + AvailableSpace::MinContent => content_sizes.min_content.to_f32_px(), + AvailableSpace::MaxContent => content_sizes.max_content.to_f32_px(), + } +} + +#[inline(always)] +fn with_independant_formatting_context<T>( + item: &mut TaffyItemBoxInner, + cb: impl FnOnce(&mut IndependentFormattingContext) -> T, +) -> T { + match item { + TaffyItemBoxInner::InFlowBox(ref mut context) => cb(context), + TaffyItemBoxInner::OutOfFlowAbsolutelyPositionedBox(ref abspos_box) => { + let mut abspos_box = AtomicRefCell::borrow_mut(abspos_box); + cb(&mut abspos_box.context) + }, + } +} + +/// Layout parameters and intermediate results about a taffy container, +/// grouped to avoid passing around many parameters +struct TaffyContainerContext<'a> { + source_child_nodes: &'a [ArcRefCell<TaffyItemBox>], + layout_context: &'a LayoutContext<'a>, + positioning_context: &'a mut PositioningContext, + content_box_size_override: &'a ContainingBlock<'a>, + style: &'a ComputedValues, +} + +struct ChildIter(std::ops::Range<usize>); +impl Iterator for ChildIter { + type Item = taffy::NodeId; + fn next(&mut self) -> Option<Self::Item> { + self.0.next().map(taffy::NodeId::from) + } +} + +impl taffy::TraversePartialTree for TaffyContainerContext<'_> { + type ChildIter<'a> = ChildIter where Self: 'a; + + fn child_ids(&self, _node_id: taffy::NodeId) -> Self::ChildIter<'_> { + ChildIter(0..self.source_child_nodes.len()) + } + + fn child_count(&self, _node_id: taffy::NodeId) -> usize { + self.source_child_nodes.len() + } + + fn get_child_id(&self, _node_id: taffy::NodeId, index: usize) -> taffy::NodeId { + taffy::NodeId::from(index) + } +} + +impl taffy::LayoutPartialTree for TaffyContainerContext<'_> { + type CoreContainerStyle<'a> = TaffyStyloStyle<&'a ComputedValues> where Self: 'a; + type CacheMut<'b> = AtomicRefMut<'b, taffy::Cache> where Self: 'b; + + fn get_core_container_style(&self, _node_id: taffy::NodeId) -> Self::CoreContainerStyle<'_> { + TaffyStyloStyle(self.style) + } + + fn set_unrounded_layout(&mut self, node_id: taffy::NodeId, layout: &taffy::Layout) { + let id = usize::from(node_id); + (*self.source_child_nodes[id]).borrow_mut().taffy_layout = *layout; + } + + fn get_cache_mut(&mut self, node_id: taffy::NodeId) -> AtomicRefMut<'_, taffy::Cache> { + let id = usize::from(node_id); + let mut_ref: AtomicRefMut<'_, _> = (*self.source_child_nodes[id]).borrow_mut(); + AtomicRefMut::map(mut_ref, |node| &mut node.taffy_layout_cache) + } + + fn compute_child_layout( + &mut self, + node_id: taffy::NodeId, + inputs: taffy::LayoutInput, + ) -> taffy::LayoutOutput { + let mut child = (*self.source_child_nodes[usize::from(node_id)]).borrow_mut(); + let child = &mut *child; + + fn option_f32_to_lpa(input: Option<f32>) -> LengthPercentageOrAuto<Au> { + match input { + None => LengthPercentageOrAuto::Auto, + Some(length) => LengthPercentageOrAuto::LengthPercentage(Au::from_f32_px(length)), + } + } + + fn option_f32_to_size(input: Option<f32>) -> Size<Au> { + match input { + None => Size::Initial, + Some(length) => Size::Numeric(Au::from_f32_px(length)), + } + } + + with_independant_formatting_context( + &mut child.taffy_level_box, + |independent_context| -> taffy::LayoutOutput { + match independent_context { + IndependentFormattingContext::Replaced(replaced) => { + // TODO: re-evaluate sizing constraint conversions in light of recent layout_2020 changes + let containing_block = &self.content_box_size_override; + + // Adjust known_dimensions from border box to content box + let pbm = replaced.style.padding_border_margin(containing_block); + let pb_sum = pbm.padding_border_sums.map(|v| v.to_f32_px()); + let content_box_known_dimensions = taffy::Size { + width: inputs + .known_dimensions + .width + .map(|width| width - pb_sum.inline), + height: inputs + .known_dimensions + .height + .map(|height| height - pb_sum.block), + }; + + let content_box_size = replaced + .contents + .used_size_as_if_inline_element_from_content_box_sizes( + containing_block, + &replaced.style, + LogicalVec2 { + inline: option_f32_to_size(content_box_known_dimensions.width), + block: option_f32_to_size(content_box_known_dimensions.height), + }, + LogicalVec2 { + inline: Size::Numeric(Au::zero()), + block: Size::Numeric(Au::zero()), + }, + LogicalVec2 { + inline: Size::Initial, + block: Size::Initial, + }, + pbm.padding_border_sums, + ) + .to_physical_size(self.style.writing_mode); + + child.child_fragments = replaced.contents.make_fragments( + &replaced.style, + containing_block, + content_box_size, + ); + + let computed_size = taffy::Size { + width: inputs.known_dimensions.width.unwrap_or_else(|| { + content_box_size.width.to_f32_px() + + pbm.padding_border_sums.inline.to_f32_px() + }), + height: inputs.known_dimensions.height.unwrap_or_else(|| { + content_box_size.height.to_f32_px() + + pbm.padding_border_sums.block.to_f32_px() + }), + }; + let size = inputs.known_dimensions.unwrap_or(computed_size); + taffy::LayoutOutput { + size, + ..taffy::LayoutOutput::DEFAULT + } + }, + + IndependentFormattingContext::NonReplaced(non_replaced) => { + // TODO: re-evaluate sizing constraint conversions in light of recent layout_2020 changes + let containing_block = &self.content_box_size_override; + + // Adjust known_dimensions from border box to content box + let pbm = non_replaced.style.padding_border_margin(containing_block); + let margin_sum = pbm.margin.auto_is(Au::zero).sum(); + let content_box_inset = + (pbm.padding_border_sums + margin_sum).map(|v| v.to_f32_px()); + let content_box_known_dimensions = + taffy::Size { + width: inputs.known_dimensions.width.map(|width| { + width - pbm.padding_border_sums.inline.to_f32_px() + }), + height: inputs.known_dimensions.height.map(|height| { + height - pbm.padding_border_sums.block.to_f32_px() + }), + }; + + // Compute inline size + let inline_size = content_box_known_dimensions.width.unwrap_or_else(|| { + let constraint_space = ConstraintSpace { + // TODO: pass min- and max- size + block_size: SizeConstraint::new( + inputs.parent_size.height.map(Au::from_f32_px), + Au::zero(), + None, + ), + writing_mode: self.style.writing_mode, + }; + + let result = non_replaced + .inline_content_sizes(self.layout_context, &constraint_space); + let adjusted_available_space = inputs + .available_space + .width + .map_definite_value(|width| width - content_box_inset.inline); + + resolve_content_size(adjusted_available_space, result.sizes) + }); + + let maybe_block_size = + option_f32_to_lpa(content_box_known_dimensions.height); + let content_box_size_override = ContainingBlock { + inline_size: Au::from_f32_px(inline_size), + block_size: maybe_block_size, + style: &non_replaced.style, + }; + + let layout = { + let mut child_positioning_context = PositioningContext::new_for_subtree( + self.positioning_context + .collects_for_nearest_positioned_ancestor(), + ); + let layout = non_replaced.layout( + self.layout_context, + &mut child_positioning_context, + &content_box_size_override, + containing_block, + ); + + // Store layout data on child for later access + child.positioning_context = child_positioning_context; + + layout + }; + + child.child_fragments = layout.fragments; + + let block_size = layout.content_block_size.to_f32_px(); + + let computed_size = taffy::Size { + width: inline_size + pbm.padding_border_sums.inline.to_f32_px(), + height: block_size + pbm.padding_border_sums.block.to_f32_px(), + }; + let size = inputs.known_dimensions.unwrap_or(computed_size); + + taffy::LayoutOutput { + size, + first_baselines: taffy::Point { + x: None, + y: layout.baselines.first.map(|au| au.to_f32_px()), + }, + ..taffy::LayoutOutput::DEFAULT + } + }, + } + }, + ) + } +} + +impl taffy::LayoutGridContainer for TaffyContainerContext<'_> { + type GridContainerStyle<'a> = TaffyStyloStyle<&'a ComputedValues> + where + Self: 'a; + + type GridItemStyle<'a> = TaffyStyloStyle<AtomicRef<'a, ComputedValues>> + where + Self: 'a; + + fn get_grid_container_style( + &self, + _node_id: taffy::prelude::NodeId, + ) -> Self::GridContainerStyle<'_> { + TaffyStyloStyle(self.style) + } + + fn get_grid_child_style( + &self, + child_node_id: taffy::prelude::NodeId, + ) -> Self::GridItemStyle<'_> { + let id = usize::from(child_node_id); + let child = (*self.source_child_nodes[id]).borrow(); + TaffyStyloStyle(AtomicRef::map(child, |c| &*c.style)) + } +} + +impl TaffyContainer { + pub fn inline_content_sizes( + &self, + layout_context: &LayoutContext, + _constraint_space: &ConstraintSpace, + ) -> InlineContentSizesResult { + let style = &self.style; + + let max_content_inputs = taffy::LayoutInput { + run_mode: taffy::RunMode::ComputeSize, + sizing_mode: taffy::SizingMode::InherentSize, + axis: taffy::RequestedAxis::Horizontal, + vertical_margins_are_collapsible: taffy::Line::FALSE, + + known_dimensions: taffy::Size::NONE, + parent_size: taffy::Size::NONE, + available_space: taffy::Size::MAX_CONTENT, + }; + + let min_content_inputs = taffy::LayoutInput { + available_space: taffy::Size::MIN_CONTENT, + ..max_content_inputs + }; + + let containing_block = &ContainingBlock { + inline_size: Au::zero(), + block_size: GenericLengthPercentageOrAuto::Auto, + style, + }; + + let mut grid_context = TaffyContainerContext { + layout_context, + positioning_context: + &mut PositioningContext::new_for_containing_block_for_all_descendants(), + content_box_size_override: containing_block, + style, + source_child_nodes: &self.children, + }; + + let (max_content_output, min_content_output) = match style.clone_display().inside() { + DisplayInside::Grid => { + let max_content_output = taffy::compute_grid_layout( + &mut grid_context, + DUMMY_NODE_ID, + max_content_inputs, + ); + let min_content_output = taffy::compute_grid_layout( + &mut grid_context, + DUMMY_NODE_ID, + min_content_inputs, + ); + (max_content_output, min_content_output) + }, + _ => panic!("Servo is only configured to use Taffy for CSS Grid layout"), + }; + + let pb_sums = style + .padding_border_margin(containing_block) + .padding_border_sums; + + InlineContentSizesResult { + sizes: ContentSizes { + max_content: Au::from_f32_px(max_content_output.size.width) - pb_sums.inline, + min_content: Au::from_f32_px(min_content_output.size.width) - pb_sums.inline, + }, + + // TODO: determine this accurately + // + // "true" is a safe default as it will prevent Servo from performing optimizations based + // on the assumption that the node's size does not depend on block constraints. + depends_on_block_constraints: true, + } + } + + /// <https://drafts.csswg.org/css-grid/#layout-algorithm> + pub(crate) fn layout( + &self, + layout_context: &LayoutContext, + positioning_context: &mut PositioningContext, + content_box_size_override: &ContainingBlock, + containing_block: &ContainingBlock, + ) -> IndependentLayout { + let mut container_ctx = TaffyContainerContext { + layout_context, + positioning_context, + content_box_size_override, + style: content_box_size_override.style, + source_child_nodes: &self.children, + }; + + fn auto_or_to_option<T>(input: GenericLengthPercentageOrAuto<T>) -> Option<T> { + match input { + LengthPercentageOrAuto::LengthPercentage(val) => Some(val), + LengthPercentageOrAuto::Auto => None, + } + } + + let container_style = &content_box_size_override.style; + let align_items = container_style.clone_align_items(); + let justify_items = container_style.clone_justify_items(); + let pbm = container_style.padding_border_margin(containing_block); + + let known_dimensions = taffy::Size { + width: Some( + (content_box_size_override.inline_size + pbm.padding_border_sums.inline) + .to_f32_px(), + ), + height: auto_or_to_option(content_box_size_override.block_size) + .map(Au::to_f32_px) + .maybe_add(pbm.padding_border_sums.block.to_f32_px()), + }; + + let taffy_containing_block = taffy::Size { + width: Some(containing_block.inline_size.to_f32_px()), + height: auto_or_to_option(containing_block.block_size).map(Au::to_f32_px), + }; + + let layout_input = taffy::LayoutInput { + run_mode: taffy::RunMode::PerformLayout, + sizing_mode: taffy::SizingMode::InherentSize, + axis: taffy::RequestedAxis::Vertical, + vertical_margins_are_collapsible: taffy::Line::FALSE, + + known_dimensions, + parent_size: taffy_containing_block, + available_space: taffy_containing_block.map(AvailableSpace::from), + }; + + let output = match container_ctx.style.clone_display().inside() { + DisplayInside::Grid => { + taffy::compute_grid_layout(&mut container_ctx, DUMMY_NODE_ID, layout_input) + }, + _ => panic!("Servo is only configured to use Taffy for CSS Grid layout"), + }; + + // Convert `taffy::Layout` into Servo `Fragment`s + let fragments: Vec<Fragment> = self + .children + .iter() + .map(|child| (**child).borrow_mut()) + .map(|mut child| { + fn rect_to_logical_sides<T>(rect: taffy::Rect<T>) -> LogicalSides<T> { + LogicalSides { + inline_start: rect.left, + inline_end: rect.right, + block_start: rect.top, + block_end: rect.bottom, + } + } + + fn rect_to_physical_sides<T>(rect: taffy::Rect<T>) -> PhysicalSides<T> { + PhysicalSides::new(rect.top, rect.right, rect.bottom, rect.left) + } + + fn size_and_pos_to_logical_rect<T: Default>( + position: taffy::Point<T>, + size: taffy::Size<T>, + ) -> PhysicalRect<T> { + PhysicalRect::new( + PhysicalPoint::new(position.x, position.y), + PhysicalSize::new(size.width, size.height), + ) + } + + let layout = &child.taffy_layout; + + let padding = rect_to_physical_sides(layout.padding.map(Au::from_f32_px)); + let border = rect_to_physical_sides(layout.border.map(Au::from_f32_px)); + let margin = rect_to_physical_sides(layout.margin.map(Au::from_f32_px)); + let logical_margin = rect_to_logical_sides(layout.margin.map(Au::from_f32_px)); + let collapsed_margin = CollapsedBlockMargins::from_margin(&logical_margin); + + // Compute content box size and position. + // + // For the x/y position we have to correct for the difference between the + // content box and the border box for both the parent and the child. + let content_size = size_and_pos_to_logical_rect( + taffy::Point { + x: Au::from_f32_px( + layout.location.x + layout.padding.left + layout.border.left, + ) - pbm.padding.inline_start - + pbm.border.inline_start, + y: Au::from_f32_px( + layout.location.y + layout.padding.top + layout.border.top, + ) - pbm.padding.block_start - + pbm.border.block_start, + }, + taffy::Size { + width: layout.size.width - + layout.padding.left - + layout.padding.right - + layout.border.left - + layout.border.right, + height: layout.size.height - + layout.padding.top - + layout.padding.bottom - + layout.border.top - + layout.border.bottom, + } + .map(Au::from_f32_px), + ); + + match &mut child.taffy_level_box { + TaffyItemBoxInner::InFlowBox(independent_box) => { + let fragment = Fragment::Box( + BoxFragment::new( + independent_box.base_fragment_info(), + independent_box.style().clone(), + std::mem::take(&mut child.child_fragments), + content_size, + padding, + border, + margin, + None, /* clearance */ + collapsed_margin, + ) + .with_baselines(Baselines { + first: output.first_baselines.y.map(Au::from_f32_px), + last: None, + }), + ); + + child + .positioning_context + .adjust_static_position_of_hoisted_fragments( + &fragment, + PositioningContextLength::zero(), + ); + let child_positioning_context = std::mem::replace( + &mut child.positioning_context, + PositioningContext::new_for_containing_block_for_all_descendants(), + ); + container_ctx + .positioning_context + .append(child_positioning_context); + + fragment + }, + TaffyItemBoxInner::OutOfFlowAbsolutelyPositionedBox(abs_pos_box) => { + fn resolve_alignment(value: AlignFlags, auto: AlignFlags) -> AlignFlags { + match value { + AlignFlags::AUTO => auto, + AlignFlags::NORMAL => AlignFlags::STRETCH, + value => value, + } + } + + let hoisted_box = AbsolutelyPositionedBox::to_hoisted( + abs_pos_box.clone(), + PhysicalRect::from_size(PhysicalSize::new( + Au::from_f32_px(output.size.width), + Au::from_f32_px(output.size.height), + )), + LogicalVec2 { + inline: resolve_alignment( + child.style.clone_align_self().0 .0, + align_items.0, + ), + block: resolve_alignment( + child.style.clone_justify_self().0 .0, + justify_items.computed.0, + ), + }, + container_ctx.style.writing_mode, + ); + let hoisted_fragment = hoisted_box.fragment.clone(); + container_ctx.positioning_context.push(hoisted_box); + Fragment::AbsoluteOrFixedPositioned(hoisted_fragment) + }, + } + }) + .collect(); + + IndependentLayout { + fragments, + content_block_size: Au::from_f32_px(output.size.height) - pbm.padding_border_sums.block, + content_inline_size_for_table: Some( + Au::from_f32_px(output.size.width) - pbm.padding_border_sums.inline, + ), + baselines: Baselines::default(), + + // TODO: determine this accurately + // + // "true" is a safe default as it will prevent Servo from performing optimizations based + // on the assumption that the node's size does not depend on block constraints. + depends_on_block_constraints: true, + } + } +} diff --git a/components/layout_2020/taffy/mod.rs b/components/layout_2020/taffy/mod.rs new file mode 100644 index 00000000000..cdefacee97b --- /dev/null +++ b/components/layout_2020/taffy/mod.rs @@ -0,0 +1,122 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * 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/. */ +mod layout; +mod stylo_taffy; +use std::fmt; + +use serde::Serialize; +use servo_arc::Arc; +use style::properties::ComputedValues; +use style::values::computed::TextDecorationLine; +use stylo_taffy::TaffyStyloStyle; + +use crate::cell::ArcRefCell; +use crate::construct_modern::{ModernContainerBuilder, ModernItemKind}; +use crate::context::LayoutContext; +use crate::dom::{LayoutBox, NodeExt}; +use crate::dom_traversal::{NodeAndStyleInfo, NonReplacedContents}; +use crate::formatting_contexts::IndependentFormattingContext; +use crate::fragment_tree::Fragment; +use crate::positioned::{AbsolutelyPositionedBox, PositioningContext}; + +#[derive(Debug, Serialize)] +pub(crate) struct TaffyContainer { + children: Vec<ArcRefCell<TaffyItemBox>>, + #[serde(skip_serializing)] + style: Arc<ComputedValues>, +} + +impl TaffyContainer { + pub fn construct<'dom>( + context: &LayoutContext, + info: &NodeAndStyleInfo<impl NodeExt<'dom>>, + contents: NonReplacedContents, + propagated_text_decoration_line: TextDecorationLine, + ) -> Self { + let text_decoration_line = + propagated_text_decoration_line | info.style.clone_text_decoration_line(); + let mut builder = ModernContainerBuilder::new(context, info, text_decoration_line); + contents.traverse(context, info, &mut builder); + let items = builder.finish(); + + let children = items + .into_iter() + .map(|item| { + let box_ = match item.kind { + ModernItemKind::InFlow => ArcRefCell::new(TaffyItemBox::new( + TaffyItemBoxInner::InFlowBox(item.formatting_context), + )), + ModernItemKind::OutOfFlow => { + let abs_pos_box = + ArcRefCell::new(AbsolutelyPositionedBox::new(item.formatting_context)); + ArcRefCell::new(TaffyItemBox::new( + TaffyItemBoxInner::OutOfFlowAbsolutelyPositionedBox(abs_pos_box), + )) + }, + }; + + if let Some(box_slot) = item.box_slot { + box_slot.set(LayoutBox::TaffyItemBox(box_.clone())); + } + + box_ + }) + .collect(); + + Self { + children, + style: info.style.clone(), + } + } +} + +#[derive(Serialize)] +pub(crate) struct TaffyItemBox { + pub(crate) taffy_layout_cache: taffy::Cache, + pub(crate) taffy_layout: taffy::Layout, + pub(crate) child_fragments: Vec<Fragment>, + #[serde(skip_serializing)] + pub(crate) positioning_context: PositioningContext, + #[serde(skip_serializing)] + pub(crate) style: Arc<ComputedValues>, + pub(crate) taffy_level_box: TaffyItemBoxInner, +} + +#[derive(Debug, Serialize)] +pub(crate) enum TaffyItemBoxInner { + InFlowBox(IndependentFormattingContext), + OutOfFlowAbsolutelyPositionedBox(ArcRefCell<AbsolutelyPositionedBox>), +} + +impl fmt::Debug for TaffyItemBox { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("TaffyItemBox") + .field("taffy_layout_cache", &self.taffy_layout_cache) + .field("taffy_layout", &self.taffy_layout) + .field("child_fragments", &self.child_fragments.len()) + .field("style", &self.style) + .field("taffy_level_box", &self.taffy_level_box) + .finish() + } +} + +impl TaffyItemBox { + fn new(inner: TaffyItemBoxInner) -> Self { + let style: Arc<ComputedValues> = match &inner { + TaffyItemBoxInner::InFlowBox(item) => item.style().clone(), + TaffyItemBoxInner::OutOfFlowAbsolutelyPositionedBox(absbox) => { + (*absbox).borrow().context.style().clone() + }, + }; + + Self { + taffy_layout_cache: Default::default(), + taffy_layout: Default::default(), + child_fragments: Vec::new(), + positioning_context: PositioningContext::new_for_containing_block_for_all_descendants(), + style, + taffy_level_box: inner, + } + } +} diff --git a/components/layout_2020/taffy/stylo_taffy/convert.rs b/components/layout_2020/taffy/stylo_taffy/convert.rs new file mode 100644 index 00000000000..9e8d8307a04 --- /dev/null +++ b/components/layout_2020/taffy/stylo_taffy/convert.rs @@ -0,0 +1,338 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * 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/. */ + +/// Private module of type aliases so we can refer to stylo types with nicer names +mod stylo { + pub(crate) use style::properties::generated::longhands::box_sizing::computed_value::T as BoxSizing; + pub(crate) use style::properties::longhands::aspect_ratio::computed_value::T as AspectRatio; + pub(crate) use style::properties::longhands::position::computed_value::T as Position; + pub(crate) use style::values::computed::{LengthPercentage, Percentage}; + pub(crate) use style::values::generics::length::{ + GenericLengthPercentageOrNormal, GenericMargin, GenericMaxSize, GenericSize, + }; + pub(crate) use style::values::generics::position::{Inset as GenericInset, PreferredRatio}; + pub(crate) use style::values::generics::NonNegative; + pub(crate) use style::values::specified::align::{AlignFlags, ContentDistribution}; + pub(crate) use style::values::specified::box_::{ + Display, DisplayInside, DisplayOutside, Overflow, + }; + pub(crate) type MarginVal = GenericMargin<LengthPercentage>; + pub(crate) type InsetVal = GenericInset<Percentage, LengthPercentage>; + pub(crate) type Size = GenericSize<NonNegative<LengthPercentage>>; + pub(crate) type MaxSize = GenericMaxSize<NonNegative<LengthPercentage>>; + + pub(crate) type Gap = GenericLengthPercentageOrNormal<NonNegative<LengthPercentage>>; + + pub(crate) use style::computed_values::grid_auto_flow::T as GridAutoFlow; + pub(crate) use style::values::computed::{GridLine, GridTemplateComponent, ImplicitGridTracks}; + pub(crate) use style::values::generics::grid::{ + RepeatCount, TrackBreadth, TrackListValue, TrackSize, + }; + pub(crate) use style::values::specified::GenericGridTemplateComponent; +} + +#[inline] +pub fn length_percentage(val: &stylo::LengthPercentage) -> taffy::LengthPercentage { + if let Some(length) = val.to_length() { + taffy::LengthPercentage::Length(length.px()) + } else if let Some(val) = val.to_percentage() { + taffy::LengthPercentage::Percent(val.0) + } else { + // TODO: Support calc + taffy::LengthPercentage::Percent(0.0) + } +} + +#[inline] +pub fn dimension(val: &stylo::Size) -> taffy::Dimension { + match val { + stylo::Size::LengthPercentage(val) => length_percentage(&val.0).into(), + stylo::Size::Auto => taffy::Dimension::Auto, + + // TODO: implement other values in Taffy + stylo::Size::MaxContent => taffy::Dimension::Auto, + stylo::Size::MinContent => taffy::Dimension::Auto, + stylo::Size::FitContent => taffy::Dimension::Auto, + stylo::Size::Stretch => taffy::Dimension::Auto, + + // Anchor positioning will be flagged off for time being + stylo::Size::AnchorSizeFunction(_) => unreachable!(), + } +} + +#[inline] +pub fn max_size_dimension(val: &stylo::MaxSize) -> taffy::Dimension { + match val { + stylo::MaxSize::LengthPercentage(val) => length_percentage(&val.0).into(), + stylo::MaxSize::None => taffy::Dimension::Auto, + + // TODO: implement other values in Taffy + stylo::MaxSize::MaxContent => taffy::Dimension::Auto, + stylo::MaxSize::MinContent => taffy::Dimension::Auto, + stylo::MaxSize::FitContent => taffy::Dimension::Auto, + stylo::MaxSize::Stretch => taffy::Dimension::Auto, + + // Anchor positioning will be flagged off for time being + stylo::MaxSize::AnchorSizeFunction(_) => unreachable!(), + } +} + +#[inline] +pub fn margin(val: &stylo::MarginVal) -> taffy::LengthPercentageAuto { + match val { + stylo::MarginVal::Auto => taffy::LengthPercentageAuto::Auto, + stylo::MarginVal::LengthPercentage(val) => length_percentage(val).into(), + + // Anchor positioning will be flagged off for time being + stylo::MarginVal::AnchorSizeFunction(_) => unreachable!(), + } +} + +#[inline] +pub fn inset(val: &stylo::InsetVal) -> taffy::LengthPercentageAuto { + match val { + stylo::InsetVal::Auto => taffy::LengthPercentageAuto::Auto, + stylo::InsetVal::LengthPercentage(val) => length_percentage(val).into(), + + // Anchor positioning will be flagged off for time being + stylo::InsetVal::AnchorSizeFunction(_) => unreachable!(), + stylo::InsetVal::AnchorFunction(_) => unreachable!(), + } +} + +#[inline] +pub fn is_block(input: stylo::Display) -> bool { + matches!(input.outside(), stylo::DisplayOutside::Block) && + matches!( + input.inside(), + stylo::DisplayInside::Flow | stylo::DisplayInside::FlowRoot + ) +} + +#[inline] +pub fn box_generation_mode(input: stylo::Display) -> taffy::BoxGenerationMode { + match input.inside() { + stylo::DisplayInside::None => taffy::BoxGenerationMode::None, + _ => taffy::BoxGenerationMode::Normal, + } +} + +#[inline] +pub fn box_sizing(input: stylo::BoxSizing) -> taffy::BoxSizing { + match input { + stylo::BoxSizing::BorderBox => taffy::BoxSizing::BorderBox, + stylo::BoxSizing::ContentBox => taffy::BoxSizing::ContentBox, + } +} + +#[inline] +pub fn position(input: stylo::Position) -> taffy::Position { + match input { + // TODO: support position:static + stylo::Position::Relative => taffy::Position::Relative, + stylo::Position::Static => taffy::Position::Relative, + + // TODO: support position:fixed and sticky + stylo::Position::Absolute => taffy::Position::Absolute, + stylo::Position::Fixed => taffy::Position::Absolute, + stylo::Position::Sticky => taffy::Position::Absolute, + } +} + +#[inline] +pub fn overflow(input: stylo::Overflow) -> taffy::Overflow { + // TODO: Enable Overflow::Clip in servo configuration of stylo + match input { + stylo::Overflow::Visible => taffy::Overflow::Visible, + stylo::Overflow::Hidden => taffy::Overflow::Hidden, + stylo::Overflow::Scroll => taffy::Overflow::Scroll, + // TODO: Support Overflow::Auto in Taffy + stylo::Overflow::Auto => taffy::Overflow::Scroll, + } +} + +#[inline] +pub fn aspect_ratio(input: stylo::AspectRatio) -> Option<f32> { + match input.ratio { + stylo::PreferredRatio::None => None, + stylo::PreferredRatio::Ratio(val) => Some(val.0 .0 / val.1 .0), + } +} + +#[inline] +pub fn content_alignment(input: stylo::ContentDistribution) -> Option<taffy::AlignContent> { + match input.primary().value() { + stylo::AlignFlags::NORMAL => None, + stylo::AlignFlags::AUTO => None, + stylo::AlignFlags::START => Some(taffy::AlignContent::Start), + stylo::AlignFlags::END => Some(taffy::AlignContent::End), + stylo::AlignFlags::FLEX_START => Some(taffy::AlignContent::FlexStart), + stylo::AlignFlags::STRETCH => Some(taffy::AlignContent::Stretch), + stylo::AlignFlags::FLEX_END => Some(taffy::AlignContent::FlexEnd), + stylo::AlignFlags::CENTER => Some(taffy::AlignContent::Center), + stylo::AlignFlags::SPACE_BETWEEN => Some(taffy::AlignContent::SpaceBetween), + stylo::AlignFlags::SPACE_AROUND => Some(taffy::AlignContent::SpaceAround), + stylo::AlignFlags::SPACE_EVENLY => Some(taffy::AlignContent::SpaceEvenly), + // Should never be hit. But no real reason to panic here. + _ => None, + } +} + +#[inline] +pub fn item_alignment(input: stylo::AlignFlags) -> Option<taffy::AlignItems> { + match input.value() { + stylo::AlignFlags::NORMAL => None, + stylo::AlignFlags::AUTO => None, + stylo::AlignFlags::STRETCH => Some(taffy::AlignItems::Stretch), + stylo::AlignFlags::FLEX_START => Some(taffy::AlignItems::FlexStart), + stylo::AlignFlags::FLEX_END => Some(taffy::AlignItems::FlexEnd), + stylo::AlignFlags::START => Some(taffy::AlignItems::Start), + stylo::AlignFlags::END => Some(taffy::AlignItems::End), + stylo::AlignFlags::CENTER => Some(taffy::AlignItems::Center), + stylo::AlignFlags::BASELINE => Some(taffy::AlignItems::Baseline), + // Should never be hit. But no real reason to panic here. + _ => None, + } +} + +#[inline] +pub fn gap(input: &stylo::Gap) -> taffy::LengthPercentage { + match input { + // For Flexbox and CSS Grid the "normal" value is 0px. This may need to be updated + // if we ever implement multi-column layout. + stylo::Gap::Normal => taffy::LengthPercentage::Length(0.0), + stylo::Gap::LengthPercentage(val) => length_percentage(&val.0), + } +} + +// CSS Grid styles +// =============== + +#[inline] +pub fn grid_auto_flow(input: stylo::GridAutoFlow) -> taffy::GridAutoFlow { + let is_row = input.contains(stylo::GridAutoFlow::ROW); + let is_dense = input.contains(stylo::GridAutoFlow::DENSE); + + match (is_row, is_dense) { + (true, false) => taffy::GridAutoFlow::Row, + (true, true) => taffy::GridAutoFlow::RowDense, + (false, false) => taffy::GridAutoFlow::Column, + (false, true) => taffy::GridAutoFlow::ColumnDense, + } +} + +#[inline] +pub fn grid_line(input: &stylo::GridLine) -> taffy::GridPlacement { + if input.is_auto() { + taffy::GridPlacement::Auto + } else if input.is_span { + taffy::style_helpers::span(input.line_num.try_into().unwrap()) + } else if input.line_num == 0 { + taffy::GridPlacement::Auto + } else { + taffy::style_helpers::line(input.line_num.try_into().unwrap()) + } +} + +#[inline] +pub fn grid_template_tracks( + input: &stylo::GridTemplateComponent, +) -> Vec<taffy::TrackSizingFunction> { + match input { + stylo::GenericGridTemplateComponent::None => Vec::new(), + stylo::GenericGridTemplateComponent::TrackList(list) => list + .values + .iter() + .map(|track| match track { + stylo::TrackListValue::TrackSize(size) => { + taffy::TrackSizingFunction::Single(track_size(size)) + }, + stylo::TrackListValue::TrackRepeat(repeat) => taffy::TrackSizingFunction::Repeat( + track_repeat(repeat.count), + repeat.track_sizes.iter().map(track_size).collect(), + ), + }) + .collect(), + + // TODO: Implement subgrid and masonry + stylo::GenericGridTemplateComponent::Subgrid(_) => Vec::new(), + stylo::GenericGridTemplateComponent::Masonry => Vec::new(), + } +} + +#[inline] +pub fn grid_auto_tracks( + input: &stylo::ImplicitGridTracks, +) -> Vec<taffy::NonRepeatedTrackSizingFunction> { + input.0.iter().map(track_size).collect() +} + +#[inline] +pub fn track_repeat(input: stylo::RepeatCount<i32>) -> taffy::GridTrackRepetition { + match input { + stylo::RepeatCount::Number(val) => { + taffy::GridTrackRepetition::Count(val.try_into().unwrap()) + }, + stylo::RepeatCount::AutoFill => taffy::GridTrackRepetition::AutoFill, + stylo::RepeatCount::AutoFit => taffy::GridTrackRepetition::AutoFit, + } +} + +#[inline] +pub fn track_size( + input: &stylo::TrackSize<stylo::LengthPercentage>, +) -> taffy::NonRepeatedTrackSizingFunction { + match input { + stylo::TrackSize::Breadth(breadth) => taffy::MinMax { + min: min_track(breadth), + max: max_track(breadth), + }, + stylo::TrackSize::Minmax(min, max) => taffy::MinMax { + min: min_track(min), + max: max_track(max), + }, + stylo::TrackSize::FitContent(limit) => taffy::MinMax { + min: taffy::MinTrackSizingFunction::Auto, + max: taffy::MaxTrackSizingFunction::FitContent(match limit { + stylo::TrackBreadth::Breadth(lp) => length_percentage(lp), + + // Are these valid? Taffy doesn't support this in any case + stylo::TrackBreadth::Fr(_) => unreachable!(), + stylo::TrackBreadth::Auto => unreachable!(), + stylo::TrackBreadth::MinContent => unreachable!(), + stylo::TrackBreadth::MaxContent => unreachable!(), + }), + }, + } +} + +#[inline] +pub fn min_track( + input: &stylo::TrackBreadth<stylo::LengthPercentage>, +) -> taffy::MinTrackSizingFunction { + match input { + stylo::TrackBreadth::Breadth(lp) => { + taffy::MinTrackSizingFunction::Fixed(length_percentage(lp)) + }, + stylo::TrackBreadth::Fr(_) => taffy::MinTrackSizingFunction::Auto, + stylo::TrackBreadth::Auto => taffy::MinTrackSizingFunction::Auto, + stylo::TrackBreadth::MinContent => taffy::MinTrackSizingFunction::MinContent, + stylo::TrackBreadth::MaxContent => taffy::MinTrackSizingFunction::MaxContent, + } +} + +#[inline] +pub fn max_track( + input: &stylo::TrackBreadth<stylo::LengthPercentage>, +) -> taffy::MaxTrackSizingFunction { + match input { + stylo::TrackBreadth::Breadth(lp) => { + taffy::MaxTrackSizingFunction::Fixed(length_percentage(lp)) + }, + stylo::TrackBreadth::Fr(val) => taffy::MaxTrackSizingFunction::Fraction(*val), + stylo::TrackBreadth::Auto => taffy::MaxTrackSizingFunction::Auto, + stylo::TrackBreadth::MinContent => taffy::MaxTrackSizingFunction::MinContent, + stylo::TrackBreadth::MaxContent => taffy::MaxTrackSizingFunction::MaxContent, + } +} diff --git a/components/layout_2020/taffy/stylo_taffy/mod.rs b/components/layout_2020/taffy/stylo_taffy/mod.rs new file mode 100644 index 00000000000..05eece6ace7 --- /dev/null +++ b/components/layout_2020/taffy/stylo_taffy/mod.rs @@ -0,0 +1,9 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * 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/. */ + +//! Conversion functions from Stylo types to Taffy types + +mod convert; +mod wrapper; +pub use wrapper::TaffyStyloStyle; diff --git a/components/layout_2020/taffy/stylo_taffy/wrapper.rs b/components/layout_2020/taffy/stylo_taffy/wrapper.rs new file mode 100644 index 00000000000..d7f6c44526c --- /dev/null +++ b/components/layout_2020/taffy/stylo_taffy/wrapper.rs @@ -0,0 +1,226 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * 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 std::ops::Deref; + +use style::properties::ComputedValues; + +use super::convert; + +/// A wrapper struct for anything that Deref's to a [`stylo::ComputedValues`], which +/// implements Taffy's layout traits and can used with Taffy's layout algorithms. +pub struct TaffyStyloStyle<T: Deref<Target = ComputedValues>>(pub T); + +impl<T: Deref<Target = ComputedValues>> From<T> for TaffyStyloStyle<T> { + fn from(value: T) -> Self { + Self(value) + } +} + +impl<T: Deref<Target = ComputedValues>> taffy::CoreStyle for TaffyStyloStyle<T> { + #[inline] + fn box_generation_mode(&self) -> taffy::BoxGenerationMode { + convert::box_generation_mode(self.0.get_box().display) + } + + #[inline] + fn is_block(&self) -> bool { + convert::is_block(self.0.get_box().display) + } + + #[inline] + fn box_sizing(&self) -> taffy::BoxSizing { + convert::box_sizing(self.0.get_position().box_sizing) + } + + #[inline] + fn overflow(&self) -> taffy::Point<taffy::Overflow> { + let box_styles = self.0.get_box(); + taffy::Point { + x: convert::overflow(box_styles.overflow_x), + y: convert::overflow(box_styles.overflow_y), + } + } + + #[inline] + fn scrollbar_width(&self) -> f32 { + 0.0 + } + + #[inline] + fn position(&self) -> taffy::Position { + convert::position(self.0.get_box().position) + } + + #[inline] + fn inset(&self) -> taffy::Rect<taffy::LengthPercentageAuto> { + let position_styles = self.0.get_position(); + taffy::Rect { + left: convert::inset(&position_styles.left), + right: convert::inset(&position_styles.right), + top: convert::inset(&position_styles.top), + bottom: convert::inset(&position_styles.bottom), + } + } + + #[inline] + fn size(&self) -> taffy::Size<taffy::Dimension> { + let position_styles = self.0.get_position(); + taffy::Size { + width: convert::dimension(&position_styles.width), + height: convert::dimension(&position_styles.height), + } + } + + #[inline] + fn min_size(&self) -> taffy::Size<taffy::Dimension> { + let position_styles = self.0.get_position(); + taffy::Size { + width: convert::dimension(&position_styles.min_width), + height: convert::dimension(&position_styles.min_height), + } + } + + #[inline] + fn max_size(&self) -> taffy::Size<taffy::Dimension> { + let position_styles = self.0.get_position(); + taffy::Size { + width: convert::max_size_dimension(&position_styles.max_width), + height: convert::max_size_dimension(&position_styles.max_height), + } + } + + #[inline] + fn aspect_ratio(&self) -> Option<f32> { + convert::aspect_ratio(self.0.get_position().aspect_ratio) + } + + #[inline] + fn margin(&self) -> taffy::Rect<taffy::LengthPercentageAuto> { + let margin_styles = self.0.get_margin(); + taffy::Rect { + left: convert::margin(&margin_styles.margin_left), + right: convert::margin(&margin_styles.margin_right), + top: convert::margin(&margin_styles.margin_top), + bottom: convert::margin(&margin_styles.margin_bottom), + } + } + + #[inline] + fn padding(&self) -> taffy::Rect<taffy::LengthPercentage> { + let padding_styles = self.0.get_padding(); + taffy::Rect { + left: convert::length_percentage(&padding_styles.padding_left.0), + right: convert::length_percentage(&padding_styles.padding_right.0), + top: convert::length_percentage(&padding_styles.padding_top.0), + bottom: convert::length_percentage(&padding_styles.padding_bottom.0), + } + } + + #[inline] + fn border(&self) -> taffy::Rect<taffy::LengthPercentage> { + let border_styles = self.0.get_border(); + taffy::Rect { + left: taffy::LengthPercentage::Length(border_styles.border_left_width.to_f32_px()), + right: taffy::LengthPercentage::Length(border_styles.border_right_width.to_f32_px()), + top: taffy::LengthPercentage::Length(border_styles.border_top_width.to_f32_px()), + bottom: taffy::LengthPercentage::Length(border_styles.border_bottom_width.to_f32_px()), + } + } +} + +impl<T: Deref<Target = ComputedValues>> taffy::GridContainerStyle for TaffyStyloStyle<T> { + type TemplateTrackList<'a> + = Vec<taffy::TrackSizingFunction> + where + Self: 'a; + type AutoTrackList<'a> + = Vec<taffy::NonRepeatedTrackSizingFunction> + where + Self: 'a; + + #[inline] + fn grid_template_rows(&self) -> Self::TemplateTrackList<'_> { + convert::grid_template_tracks(&self.0.get_position().grid_template_rows) + } + + #[inline] + fn grid_template_columns(&self) -> Self::TemplateTrackList<'_> { + convert::grid_template_tracks(&self.0.get_position().grid_template_columns) + } + + #[inline] + fn grid_auto_rows(&self) -> Self::AutoTrackList<'_> { + convert::grid_auto_tracks(&self.0.get_position().grid_auto_rows) + } + + #[inline] + fn grid_auto_columns(&self) -> Self::AutoTrackList<'_> { + convert::grid_auto_tracks(&self.0.get_position().grid_auto_columns) + } + + #[inline] + fn grid_auto_flow(&self) -> taffy::GridAutoFlow { + convert::grid_auto_flow(self.0.get_position().grid_auto_flow) + } + + #[inline] + fn gap(&self) -> taffy::Size<taffy::LengthPercentage> { + let position_styles = self.0.get_position(); + taffy::Size { + width: convert::gap(&position_styles.column_gap), + height: convert::gap(&position_styles.row_gap), + } + } + + #[inline] + fn align_content(&self) -> Option<taffy::AlignContent> { + convert::content_alignment(self.0.get_position().align_content.0) + } + + #[inline] + fn justify_content(&self) -> Option<taffy::JustifyContent> { + convert::content_alignment(self.0.get_position().justify_content.0) + } + + #[inline] + fn align_items(&self) -> Option<taffy::AlignItems> { + convert::item_alignment(self.0.get_position().align_items.0) + } + + #[inline] + fn justify_items(&self) -> Option<taffy::AlignItems> { + convert::item_alignment(self.0.get_position().justify_items.computed.0) + } +} + +impl<T: Deref<Target = ComputedValues>> taffy::GridItemStyle for TaffyStyloStyle<T> { + #[inline] + fn grid_row(&self) -> taffy::Line<taffy::GridPlacement> { + let position_styles = self.0.get_position(); + taffy::Line { + start: convert::grid_line(&position_styles.grid_row_start), + end: convert::grid_line(&position_styles.grid_row_end), + } + } + + #[inline] + fn grid_column(&self) -> taffy::Line<taffy::GridPlacement> { + let position_styles = self.0.get_position(); + taffy::Line { + start: convert::grid_line(&position_styles.grid_column_start), + end: convert::grid_line(&position_styles.grid_column_end), + } + } + + #[inline] + fn align_self(&self) -> Option<taffy::AlignSelf> { + convert::item_alignment(self.0.get_position().align_self.0 .0) + } + + #[inline] + fn justify_self(&self) -> Option<taffy::AlignSelf> { + convert::item_alignment(self.0.get_position().justify_self.0 .0) + } +} |