use std::mem;
use app_units::Au;
use malloc_size_of_derive::MallocSizeOf;
use rayon::iter::IntoParallelRefMutIterator;
use rayon::prelude::{IndexedParallelIterator, ParallelIterator};
use style::Zero;
use style::computed_values::position::T as Position;
use style::logical_geometry::{Direction, WritingMode};
use style::properties::ComputedValues;
use style::values::specified::align::AlignFlags;
use crate::cell::ArcRefCell;
use crate::context::LayoutContext;
use crate::dom_traversal::{Contents, NodeAndStyleInfo};
use crate::formatting_contexts::IndependentFormattingContext;
use crate::fragment_tree::{BoxFragment, Fragment, FragmentFlags, HoistedSharedFragment};
use crate::geom::{
AuOrAuto, LogicalRect, LogicalSides, LogicalSides1D, LogicalVec2, PhysicalPoint, PhysicalRect,
PhysicalSides, PhysicalSize, PhysicalVec, ToLogical, ToLogicalWithContainingBlock,
};
use crate::layout_box_base::{CacheableLayoutResult, LayoutBoxBase};
use crate::sizing::{LazySize, Size, SizeConstraint, Sizes};
use crate::style_ext::{Clamp, ComputedValuesExt, ContentBoxSizesAndPBM, DisplayInside};
use crate::{
ConstraintSpace, ContainingBlock, ContainingBlockSize, DefiniteContainingBlock,
PropagatedBoxTreeData,
};
#[derive(Debug, MallocSizeOf)]
pub(crate) struct AbsolutelyPositionedBox {
pub context: IndependentFormattingContext,
}
#[derive(Clone, MallocSizeOf)]
pub(crate) struct HoistedAbsolutelyPositionedBox {
absolutely_positioned_box: ArcRefCell<AbsolutelyPositionedBox>,
pub fragment: ArcRefCell<HoistedSharedFragment>,
pub adjusted_static_position_rect: Option<PhysicalRect<Au>>,
pub resolved_alignment: LogicalVec2<AlignFlags>,
pub original_parent_writing_mode: WritingMode,
}
impl AbsolutelyPositionedBox {
pub fn new(context: IndependentFormattingContext) -> Self {
Self { context }
}
pub fn construct(
context: &LayoutContext,
node_info: &NodeAndStyleInfo,
display_inside: DisplayInside,
contents: Contents,
) -> Self {
Self {
context: IndependentFormattingContext::construct(
context,
node_info,
display_inside,
contents,
PropagatedBoxTreeData::default(),
),
}
}
pub(crate) fn to_hoisted(
absolutely_positioned_box: ArcRefCell<Self>,
static_position_rect: PhysicalRect<Au>,
resolved_alignment: LogicalVec2<AlignFlags>,
original_parent_writing_mode: WritingMode,
) -> HoistedAbsolutelyPositionedBox {
HoistedAbsolutelyPositionedBox {
fragment: ArcRefCell::new(HoistedSharedFragment::new(static_position_rect)),
adjusted_static_position_rect: None,
resolved_alignment,
original_parent_writing_mode,
absolutely_positioned_box,
}
}
}
#[derive(Clone, Default, MallocSizeOf)]
pub(crate) struct PositioningContext {
absolutes: Vec<HoistedAbsolutelyPositionedBox>,
}
impl PositioningContext {
#[inline]
pub(crate) fn new_for_layout_box_base(layout_box_base: &LayoutBoxBase) -> Option<Self> {
Self::new_for_style_and_fragment_flags(
&layout_box_base.style,
&layout_box_base.base_fragment_info.flags,
)
}
fn new_for_style_and_fragment_flags(
style: &ComputedValues,
flags: &FragmentFlags,
) -> Option<Self> {
if style.establishes_containing_block_for_absolute_descendants(*flags) {
Some(Self::default())
} else {
None
}
}
pub(crate) fn adjust_static_position_of_hoisted_fragments(
&mut self,
parent_fragment: &Fragment,
index: PositioningContextLength,
) {
let Some(base) = parent_fragment.base() else {
return;
};
self.adjust_static_position_of_hoisted_fragments_with_offset(
&base.rect.origin.to_vector(),
index,
);
}
pub(crate) fn adjust_static_position_of_hoisted_fragments_with_offset(
&mut self,
offset: &PhysicalVec<Au>,
index: PositioningContextLength,
) {
self.absolutes
.iter_mut()
.skip(index.0)
.for_each(|hoisted_box| {
hoisted_box.adjust_static_position_with_offset(offset);
})
}
pub(crate) fn layout_maybe_position_relative_fragment(
&mut self,
layout_context: &LayoutContext,
containing_block: &ContainingBlock,
base: &LayoutBoxBase,
fragment_layout_fn: impl FnOnce(&mut Self) -> BoxFragment,
) -> BoxFragment {
let establishes_containing_block_for_absolutes = base
.style
.establishes_containing_block_for_absolute_descendants(base.base_fragment_info.flags);
if !establishes_containing_block_for_absolutes {
return fragment_layout_fn(self);
}
let mut new_context = PositioningContext::default();
let mut new_fragment = fragment_layout_fn(&mut new_context);
new_context.layout_collected_children(layout_context, &mut new_fragment);
self.append(new_context);
if base.style.clone_position() == Position::Relative {
new_fragment.base.rect.origin += relative_adjustement(&base.style, containing_block)
.to_physical_vector(containing_block.style.writing_mode)
}
new_fragment
}
fn take_boxes_for_fragment(
&mut self,
new_fragment: &BoxFragment,
boxes_to_layout_out: &mut Vec<HoistedAbsolutelyPositionedBox>,
boxes_to_continue_hoisting_out: &mut Vec<HoistedAbsolutelyPositionedBox>,
) {
let style = new_fragment.style();
debug_assert!(
style.establishes_containing_block_for_absolute_descendants(new_fragment.base.flags)
);
if style.establishes_containing_block_for_all_descendants(new_fragment.base.flags) {
boxes_to_layout_out.append(&mut self.absolutes);
return;
}
let (mut boxes_to_layout, mut boxes_to_continue_hoisting) = self
.absolutes
.drain(..)
.partition(|hoisted_box| hoisted_box.position() != Position::Fixed);
boxes_to_layout_out.append(&mut boxes_to_layout);
boxes_to_continue_hoisting_out.append(&mut boxes_to_continue_hoisting);
}
pub(crate) fn layout_collected_children(
&mut self,
layout_context: &LayoutContext,
new_fragment: &mut BoxFragment,
) {
if self.absolutes.is_empty() {
return;
}
let style = new_fragment.style().clone();
if !style.establishes_containing_block_for_absolute_descendants(new_fragment.base.flags) {
return;
}
let padding_rect = PhysicalRect::new(
PhysicalPoint::origin(),
new_fragment.base.rect.size,
)
.outer_rect(new_fragment.padding);
let containing_block = DefiniteContainingBlock {
size: padding_rect.size.to_logical(style.writing_mode),
style: &style,
};
let mut fixed_position_boxes_to_hoist = Vec::new();
let mut boxes_to_layout = Vec::new();
self.take_boxes_for_fragment(
new_fragment,
&mut boxes_to_layout,
&mut fixed_position_boxes_to_hoist,
);
while !boxes_to_layout.is_empty() {
HoistedAbsolutelyPositionedBox::layout_many(
layout_context,
std::mem::take(&mut boxes_to_layout),
&mut new_fragment.children,
&mut self.absolutes,
&containing_block,
new_fragment.padding,
);
self.take_boxes_for_fragment(
new_fragment,
&mut boxes_to_layout,
&mut fixed_position_boxes_to_hoist,
);
}
self.absolutes = fixed_position_boxes_to_hoist;
}
pub(crate) fn push(&mut self, hoisted_box: HoistedAbsolutelyPositionedBox) {
debug_assert!(hoisted_box.position().is_absolutely_positioned());
self.absolutes.push(hoisted_box);
}
pub(crate) fn append(&mut self, mut other: Self) {
if other.absolutes.is_empty() {
return;
}
if self.absolutes.is_empty() {
self.absolutes = other.absolutes;
} else {
self.absolutes.append(&mut other.absolutes)
}
}
pub(crate) fn layout_initial_containing_block_children(
&mut self,
layout_context: &LayoutContext,
initial_containing_block: &DefiniteContainingBlock,
fragments: &mut Vec<Fragment>,
) {
while !self.absolutes.is_empty() {
HoistedAbsolutelyPositionedBox::layout_many(
layout_context,
mem::take(&mut self.absolutes),
fragments,
&mut self.absolutes,
initial_containing_block,
Default::default(),
)
}
}
pub(crate) fn len(&self) -> PositioningContextLength {
PositioningContextLength(self.absolutes.len())
}
pub(crate) fn truncate(&mut self, length: &PositioningContextLength) {
self.absolutes.truncate(length.0)
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub(crate) struct PositioningContextLength(usize);
impl Zero for PositioningContextLength {
fn zero() -> Self {
Self(0)
}
fn is_zero(&self) -> bool {
self.0.is_zero()
}
}
impl HoistedAbsolutelyPositionedBox {
fn position(&self) -> Position {
let position = self
.absolutely_positioned_box
.borrow()
.context
.style()
.clone_position();
assert!(position.is_absolutely_positioned());
position
}
pub(crate) fn layout_many(
layout_context: &LayoutContext,
mut boxes: Vec<Self>,
fragments: &mut Vec<Fragment>,
for_nearest_containing_block_for_all_descendants: &mut Vec<HoistedAbsolutelyPositionedBox>,
containing_block: &DefiniteContainingBlock,
containing_block_padding: PhysicalSides<Au>,
) {
if layout_context.use_rayon {
let mut new_fragments = Vec::new();
let mut new_hoisted_boxes = Vec::new();
boxes
.par_iter_mut()
.map(|hoisted_box| {
let mut new_hoisted_boxes: Vec<HoistedAbsolutelyPositionedBox> = Vec::new();
let new_fragment = hoisted_box.layout(
layout_context,
&mut new_hoisted_boxes,
containing_block,
containing_block_padding,
);
hoisted_box.fragment.borrow_mut().fragment = Some(new_fragment.clone());
(new_fragment, new_hoisted_boxes)
})
.unzip_into_vecs(&mut new_fragments, &mut new_hoisted_boxes);
fragments.extend(new_fragments);
for_nearest_containing_block_for_all_descendants
.extend(new_hoisted_boxes.into_iter().flatten());
} else {
fragments.extend(boxes.iter_mut().map(|box_| {
let new_fragment = box_.layout(
layout_context,
for_nearest_containing_block_for_all_descendants,
containing_block,
containing_block_padding,
);
box_.fragment.borrow_mut().fragment = Some(new_fragment.clone());
new_fragment
}))
}
}
pub(crate) fn layout(
&mut self,
layout_context: &LayoutContext,
hoisted_absolutes_from_children: &mut Vec<HoistedAbsolutelyPositionedBox>,
containing_block: &DefiniteContainingBlock,
containing_block_padding: PhysicalSides<Au>,
) -> Fragment {
let cbis = containing_block.size.inline;
let cbbs = containing_block.size.block;
let containing_block_writing_mode = containing_block.style.writing_mode;
let absolutely_positioned_box = self.absolutely_positioned_box.borrow();
let context = &absolutely_positioned_box.context;
let style = context.style().clone();
let layout_style = context.layout_style();
let ContentBoxSizesAndPBM {
content_box_sizes,
pbm,
..
} = layout_style.content_box_sizes_and_padding_border_margin(&containing_block.into());
let containing_block = &containing_block.into();
let is_table = layout_style.is_table();
let is_table_or_replaced = is_table || context.is_replaced();
let preferred_aspect_ratio = context.preferred_aspect_ratio(&pbm.padding_border_sums);
let mut static_position_rect = self
.static_position_rect()
.outer_rect(-containing_block_padding);
static_position_rect.size = static_position_rect.size.max(PhysicalSize::zero());
let static_position_rect = static_position_rect.to_logical(containing_block);
let box_offset = style.box_offsets(containing_block.style.writing_mode);
let inline_box_offsets = box_offset.inline_sides().percentages_relative_to(cbis);
let inline_alignment = match inline_box_offsets.either_specified() {
true => style.clone_justify_self().0,
false => self.resolved_alignment.inline,
};
let inline_axis_solver = AbsoluteAxisSolver {
axis: Direction::Inline,
containing_size: cbis,
padding_border_sum: pbm.padding_border_sums.inline,
computed_margin_start: pbm.margin.inline_start,
computed_margin_end: pbm.margin.inline_end,
computed_sizes: content_box_sizes.inline,
avoid_negative_margin_start: true,
box_offsets: inline_box_offsets,
static_position_rect_axis: static_position_rect.get_axis(Direction::Inline),
alignment: inline_alignment,
flip_anchor: self.original_parent_writing_mode.is_bidi_ltr() !=
containing_block_writing_mode.is_bidi_ltr(),
is_table_or_replaced,
};
let block_box_offsets = box_offset.block_sides().percentages_relative_to(cbbs);
let block_alignment = match block_box_offsets.either_specified() {
true => style.clone_align_self().0,
false => self.resolved_alignment.block,
};
let block_axis_solver = AbsoluteAxisSolver {
axis: Direction::Block,
containing_size: cbbs,
padding_border_sum: pbm.padding_border_sums.block,
computed_margin_start: pbm.margin.block_start,
computed_margin_end: pbm.margin.block_end,
computed_sizes: content_box_sizes.block,
avoid_negative_margin_start: false,
box_offsets: block_box_offsets,
static_position_rect_axis: static_position_rect.get_axis(Direction::Block),
alignment: block_alignment,
flip_anchor: false,
is_table_or_replaced,
};
let block_automatic_size = block_axis_solver.automatic_size();
let block_stretch_size = Some(block_axis_solver.stretch_size());
let inline_stretch_size = inline_axis_solver.stretch_size();
let tentative_block_content_size =
context.tentative_block_content_size(preferred_aspect_ratio, inline_stretch_size);
let tentative_block_size = if let Some(block_content_size) = tentative_block_content_size {
SizeConstraint::Definite(block_axis_solver.computed_sizes.resolve(
Direction::Block,
block_automatic_size,
Au::zero,
block_stretch_size,
|| block_content_size,
is_table,
))
} else {
block_axis_solver.computed_sizes.resolve_extrinsic(
block_automatic_size,
Au::zero(),
block_stretch_size,
)
};
let get_inline_content_size = || {
let constraint_space =
ConstraintSpace::new(tentative_block_size, &style, preferred_aspect_ratio);
context
.inline_content_sizes(layout_context, &constraint_space)
.sizes
};
let inline_size = inline_axis_solver.computed_sizes.resolve(
Direction::Inline,
inline_axis_solver.automatic_size(),
Au::zero,
Some(inline_stretch_size),
get_inline_content_size,
is_table,
);
let containing_block_for_children = ContainingBlock {
size: ContainingBlockSize {
inline: inline_size,
block: tentative_block_size,
},
style: &style,
};
assert_eq!(
containing_block_writing_mode.is_horizontal(),
style.writing_mode.is_horizontal(),
"Mixed horizontal and vertical writing modes are not supported yet"
);
let mut positioning_context = PositioningContext::default();
let lazy_block_size = LazySize::new(
&block_axis_solver.computed_sizes,
Direction::Block,
block_automatic_size,
Au::zero,
block_stretch_size,
is_table,
);
let CacheableLayoutResult {
content_inline_size_for_table,
content_block_size,
fragments,
specific_layout_info,
..
} = context.layout(
layout_context,
&mut positioning_context,
&containing_block_for_children,
containing_block,
preferred_aspect_ratio,
&lazy_block_size,
);
let content_size = LogicalVec2 {
inline: content_inline_size_for_table.unwrap_or(inline_size),
block: lazy_block_size.resolve(|| content_block_size),
};
let inline_margins = inline_axis_solver.solve_margins(content_size.inline);
let block_margins = block_axis_solver.solve_margins(content_size.block);
let margin = LogicalSides {
inline_start: inline_margins.start,
inline_end: inline_margins.end,
block_start: block_margins.start,
block_end: block_margins.end,
};
let pb = pbm.padding + pbm.border;
let margin_rect_size = content_size + pbm.padding_border_sums + margin.sum();
let inline_origin = inline_axis_solver.origin_for_margin_box(
margin_rect_size.inline,
style.writing_mode,
self.original_parent_writing_mode,
containing_block_writing_mode,
);
let block_origin = block_axis_solver.origin_for_margin_box(
margin_rect_size.block,
style.writing_mode,
self.original_parent_writing_mode,
containing_block_writing_mode,
);
let content_rect = LogicalRect {
start_corner: LogicalVec2 {
inline: inline_origin + margin.inline_start + pb.inline_start,
block: block_origin + margin.block_start + pb.block_start,
},
size: content_size,
};
let mut new_fragment = BoxFragment::new(
context.base_fragment_info(),
style,
fragments,
content_rect.as_physical(Some(containing_block)),
pbm.padding.to_physical(containing_block_writing_mode),
pbm.border.to_physical(containing_block_writing_mode),
margin.to_physical(containing_block_writing_mode),
specific_layout_info,
);
positioning_context.layout_collected_children(layout_context, &mut new_fragment);
positioning_context.adjust_static_position_of_hoisted_fragments_with_offset(
&new_fragment.base.rect.origin.to_vector(),
PositioningContextLength::zero(),
);
hoisted_absolutes_from_children.extend(positioning_context.absolutes);
let fragment = Fragment::Box(ArcRefCell::new(new_fragment));
context.base.set_fragment(fragment.clone());
fragment
}
fn static_position_rect(&self) -> PhysicalRect<Au> {
self.adjusted_static_position_rect
.unwrap_or_else(|| self.fragment.borrow().original_static_position_rect)
}
fn adjust_static_position_with_offset(&mut self, offset: &PhysicalVec<Au>) {
self.adjusted_static_position_rect = Some(self.static_position_rect().translate(*offset));
}
}
#[derive(Clone, Copy, Debug)]
struct RectAxis {
origin: Au,
length: Au,
}
impl LogicalRect<Au> {
fn get_axis(&self, axis: Direction) -> RectAxis {
match axis {
Direction::Block => RectAxis {
origin: self.start_corner.block,
length: self.size.block,
},
Direction::Inline => RectAxis {
origin: self.start_corner.inline,
length: self.size.inline,
},
}
}
}
struct AbsoluteAxisSolver {
axis: Direction,
containing_size: Au,
padding_border_sum: Au,
computed_margin_start: AuOrAuto,
computed_margin_end: AuOrAuto,
computed_sizes: Sizes,
avoid_negative_margin_start: bool,
box_offsets: LogicalSides1D<AuOrAuto>,
static_position_rect_axis: RectAxis,
alignment: AlignFlags,
flip_anchor: bool,
is_table_or_replaced: bool,
}
impl AbsoluteAxisSolver {
fn inset_sum(&self) -> Au {
match (
self.box_offsets.start.non_auto(),
self.box_offsets.end.non_auto(),
) {
(None, None) => {
if self.flip_anchor {
self.containing_size -
self.static_position_rect_axis.origin -
self.static_position_rect_axis.length
} else {
self.static_position_rect_axis.origin
}
},
(Some(start), None) => start,
(None, Some(end)) => end,
(Some(start), Some(end)) => start + end,
}
}
#[inline]
fn available_space(&self) -> Au {
Au::zero().max(self.containing_size - self.inset_sum())
}
#[inline]
fn automatic_size(&self) -> Size<Au> {
match self.alignment.value() {
_ if self.box_offsets.either_auto() => Size::FitContent,
AlignFlags::NORMAL | AlignFlags::AUTO if !self.is_table_or_replaced => Size::Stretch,
AlignFlags::STRETCH => Size::Stretch,
_ => Size::FitContent,
}
}
#[inline]
fn stretch_size(&self) -> Au {
Au::zero().max(
self.available_space() -
self.padding_border_sum -
self.computed_margin_start.auto_is(Au::zero) -
self.computed_margin_end.auto_is(Au::zero),
)
}
fn solve_margins(&self, size: Au) -> LogicalSides1D<Au> {
if self.box_offsets.either_auto() {
LogicalSides1D::new(
self.computed_margin_start.auto_is(Au::zero),
self.computed_margin_end.auto_is(Au::zero),
)
} else {
let free_space = self.available_space() - self.padding_border_sum - size;
match (self.computed_margin_start, self.computed_margin_end) {
(AuOrAuto::Auto, AuOrAuto::Auto) => {
if self.avoid_negative_margin_start && free_space < Au::zero() {
LogicalSides1D::new(Au::zero(), free_space)
} else {
let margin_start = free_space / 2;
LogicalSides1D::new(margin_start, free_space - margin_start)
}
},
(AuOrAuto::Auto, AuOrAuto::LengthPercentage(end)) => {
LogicalSides1D::new(free_space - end, end)
},
(AuOrAuto::LengthPercentage(start), AuOrAuto::Auto) => {
LogicalSides1D::new(start, free_space - start)
},
(AuOrAuto::LengthPercentage(start), AuOrAuto::LengthPercentage(end)) => {
LogicalSides1D::new(start, end)
},
}
}
}
fn origin_for_margin_box(
&self,
size: Au,
self_writing_mode: WritingMode,
original_parent_writing_mode: WritingMode,
containing_block_writing_mode: WritingMode,
) -> Au {
let (alignment_container, alignment_container_writing_mode, flip_anchor, offsets) = match (
self.box_offsets.start.non_auto(),
self.box_offsets.end.non_auto(),
) {
(None, None) => (
self.static_position_rect_axis,
original_parent_writing_mode,
self.flip_anchor,
None,
),
(Some(start), Some(end)) => {
let alignment_container = RectAxis {
origin: start,
length: self.available_space(),
};
(
alignment_container,
containing_block_writing_mode,
false,
Some(LogicalSides1D { start, end }),
)
},
(Some(start), None) => return start,
(None, Some(end)) => {
return self.containing_size - size - end;
},
};
assert_eq!(
self_writing_mode.is_horizontal(),
original_parent_writing_mode.is_horizontal(),
"Mixed horizontal and vertical writing modes are not supported yet"
);
assert_eq!(
self_writing_mode.is_horizontal(),
containing_block_writing_mode.is_horizontal(),
"Mixed horizontal and vertical writing modes are not supported yet"
);
let self_value_matches_container = || {
self.axis == Direction::Block ||
self_writing_mode.is_bidi_ltr() == alignment_container_writing_mode.is_bidi_ltr()
};
let alignment = match self.alignment.value() {
AlignFlags::CENTER | AlignFlags::SPACE_AROUND | AlignFlags::SPACE_EVENLY => {
AlignFlags::CENTER
},
AlignFlags::SELF_START if self_value_matches_container() => AlignFlags::START,
AlignFlags::SELF_START => AlignFlags::END,
AlignFlags::SELF_END if self_value_matches_container() => AlignFlags::END,
AlignFlags::SELF_END => AlignFlags::START,
AlignFlags::LEFT if alignment_container_writing_mode.is_bidi_ltr() => AlignFlags::START,
AlignFlags::LEFT => AlignFlags::END,
AlignFlags::RIGHT if alignment_container_writing_mode.is_bidi_ltr() => AlignFlags::END,
AlignFlags::RIGHT => AlignFlags::START,
AlignFlags::END | AlignFlags::FLEX_END | AlignFlags::LAST_BASELINE => AlignFlags::END,
_ => AlignFlags::START,
};
let alignment = match alignment {
AlignFlags::START if flip_anchor => AlignFlags::END,
AlignFlags::END if flip_anchor => AlignFlags::START,
alignment => alignment,
};
let free_space = alignment_container.length - size;
let flags = self.alignment.flags();
let alignment = if flags == AlignFlags::SAFE && free_space < Au::zero() {
AlignFlags::START
} else {
alignment
};
let origin = match alignment {
AlignFlags::START => alignment_container.origin,
AlignFlags::CENTER => alignment_container.origin + free_space / 2,
AlignFlags::END => alignment_container.origin + free_space,
_ => unreachable!(),
};
if matches!(flags, AlignFlags::SAFE | AlignFlags::UNSAFE) ||
matches!(
self.alignment,
AlignFlags::NORMAL | AlignFlags::AUTO | AlignFlags::STRETCH
)
{
return origin;
}
let Some(offsets) = offsets else {
return origin;
};
let min = Au::zero().min(offsets.start);
let max = self.containing_size - Au::zero().min(offsets.end) - size;
origin.clamp_between_extremums(min, Some(max))
}
}
pub(crate) fn relative_adjustement(
style: &ComputedValues,
containing_block: &ContainingBlock,
) -> LogicalVec2<Au> {
let cbis = containing_block.size.inline;
let cbbs = containing_block.size.block;
let box_offsets = style
.box_offsets(containing_block.style.writing_mode)
.map_inline_and_block_axes(
|value| value.map(|value| value.to_used_value(cbis)),
|value| match cbbs {
SizeConstraint::Definite(cbbs) => value.map(|value| value.to_used_value(cbbs)),
_ => match value.non_auto().and_then(|value| value.to_length()) {
Some(value) => AuOrAuto::LengthPercentage(value.into()),
None => AuOrAuto::Auto,
},
},
);
fn adjust(start: AuOrAuto, end: AuOrAuto) -> Au {
match (start, end) {
(AuOrAuto::Auto, AuOrAuto::Auto) => Au::zero(),
(AuOrAuto::Auto, AuOrAuto::LengthPercentage(end)) => -end,
(AuOrAuto::LengthPercentage(start), _) => start,
}
}
LogicalVec2 {
inline: adjust(box_offsets.inline_start, box_offsets.inline_end),
block: adjust(box_offsets.block_start, box_offsets.block_end),
}
}