layout: Combine layout_2020 and layout_thread_2020 into a crate called layout (#36613)

Now that legacy layout has been removed, the name `layout_2020` doesn't
make much sense any longer, also it's 2025 now for better or worse. The
split between the "layout thread" and "layout" also doesn't make as much
sense since layout doesn't run on it's own thread. There's a possibility
that it will in the future, but that should be something that the user
of the crate controls rather than layout iself.

This is part of the larger layout interface cleanup and optimization
that
@Looriool and I are doing.

Testing: Covered by existing tests as this is just code movement.

Signed-off-by: Martin Robinson <mrobinson@igalia.com>
This commit is contained in:
Martin Robinson 2025-04-19 12:17:03 +02:00 committed by GitHub
parent 3ab5b8c447
commit 7787cab521
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
62 changed files with 58 additions and 122 deletions

View file

@ -0,0 +1,663 @@
/* 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};
use style::Zero;
use style::properties::ComputedValues;
use style::values::specified::align::AlignFlags;
use style::values::specified::box_::DisplayInside;
use taffy::style_helpers::{TaffyMaxContent, TaffyMinContent};
use taffy::{AvailableSpace, MaybeMath, RequestedAxis, RunMode};
use super::{
SpecificTaffyGridInfo, TaffyContainer, TaffyItemBox, TaffyItemBoxInner, TaffyStyloStyle,
};
use crate::cell::ArcRefCell;
use crate::context::LayoutContext;
use crate::formatting_contexts::{
Baselines, IndependentFormattingContext, IndependentFormattingContextContents,
};
use crate::fragment_tree::{
BoxFragment, CollapsedBlockMargins, Fragment, FragmentFlags, SpecificLayoutInfo,
};
use crate::geom::{
LogicalVec2, PhysicalPoint, PhysicalRect, PhysicalSides, PhysicalSize, Size, SizeConstraint,
Sizes,
};
use crate::layout_box_base::CacheableLayoutResult;
use crate::positioned::{AbsolutelyPositionedBox, PositioningContext, PositioningContextLength};
use crate::sizing::{ComputeInlineContentSizes, ContentSizes, InlineContentSizesResult};
use crate::style_ext::{ComputedValuesExt, LayoutStyle};
use crate::{ConstraintSpace, ContainingBlock, ContainingBlockSize};
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(&IndependentFormattingContext) -> T,
) -> T {
match item {
TaffyItemBoxInner::InFlowBox(context) => cb(context),
TaffyItemBoxInner::OutOfFlowAbsolutelyPositionedBox(abspos_box) => {
cb(&AtomicRefCell::borrow(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,
specific_layout_info: Option<SpecificLayoutInfo>,
/// Temporary location for children specific info, which will be moved into child fragments
child_specific_layout_infos: Vec<Option<SpecificLayoutInfo>>,
}
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;
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 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_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 {
// TODO: re-evaluate sizing constraint conversions in light of recent layout changes
let containing_block = &self.content_box_size_override;
let style = independent_context.style();
// Adjust known_dimensions from border box to content box
let pbm = independent_context
.layout_style()
.padding_border_margin(containing_block);
let pb_sum = pbm.padding_border_sums.map(|v| v.to_f32_px());
let margin_sum = pbm.margin.auto_is(Au::zero).sum().map(|v| v.to_f32_px());
let content_box_inset = pb_sum + margin_sum;
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),
};
match &independent_context.contents {
IndependentFormattingContextContents::Replaced(replaced) => {
let content_box_size = replaced
.used_size_as_if_inline_element_from_content_box_sizes(
containing_block,
style,
independent_context
.preferred_aspect_ratio(&pbm.padding_border_sums),
LogicalVec2 {
block: &Sizes::new(
option_f32_to_size(content_box_known_dimensions.height),
Size::Initial,
Size::Initial,
),
inline: &Sizes::new(
option_f32_to_size(content_box_known_dimensions.width),
Size::Initial,
Size::Initial,
),
},
Size::FitContent.into(),
pbm.padding_border_sums + pbm.margin.auto_is(Au::zero).sum(),
)
.to_physical_size(self.style.writing_mode);
// Create fragments if the RunMode if PerformLayout
// If the RunMode is ComputeSize then only the returned size will be used
if inputs.run_mode == RunMode::PerformLayout {
child.child_fragments = replaced.make_fragments(
self.layout_context,
style,
content_box_size,
);
}
let computed_size = taffy::Size {
width: inputs.known_dimensions.width.unwrap_or_else(|| {
content_box_size.width.to_f32_px() + pb_sum.inline
}),
height: inputs.known_dimensions.height.unwrap_or_else(|| {
content_box_size.height.to_f32_px() + pb_sum.block
}),
};
let size = inputs.known_dimensions.unwrap_or(computed_size);
taffy::LayoutOutput {
size,
..taffy::LayoutOutput::DEFAULT
}
},
IndependentFormattingContextContents::NonReplaced(non_replaced) => {
// 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,
preferred_aspect_ratio: non_replaced.preferred_aspect_ratio(),
};
let result = independent_context
.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)
});
// Return early if only inline content sizes are requested
if inputs.run_mode == RunMode::ComputeSize &&
inputs.axis == RequestedAxis::Horizontal
{
return taffy::LayoutOutput::from_outer_size(taffy::Size {
width: inline_size + pb_sum.inline,
// If RequestedAxis is Horizontal then height will be ignored.
height: 0.0,
});
}
let content_box_size_override = ContainingBlock {
size: ContainingBlockSize {
inline: Au::from_f32_px(inline_size),
block: content_box_known_dimensions
.height
.map(Au::from_f32_px)
.map_or_else(SizeConstraint::default, SizeConstraint::Definite),
},
style,
};
let layout = {
let mut child_positioning_context =
PositioningContext::new_for_style(style).unwrap_or_else(|| {
PositioningContext::new_for_subtree(
self.positioning_context
.collects_for_nearest_positioned_ancestor(),
)
});
let layout = non_replaced.layout_without_caching(
self.layout_context,
&mut child_positioning_context,
&content_box_size_override,
containing_block,
false, /* depends_on_block_constraints */
);
// Store layout data on child for later access
child.positioning_context = child_positioning_context;
layout
};
child.child_fragments = layout.fragments;
self.child_specific_layout_infos[usize::from(node_id)] =
layout.specific_layout_info;
let block_size = layout.content_block_size.to_f32_px();
let computed_size = taffy::Size {
width: inline_size + pb_sum.inline,
height: block_size + pb_sum.block,
};
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))
}
fn set_detailed_grid_info(
&mut self,
_node_id: taffy::NodeId,
specific_layout_info: taffy::DetailedGridInfo,
) {
self.specific_layout_info = Some(SpecificLayoutInfo::Grid(Box::new(
SpecificTaffyGridInfo::from_detailed_grid_layout(specific_layout_info),
)));
}
}
impl ComputeInlineContentSizes for TaffyContainer {
fn compute_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 {
size: ContainingBlockSize {
inline: Au::zero(),
block: SizeConstraint::default(),
},
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,
specific_layout_info: None,
child_specific_layout_infos: vec![None; self.children.len()],
};
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 = self
.layout_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,
}
}
}
impl TaffyContainer {
/// <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,
) -> CacheableLayoutResult {
let mut container_ctx = TaffyContainerContext {
layout_context,
positioning_context,
content_box_size_override,
style: content_box_size_override.style,
source_child_nodes: &self.children,
specific_layout_info: None,
child_specific_layout_infos: vec![None; self.children.len()],
};
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 = self.layout_style().padding_border_margin(containing_block);
let known_dimensions = taffy::Size {
width: Some(
(content_box_size_override.size.inline + pbm.padding_border_sums.inline)
.to_f32_px(),
),
height: content_box_size_override
.size
.block
.to_definite()
.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.size.inline.to_f32_px()),
height: containing_block.size.block.to_definite().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
// with container_ctx.child_specific_layout_infos will also moved to the corresponding `Fragment`s
let fragments: Vec<Fragment> = self
.children
.iter()
.map(|child| (**child).borrow_mut())
.enumerate()
.map(|(child_id, mut child)| {
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));
// 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),
);
let child_specific_layout_info: Option<SpecificLayoutInfo> =
std::mem::take(&mut container_ctx.child_specific_layout_infos[child_id]);
let establishes_containing_block_for_absolute_descendants =
if let TaffyItemBoxInner::InFlowBox(independent_box) = &child.taffy_level_box {
child
.style
.establishes_containing_block_for_absolute_descendants(
independent_box.base_fragment_info().flags,
)
} else {
false
};
let fragment = match &mut child.taffy_level_box {
TaffyItemBoxInner::InFlowBox(independent_box) => {
let mut fragment_info = independent_box.base_fragment_info();
fragment_info
.flags
.insert(FragmentFlags::IS_FLEX_OR_GRID_ITEM);
let mut box_fragment = BoxFragment::new(
fragment_info,
independent_box.style().clone(),
std::mem::take(&mut child.child_fragments),
content_size,
padding,
border,
margin,
None, /* clearance */
)
.with_baselines(Baselines {
first: output.first_baselines.y.map(Au::from_f32_px),
last: None,
})
.with_specific_layout_info(child_specific_layout_info);
if establishes_containing_block_for_absolute_descendants {
child.positioning_context.layout_collected_children(
container_ctx.layout_context,
&mut box_fragment,
);
}
let fragment = Fragment::Box(ArcRefCell::new(box_fragment));
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)
},
};
if let TaffyItemBoxInner::InFlowBox(independent_formatting_context) =
&child.taffy_level_box
{
independent_formatting_context
.base
.set_fragment(fragment.clone());
}
fragment
})
.collect();
CacheableLayoutResult {
fragments,
content_block_size: Au::from_f32_px(output.size.height) - pbm.padding_border_sums.block,
content_inline_size_for_table: None,
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,
specific_layout_info: container_ctx.specific_layout_info,
collapsible_margins_in_children: CollapsedBlockMargins::zero(),
}
}
#[inline]
pub(crate) fn layout_style(&self) -> LayoutStyle {
LayoutStyle::Default(&self.style)
}
}

View file

@ -0,0 +1,184 @@
/* 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 app_units::Au;
use malloc_size_of_derive::MallocSizeOf;
use servo_arc::Arc;
use style::properties::ComputedValues;
use stylo_taffy::TaffyStyloStyle;
use crate::PropagatedBoxTreeData;
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, MallocSizeOf)]
pub(crate) struct TaffyContainer {
children: Vec<ArcRefCell<TaffyItemBox>>,
#[conditional_malloc_size_of]
style: Arc<ComputedValues>,
}
impl TaffyContainer {
pub fn construct<'dom>(
context: &LayoutContext,
info: &NodeAndStyleInfo<impl NodeExt<'dom>>,
contents: NonReplacedContents,
propagated_data: PropagatedBoxTreeData,
) -> Self {
let mut builder =
ModernContainerBuilder::new(context, info, propagated_data.union(&info.style));
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(MallocSizeOf)]
pub(crate) struct TaffyItemBox {
pub(crate) taffy_layout: taffy::Layout,
pub(crate) child_fragments: Vec<Fragment>,
pub(crate) positioning_context: PositioningContext,
#[conditional_malloc_size_of]
pub(crate) style: Arc<ComputedValues>,
pub(crate) taffy_level_box: TaffyItemBoxInner,
}
#[derive(Debug, MallocSizeOf)]
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", &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: Default::default(),
child_fragments: Vec::new(),
positioning_context: PositioningContext::new_for_containing_block_for_all_descendants(),
style,
taffy_level_box: inner,
}
}
pub(crate) fn invalidate_cached_fragment(&mut self) {
self.taffy_layout = Default::default();
self.positioning_context =
PositioningContext::new_for_containing_block_for_all_descendants();
match self.taffy_level_box {
TaffyItemBoxInner::InFlowBox(ref independent_formatting_context) => {
independent_formatting_context
.base
.invalidate_cached_fragment()
},
TaffyItemBoxInner::OutOfFlowAbsolutelyPositionedBox(ref positioned_box) => {
positioned_box
.borrow()
.context
.base
.invalidate_cached_fragment()
},
}
}
pub(crate) fn fragments(&self) -> Vec<Fragment> {
match self.taffy_level_box {
TaffyItemBoxInner::InFlowBox(ref independent_formatting_context) => {
independent_formatting_context.base.fragments()
},
TaffyItemBoxInner::OutOfFlowAbsolutelyPositionedBox(ref positioned_box) => {
positioned_box.borrow().context.base.fragments()
},
}
}
}
/// Details from Taffy grid layout that will be stored
#[derive(Clone, Debug, MallocSizeOf)]
pub(crate) struct SpecificTaffyGridInfo {
pub rows: SpecificTaffyGridTrackInfo,
pub columns: SpecificTaffyGridTrackInfo,
}
impl SpecificTaffyGridInfo {
fn from_detailed_grid_layout(grid_info: taffy::DetailedGridInfo) -> Self {
Self {
rows: SpecificTaffyGridTrackInfo {
sizes: grid_info
.rows
.sizes
.iter()
.map(|size| Au::from_f32_px(*size))
.collect(),
},
columns: SpecificTaffyGridTrackInfo {
sizes: grid_info
.columns
.sizes
.iter()
.map(|size| Au::from_f32_px(*size))
.collect(),
},
}
}
}
#[derive(Clone, Debug, MallocSizeOf)]
pub(crate) struct SpecificTaffyGridTrackInfo {
pub sizes: Box<[Au]>,
}

View file

@ -0,0 +1,347 @@
/* 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::NonNegative;
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::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::FitContentFunction(_) => 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::FitContentFunction(_) => 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::Relative,
}
}
#[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,
stylo::Overflow::Clip => taffy::Overflow::Clip,
// 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::LEFT => Some(taffy::AlignContent::Start),
stylo::AlignFlags::RIGHT => 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::AUTO => None,
stylo::AlignFlags::NORMAL => Some(taffy::AlignItems::Stretch),
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::SELF_START => Some(taffy::AlignItems::Start),
stylo::AlignFlags::SELF_END => Some(taffy::AlignItems::End),
stylo::AlignFlags::START => Some(taffy::AlignItems::Start),
stylo::AlignFlags::END => Some(taffy::AlignItems::End),
stylo::AlignFlags::LEFT => Some(taffy::AlignItems::Start),
stylo::AlignFlags::RIGHT => 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,
}
}

View file

@ -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;

View file

@ -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 [`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)
}
}