blob: 7f1d9f4618c5f0b2b6f6f2f1a7501be85d26ca50 [file]
/* 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::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, LengthPercentageOrAuto, 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>,
/// A reference to a Fragment which is shared between this `HoistedAbsolutelyPositionedBox`
/// and its placeholder `AbsoluteOrFixedPositionedFragment` in the original tree position.
/// This will be used later in order to paint this hoisted box in tree order.
pub fragment: ArcRefCell<HoistedSharedFragment>,
}
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,
// Text decorations are not propagated to any out-of-flow descendants. In addition,
// absolutes don't affect the size of ancestors so it is fine to allow descendent
// tables to resolve percentage columns.
PropagatedBoxTreeData::default(),
),
}
}
pub(crate) fn to_hoisted(
absolutely_positioned_box: ArcRefCell<Self>,
static_position_rectangle: PhysicalRect<Au>,
resolved_alignment: LogicalVec2<AlignFlags>,
original_parent_writing_mode: WritingMode,
) -> HoistedAbsolutelyPositionedBox {
HoistedAbsolutelyPositionedBox {
fragment: ArcRefCell::new(HoistedSharedFragment::new(
static_position_rectangle,
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
}
}
/// Absolute and fixed position fragments are hoisted up to their containing blocks
/// from their tree position. When these fragments have static inset start positions,
/// that position (relative to the ancestor containing block) needs to be included
/// with the hoisted fragment so that it can be laid out properly at the containing
/// block.
///
/// This function is used to update the static position of hoisted boxes added after
/// the given index at every level of the fragment tree as the hoisted fragments move
/// up to their containing blocks. Once an ancestor fragment is laid out, this
/// function can be used to aggregate its offset to any descendent boxes that are
/// being hoisted. In this case, the appropriate index to use is the result of
/// [`PositioningContext::len()`] cached before laying out the [`Fragment`].
pub(crate) fn adjust_static_position_of_hoisted_fragments(
&mut self,
parent_fragment: &Fragment,
index: PositioningContextLength,
) {
let start_offset = match &parent_fragment {
Fragment::Box(fragment) | Fragment::Float(fragment) => {
fragment.borrow().content_rect.origin
},
Fragment::AbsoluteOrFixedPositioned(_) => return,
Fragment::Positioning(fragment) => fragment.borrow().rect.origin,
_ => unreachable!(),
};
self.adjust_static_position_of_hoisted_fragments_with_offset(
&start_offset.to_vector(),
index,
);
}
/// See documentation for [PositioningContext::adjust_static_position_of_hoisted_fragments].
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_fragment| {
hoisted_fragment
.fragment
.borrow_mut()
.adjust_offsets(offset)
})
}
/// Given `fragment_layout_fn`, a closure which lays out a fragment in a provided
/// `PositioningContext`, create a new positioning context if necessary for the fragment and
/// lay out the fragment and all its children. Returns the newly created `BoxFragment`.
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 {
// If a new `PositioningContext` isn't necessary, simply create the fragment using
// the given closure and the current `PositioningContext`.
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);
// Lay out all of the absolutely positioned children for this fragment, and, if it
// isn't a containing block for fixed elements, then pass those up to the parent.
new_context.layout_collected_children(layout_context, &mut new_fragment);
self.append(new_context);
if base.style.clone_position() == Position::Relative {
new_fragment.content_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>,
) {
debug_assert!(
new_fragment
.style
.establishes_containing_block_for_absolute_descendants(new_fragment.base.flags)
);
if new_fragment
.style
.establishes_containing_block_for_all_descendants(new_fragment.base.flags)
{
boxes_to_layout_out.append(&mut self.absolutes);
return;
}
// TODO: This could potentially use `extract_if` when that is stabilized.
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);
}
// Lay out the hoisted boxes collected into this `PositioningContext` and add them
// to the given `BoxFragment`.
pub(crate) fn layout_collected_children(
&mut self,
layout_context: &LayoutContext,
new_fragment: &mut BoxFragment,
) {
if self.absolutes.is_empty() {
return;
}
// Sometimes we create temporary PositioningContexts just to collect hoisted absolutes and
// then these are processed later. In that case and if this fragment doesn't establish a
// containing block for absolutes at all, we just do nothing. All hoisted fragments will
// later be passed up to a parent PositioningContext.
//
// Handling this case here, when the PositioningContext is completely ineffectual other than
// as a temporary container for hoisted boxes, means that callers can execute less conditional
// code.
if !new_fragment
.style
.establishes_containing_block_for_absolute_descendants(new_fragment.base.flags)
{
return;
}
let padding_rect = PhysicalRect::new(
// Ignore the content rect’s position in its own containing block:
PhysicalPoint::origin(),
new_fragment.content_rect.size,
)
.outer_rect(new_fragment.padding);
let containing_block = DefiniteContainingBlock {
size: padding_rect
.size
.to_logical(new_fragment.style.writing_mode),
style: &new_fragment.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,
);
// Laying out a `position: absolute` child (which only establishes a containing block for
// `position: absolute` descendants) can result in more `position: fixed` descendants
// collecting in `self.absolutes`. We need to loop here in order to keep either laying them
// out or putting them into `fixed_position_boxes_to_hoist`. We know there aren't any more
// when `self.absolutes` is empty.
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,
);
}
// We replace here instead of simply preserving these in `take_boxes_for_fragment`
// so that we don't have to continually re-iterate over them when laying out in the
// loop above.
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>,
) {
// Laying out a `position: absolute` child (which only establishes a containing block for
// `position: absolute` descendants) can result in more `position: fixed` descendants
// collecting in `self.absolutes`. We need to loop here in order to keep laying them out. We
// know there aren't any more when `self.absolutes` is empty.
while !self.absolutes.is_empty() {
HoistedAbsolutelyPositionedBox::layout_many(
layout_context,
mem::take(&mut self.absolutes),
fragments,
&mut self.absolutes,
initial_containing_block,
Default::default(),
)
}
}
/// Get the length of this [PositioningContext].
pub(crate) fn len(&self) -> PositioningContextLength {
PositioningContextLength(self.absolutes.len())
}
/// Truncate this [PositioningContext] to the given [PositioningContextLength]. This
/// is useful for "unhoisting" boxes in this context and returning it to the state at
/// the time that [`PositioningContext::len()`] was called.
pub(crate) fn truncate(&mut self, length: &PositioningContextLength) {
self.absolutes.truncate(length.0)
}
}
/// A data structure which stores the size of a positioning context.
#[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 shared_fragment = self.fragment.borrow();
// The static position rect was calculated assuming that the containing block would be
// established by the content box of some ancestor, but the actual containing block is
// established by the padding box. So we need to add the padding of that ancestor.
let mut static_position_rect = shared_fragment
.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);
// When the "static-position rect" doesn't come into play, we do not do any alignment
// in the inline axis.
let inline_box_offsets = box_offset.inline_sides();
let inline_alignment = match inline_box_offsets.either_specified() {
true => style.clone_justify_self().0.0,
false => shared_fragment.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: shared_fragment.original_parent_writing_mode.is_bidi_ltr() !=
containing_block_writing_mode.is_bidi_ltr(),
is_table_or_replaced,
};
// When the "static-position rect" doesn't come into play, we re-resolve "align-self"
// against this containing block.
let block_box_offsets = box_offset.block_sides();
let block_alignment = match block_box_offsets.either_specified() {
true => style.clone_align_self().0.0,
false => shared_fragment.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,
};
// The block size can depend on layout results, so we only solve it tentatively,
// we may have to resolve it properly later on.
let block_automatic_size = block_axis_solver.automatic_size();
let block_stretch_size = Some(block_axis_solver.stretch_size());
let tentative_block_content_size =
context.tentative_block_content_size(preferred_aspect_ratio);
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,
)
};
// The inline axis can be fully resolved, computing intrinsic sizes using the
// extrinsic block size.
let get_inline_content_size = || {
let constraint_space = ConstraintSpace::new(
tentative_block_size,
style.writing_mode,
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_axis_solver.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,
};
// https://drafts.csswg.org/css-writing-modes/#orthogonal-flows
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 {
// Tables can become narrower than predicted due to collapsed columns.
inline: content_inline_size_for_table.unwrap_or(inline_size),
// Now we can properly solve the block 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,
shared_fragment.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,
shared_fragment.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,
);
// This is an absolutely positioned element, which means it also establishes a
// containing block for absolutes. We lay out any absolutely positioned children
// here and pass the rest to `hoisted_absolutes_from_children.`
positioning_context.layout_collected_children(layout_context, &mut new_fragment);
// Any hoisted boxes that remain in this positioning context are going to be hoisted
// up above this absolutely positioned box. These will necessarily be fixed position
// elements, because absolutely positioned elements form containing blocks for all
// other elements. If any of them have a static start position though, we need to
// adjust it to account for the start corner of this absolute.
positioning_context.adjust_static_position_of_hoisted_fragments_with_offset(
&new_fragment.content_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
}
}
#[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<'a> {
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<LengthPercentageOrAuto<'a>>,
static_position_rect_axis: RectAxis,
alignment: AlignFlags,
flip_anchor: bool,
is_table_or_replaced: bool,
}
impl AbsoluteAxisSolver<'_> {
/// Returns the amount that we need to subtract from the containing block size in order to
/// obtain the inset-modified containing block that we will use for sizing purposes.
/// (Note that for alignment purposes, we may re-resolve auto insets to a different value.)
/// <https://drafts.csswg.org/css-position/#resolving-insets>
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.to_used_value(self.containing_size),
(None, Some(end)) => end.to_used_value(self.containing_size),
(Some(start), Some(end)) => {
start.to_used_value(self.containing_size) + end.to_used_value(self.containing_size)
},
}
}
#[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.containing_size -
self.inset_sum() -
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.containing_size - self.inset_sum() - 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 offsets = LogicalSides1D {
start: start.to_used_value(self.containing_size),
end: end.to_used_value(self.containing_size),
};
let alignment_container = RectAxis {
origin: offsets.start,
length: self.containing_size - offsets.sum(),
};
(
alignment_container,
containing_block_writing_mode,
false,
Some(offsets),
)
},
// If a single offset is auto, for alignment purposes it resolves to the amount
// that makes the inset-modified containing block be exactly as big as the abspos.
// Therefore the free space is zero and the alignment value is irrelevant.
(Some(start), None) => return start.to_used_value(self.containing_size),
(None, Some(end)) => {
return self.containing_size - size - end.to_used_value(self.containing_size);
},
};
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()
};
// Here we resolve the alignment to either start, center, or end.
// Note we need to handle both self-alignment values (when some inset isn't auto)
// and distributed alignment values (when both insets are auto).
// The latter are treated as their fallback alignment.
let alignment = match self.alignment.value() {
// https://drafts.csswg.org/css-align/#valdef-self-position-center
// https://drafts.csswg.org/css-align/#valdef-align-content-space-around
// https://drafts.csswg.org/css-align/#valdef-align-content-space-evenly
AlignFlags::CENTER | AlignFlags::SPACE_AROUND | AlignFlags::SPACE_EVENLY => {
AlignFlags::CENTER
},
// https://drafts.csswg.org/css-align/#valdef-self-position-self-start
AlignFlags::SELF_START if self_value_matches_container() => AlignFlags::START,
AlignFlags::SELF_START => AlignFlags::END,
// https://drafts.csswg.org/css-align/#valdef-self-position-self-end
AlignFlags::SELF_END if self_value_matches_container() => AlignFlags::END,
AlignFlags::SELF_END => AlignFlags::START,
// https://drafts.csswg.org/css-align/#valdef-justify-content-left
AlignFlags::LEFT if alignment_container_writing_mode.is_bidi_ltr() => AlignFlags::START,
AlignFlags::LEFT => AlignFlags::END,
// https://drafts.csswg.org/css-align/#valdef-justify-content-right
AlignFlags::RIGHT if alignment_container_writing_mode.is_bidi_ltr() => AlignFlags::END,
AlignFlags::RIGHT => AlignFlags::START,
// https://drafts.csswg.org/css-align/#valdef-self-position-end
// https://drafts.csswg.org/css-align/#valdef-self-position-flex-end
AlignFlags::END | AlignFlags::FLEX_END => AlignFlags::END,
// https://drafts.csswg.org/css-align/#valdef-self-position-start
// https://drafts.csswg.org/css-align/#valdef-self-position-flex-start
_ => 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;
};
// Handle default overflow alignment.
// https://drafts.csswg.org/css-align/#auto-safety-position
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))
}
}
/// <https://drafts.csswg.org/css2/visuren.html#relative-positioning>
pub(crate) fn relative_adjustement(
style: &ComputedValues,
containing_block: &ContainingBlock,
) -> LogicalVec2<Au> {
// It's not completely clear what to do with indefinite percentages
// (https://github.com/w3c/csswg-drafts/issues/9353), so we match
// other browsers and treat them as 'auto' offsets.
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),
}
}