1use std::mem;
6
7use app_units::Au;
8use malloc_size_of_derive::MallocSizeOf;
9use rayon::iter::IntoParallelRefMutIterator;
10use rayon::prelude::{IndexedParallelIterator, ParallelIterator};
11use style::Zero;
12use style::computed_values::position::T as Position;
13use style::logical_geometry::{Direction, WritingMode};
14use style::properties::ComputedValues;
15use style::values::specified::align::AlignFlags;
16
17use crate::cell::ArcRefCell;
18use crate::context::LayoutContext;
19use crate::dom_traversal::{Contents, NodeAndStyleInfo};
20use crate::formatting_contexts::IndependentFormattingContext;
21use crate::fragment_tree::{BoxFragment, Fragment, FragmentFlags, HoistedSharedFragment};
22use crate::geom::{
23 AuOrAuto, LogicalRect, LogicalSides, LogicalSides1D, LogicalVec2, PhysicalPoint, PhysicalRect,
24 PhysicalSides, PhysicalSize, PhysicalVec, ToLogical, ToLogicalWithContainingBlock,
25};
26use crate::layout_box_base::{CacheableLayoutResult, LayoutBoxBase};
27use crate::sizing::{LazySize, Size, SizeConstraint, Sizes};
28use crate::style_ext::{Clamp, ComputedValuesExt, ContentBoxSizesAndPBM, DisplayInside};
29use crate::{
30 ConstraintSpace, ContainingBlock, ContainingBlockSize, DefiniteContainingBlock,
31 PropagatedBoxTreeData,
32};
33
34#[derive(Debug, MallocSizeOf)]
35pub(crate) struct AbsolutelyPositionedBox {
36 pub context: IndependentFormattingContext,
37}
38
39#[derive(Clone, MallocSizeOf)]
40pub(crate) struct HoistedAbsolutelyPositionedBox {
41 absolutely_positioned_box: ArcRefCell<AbsolutelyPositionedBox>,
42 pub fragment: ArcRefCell<HoistedSharedFragment>,
46 pub adjusted_static_position_rect: Option<PhysicalRect<Au>>,
56 pub resolved_alignment: LogicalVec2<AlignFlags>,
61 pub original_parent_writing_mode: WritingMode,
66}
67
68impl AbsolutelyPositionedBox {
69 pub fn new(context: IndependentFormattingContext) -> Self {
70 Self { context }
71 }
72
73 pub fn construct(
74 context: &LayoutContext,
75 node_info: &NodeAndStyleInfo,
76 display_inside: DisplayInside,
77 contents: Contents,
78 ) -> Self {
79 Self {
80 context: IndependentFormattingContext::construct(
81 context,
82 node_info,
83 display_inside,
84 contents,
85 PropagatedBoxTreeData::default(),
89 ),
90 }
91 }
92
93 pub(crate) fn to_hoisted(
94 absolutely_positioned_box: ArcRefCell<Self>,
95 static_position_rect: PhysicalRect<Au>,
96 resolved_alignment: LogicalVec2<AlignFlags>,
97 original_parent_writing_mode: WritingMode,
98 ) -> HoistedAbsolutelyPositionedBox {
99 HoistedAbsolutelyPositionedBox {
100 fragment: ArcRefCell::new(HoistedSharedFragment::new(static_position_rect)),
101 adjusted_static_position_rect: None,
102 resolved_alignment,
103 original_parent_writing_mode,
104 absolutely_positioned_box,
105 }
106 }
107}
108
109#[derive(Clone, Default, MallocSizeOf)]
110pub(crate) struct PositioningContext {
111 absolutes: Vec<HoistedAbsolutelyPositionedBox>,
112}
113
114impl PositioningContext {
115 #[inline]
116 pub(crate) fn new_for_layout_box_base(layout_box_base: &LayoutBoxBase) -> Option<Self> {
117 Self::new_for_style_and_fragment_flags(
118 &layout_box_base.style,
119 &layout_box_base.base_fragment_info.flags,
120 )
121 }
122
123 fn new_for_style_and_fragment_flags(
124 style: &ComputedValues,
125 flags: &FragmentFlags,
126 ) -> Option<Self> {
127 if style.establishes_containing_block_for_absolute_descendants(*flags) {
128 Some(Self::default())
129 } else {
130 None
131 }
132 }
133
134 pub(crate) fn adjust_static_position_of_hoisted_fragments(
147 &mut self,
148 parent_fragment: &Fragment,
149 index: PositioningContextLength,
150 ) {
151 let Some(base) = parent_fragment.base() else {
152 return;
153 };
154 self.adjust_static_position_of_hoisted_fragments_with_offset(
155 &base.rect.origin.to_vector(),
156 index,
157 );
158 }
159
160 pub(crate) fn adjust_static_position_of_hoisted_fragments_with_offset(
162 &mut self,
163 offset: &PhysicalVec<Au>,
164 index: PositioningContextLength,
165 ) {
166 self.absolutes
167 .iter_mut()
168 .skip(index.0)
169 .for_each(|hoisted_box| {
170 hoisted_box.adjust_static_position_with_offset(offset);
171 })
172 }
173
174 pub(crate) fn layout_maybe_position_relative_fragment(
178 &mut self,
179 layout_context: &LayoutContext,
180 containing_block: &ContainingBlock,
181 base: &LayoutBoxBase,
182 fragment_layout_fn: impl FnOnce(&mut Self) -> BoxFragment,
183 ) -> BoxFragment {
184 let establishes_containing_block_for_absolutes = base
187 .style
188 .establishes_containing_block_for_absolute_descendants(base.base_fragment_info.flags);
189 if !establishes_containing_block_for_absolutes {
190 return fragment_layout_fn(self);
191 }
192
193 let mut new_context = PositioningContext::default();
194 let mut new_fragment = fragment_layout_fn(&mut new_context);
195
196 new_context.layout_collected_children(layout_context, &mut new_fragment);
199 self.append(new_context);
200
201 if base.style.clone_position() == Position::Relative {
202 new_fragment.base.rect.origin += relative_adjustement(&base.style, containing_block)
203 .to_physical_vector(containing_block.style.writing_mode)
204 }
205
206 new_fragment
207 }
208
209 fn take_boxes_for_fragment(
210 &mut self,
211 new_fragment: &BoxFragment,
212 boxes_to_layout_out: &mut Vec<HoistedAbsolutelyPositionedBox>,
213 boxes_to_continue_hoisting_out: &mut Vec<HoistedAbsolutelyPositionedBox>,
214 ) {
215 let style = new_fragment.style();
216 debug_assert!(
217 style.establishes_containing_block_for_absolute_descendants(new_fragment.base.flags)
218 );
219
220 if style.establishes_containing_block_for_all_descendants(new_fragment.base.flags) {
221 boxes_to_layout_out.append(&mut self.absolutes);
222 return;
223 }
224
225 let (mut boxes_to_layout, mut boxes_to_continue_hoisting) = self
227 .absolutes
228 .drain(..)
229 .partition(|hoisted_box| hoisted_box.position() != Position::Fixed);
230 boxes_to_layout_out.append(&mut boxes_to_layout);
231 boxes_to_continue_hoisting_out.append(&mut boxes_to_continue_hoisting);
232 }
233
234 pub(crate) fn layout_collected_children(
237 &mut self,
238 layout_context: &LayoutContext,
239 new_fragment: &mut BoxFragment,
240 ) {
241 if self.absolutes.is_empty() {
242 return;
243 }
244
245 let style = new_fragment.style().clone();
254 if !style.establishes_containing_block_for_absolute_descendants(new_fragment.base.flags) {
255 return;
256 }
257
258 let padding_rect = PhysicalRect::new(
259 PhysicalPoint::origin(),
261 new_fragment.base.rect.size,
262 )
263 .outer_rect(new_fragment.padding);
264 let containing_block = DefiniteContainingBlock {
265 size: padding_rect.size.to_logical(style.writing_mode),
266 style: &style,
267 };
268
269 let mut fixed_position_boxes_to_hoist = Vec::new();
270 let mut boxes_to_layout = Vec::new();
271 self.take_boxes_for_fragment(
272 new_fragment,
273 &mut boxes_to_layout,
274 &mut fixed_position_boxes_to_hoist,
275 );
276
277 while !boxes_to_layout.is_empty() {
283 HoistedAbsolutelyPositionedBox::layout_many(
284 layout_context,
285 std::mem::take(&mut boxes_to_layout),
286 &mut new_fragment.children,
287 &mut self.absolutes,
288 &containing_block,
289 new_fragment.padding,
290 );
291
292 self.take_boxes_for_fragment(
293 new_fragment,
294 &mut boxes_to_layout,
295 &mut fixed_position_boxes_to_hoist,
296 );
297 }
298
299 self.absolutes = fixed_position_boxes_to_hoist;
303 }
304
305 pub(crate) fn push(&mut self, hoisted_box: HoistedAbsolutelyPositionedBox) {
306 debug_assert!(hoisted_box.position().is_absolutely_positioned());
307 self.absolutes.push(hoisted_box);
308 }
309
310 pub(crate) fn append(&mut self, mut other: Self) {
311 if other.absolutes.is_empty() {
312 return;
313 }
314 if self.absolutes.is_empty() {
315 self.absolutes = other.absolutes;
316 } else {
317 self.absolutes.append(&mut other.absolutes)
318 }
319 }
320
321 pub(crate) fn layout_initial_containing_block_children(
322 &mut self,
323 layout_context: &LayoutContext,
324 initial_containing_block: &DefiniteContainingBlock,
325 fragments: &mut Vec<Fragment>,
326 ) {
327 while !self.absolutes.is_empty() {
332 HoistedAbsolutelyPositionedBox::layout_many(
333 layout_context,
334 mem::take(&mut self.absolutes),
335 fragments,
336 &mut self.absolutes,
337 initial_containing_block,
338 Default::default(),
339 )
340 }
341 }
342
343 pub(crate) fn len(&self) -> PositioningContextLength {
345 PositioningContextLength(self.absolutes.len())
346 }
347
348 pub(crate) fn truncate(&mut self, length: &PositioningContextLength) {
352 self.absolutes.truncate(length.0)
353 }
354}
355
356#[derive(Clone, Copy, Debug, PartialEq)]
358pub(crate) struct PositioningContextLength(usize);
359
360impl Zero for PositioningContextLength {
361 fn zero() -> Self {
362 Self(0)
363 }
364
365 fn is_zero(&self) -> bool {
366 self.0.is_zero()
367 }
368}
369
370impl HoistedAbsolutelyPositionedBox {
371 fn position(&self) -> Position {
372 let position = self
373 .absolutely_positioned_box
374 .borrow()
375 .context
376 .style()
377 .clone_position();
378 assert!(position.is_absolutely_positioned());
379 position
380 }
381
382 pub(crate) fn layout_many(
383 layout_context: &LayoutContext,
384 mut boxes: Vec<Self>,
385 fragments: &mut Vec<Fragment>,
386 for_nearest_containing_block_for_all_descendants: &mut Vec<HoistedAbsolutelyPositionedBox>,
387 containing_block: &DefiniteContainingBlock,
388 containing_block_padding: PhysicalSides<Au>,
389 ) {
390 if layout_context.use_rayon {
391 let mut new_fragments = Vec::new();
392 let mut new_hoisted_boxes = Vec::new();
393
394 boxes
395 .par_iter_mut()
396 .map(|hoisted_box| {
397 let mut new_hoisted_boxes: Vec<HoistedAbsolutelyPositionedBox> = Vec::new();
398 let new_fragment = hoisted_box.layout(
399 layout_context,
400 &mut new_hoisted_boxes,
401 containing_block,
402 containing_block_padding,
403 );
404
405 hoisted_box.fragment.borrow_mut().fragment = Some(new_fragment.clone());
406 (new_fragment, new_hoisted_boxes)
407 })
408 .unzip_into_vecs(&mut new_fragments, &mut new_hoisted_boxes);
409
410 fragments.extend(new_fragments);
411 for_nearest_containing_block_for_all_descendants
412 .extend(new_hoisted_boxes.into_iter().flatten());
413 } else {
414 fragments.extend(boxes.iter_mut().map(|box_| {
415 let new_fragment = box_.layout(
416 layout_context,
417 for_nearest_containing_block_for_all_descendants,
418 containing_block,
419 containing_block_padding,
420 );
421
422 box_.fragment.borrow_mut().fragment = Some(new_fragment.clone());
423 new_fragment
424 }))
425 }
426 }
427
428 pub(crate) fn layout(
429 &mut self,
430 layout_context: &LayoutContext,
431 hoisted_absolutes_from_children: &mut Vec<HoistedAbsolutelyPositionedBox>,
432 containing_block: &DefiniteContainingBlock,
433 containing_block_padding: PhysicalSides<Au>,
434 ) -> Fragment {
435 let cbis = containing_block.size.inline;
436 let cbbs = containing_block.size.block;
437 let containing_block_writing_mode = containing_block.style.writing_mode;
438 let absolutely_positioned_box = self.absolutely_positioned_box.borrow();
439 let context = &absolutely_positioned_box.context;
440 let style = context.style().clone();
441 let layout_style = context.layout_style();
442 let ContentBoxSizesAndPBM {
443 content_box_sizes,
444 pbm,
445 ..
446 } = layout_style.content_box_sizes_and_padding_border_margin(&containing_block.into());
447 let containing_block = &containing_block.into();
448 let is_table = layout_style.is_table();
449 let is_table_or_replaced = is_table || context.is_replaced();
450 let preferred_aspect_ratio = context.preferred_aspect_ratio(&pbm.padding_border_sums);
451
452 let mut static_position_rect = self
456 .static_position_rect()
457 .outer_rect(-containing_block_padding);
458 static_position_rect.size = static_position_rect.size.max(PhysicalSize::zero());
459 let static_position_rect = static_position_rect.to_logical(containing_block);
460
461 let box_offset = style.box_offsets(containing_block.style.writing_mode);
462
463 let inline_box_offsets = box_offset.inline_sides().percentages_relative_to(cbis);
466 let inline_alignment = match inline_box_offsets.either_specified() {
467 true => style.clone_justify_self().0,
468 false => self.resolved_alignment.inline,
469 };
470
471 let inline_axis_solver = AbsoluteAxisSolver {
472 axis: Direction::Inline,
473 containing_size: cbis,
474 padding_border_sum: pbm.padding_border_sums.inline,
475 computed_margin_start: pbm.margin.inline_start,
476 computed_margin_end: pbm.margin.inline_end,
477 computed_sizes: content_box_sizes.inline,
478 avoid_negative_margin_start: true,
479 box_offsets: inline_box_offsets,
480 static_position_rect_axis: static_position_rect.get_axis(Direction::Inline),
481 alignment: inline_alignment,
482 flip_anchor: self.original_parent_writing_mode.is_bidi_ltr() !=
483 containing_block_writing_mode.is_bidi_ltr(),
484 is_table_or_replaced,
485 };
486
487 let block_box_offsets = box_offset.block_sides().percentages_relative_to(cbbs);
490 let block_alignment = match block_box_offsets.either_specified() {
491 true => style.clone_align_self().0,
492 false => self.resolved_alignment.block,
493 };
494 let block_axis_solver = AbsoluteAxisSolver {
495 axis: Direction::Block,
496 containing_size: cbbs,
497 padding_border_sum: pbm.padding_border_sums.block,
498 computed_margin_start: pbm.margin.block_start,
499 computed_margin_end: pbm.margin.block_end,
500 computed_sizes: content_box_sizes.block,
501 avoid_negative_margin_start: false,
502 box_offsets: block_box_offsets,
503 static_position_rect_axis: static_position_rect.get_axis(Direction::Block),
504 alignment: block_alignment,
505 flip_anchor: false,
506 is_table_or_replaced,
507 };
508
509 let block_automatic_size = block_axis_solver.automatic_size();
512 let block_stretch_size = Some(block_axis_solver.stretch_size());
513 let inline_stretch_size = inline_axis_solver.stretch_size();
514 let tentative_block_content_size =
515 context.tentative_block_content_size(preferred_aspect_ratio, inline_stretch_size);
516 let tentative_block_size = if let Some(block_content_size) = tentative_block_content_size {
517 SizeConstraint::Definite(block_axis_solver.computed_sizes.resolve(
518 Direction::Block,
519 block_automatic_size,
520 Au::zero,
521 block_stretch_size,
522 || block_content_size,
523 is_table,
524 ))
525 } else {
526 block_axis_solver.computed_sizes.resolve_extrinsic(
527 block_automatic_size,
528 Au::zero(),
529 block_stretch_size,
530 )
531 };
532
533 let get_inline_content_size = || {
536 let constraint_space =
537 ConstraintSpace::new(tentative_block_size, &style, preferred_aspect_ratio);
538 context
539 .inline_content_sizes(layout_context, &constraint_space)
540 .sizes
541 };
542 let inline_size = inline_axis_solver.computed_sizes.resolve(
543 Direction::Inline,
544 inline_axis_solver.automatic_size(),
545 Au::zero,
546 Some(inline_stretch_size),
547 get_inline_content_size,
548 is_table,
549 );
550
551 let containing_block_for_children = ContainingBlock {
552 size: ContainingBlockSize {
553 inline: inline_size,
554 block: tentative_block_size,
555 },
556 style: &style,
557 };
558 assert_eq!(
560 containing_block_writing_mode.is_horizontal(),
561 style.writing_mode.is_horizontal(),
562 "Mixed horizontal and vertical writing modes are not supported yet"
563 );
564
565 let mut positioning_context = PositioningContext::default();
566 let lazy_block_size = LazySize::new(
567 &block_axis_solver.computed_sizes,
568 Direction::Block,
569 block_automatic_size,
570 Au::zero,
571 block_stretch_size,
572 is_table,
573 );
574 let CacheableLayoutResult {
575 content_inline_size_for_table,
576 content_block_size,
577 fragments,
578 specific_layout_info,
579 ..
580 } = context.layout(
581 layout_context,
582 &mut positioning_context,
583 &containing_block_for_children,
584 containing_block,
585 preferred_aspect_ratio,
586 &lazy_block_size,
587 );
588
589 let content_size = LogicalVec2 {
590 inline: content_inline_size_for_table.unwrap_or(inline_size),
592
593 block: lazy_block_size.resolve(|| content_block_size),
595 };
596
597 let inline_margins = inline_axis_solver.solve_margins(content_size.inline);
598 let block_margins = block_axis_solver.solve_margins(content_size.block);
599 let margin = LogicalSides {
600 inline_start: inline_margins.start,
601 inline_end: inline_margins.end,
602 block_start: block_margins.start,
603 block_end: block_margins.end,
604 };
605
606 let pb = pbm.padding + pbm.border;
607 let margin_rect_size = content_size + pbm.padding_border_sums + margin.sum();
608 let inline_origin = inline_axis_solver.origin_for_margin_box(
609 margin_rect_size.inline,
610 style.writing_mode,
611 self.original_parent_writing_mode,
612 containing_block_writing_mode,
613 );
614 let block_origin = block_axis_solver.origin_for_margin_box(
615 margin_rect_size.block,
616 style.writing_mode,
617 self.original_parent_writing_mode,
618 containing_block_writing_mode,
619 );
620
621 let content_rect = LogicalRect {
622 start_corner: LogicalVec2 {
623 inline: inline_origin + margin.inline_start + pb.inline_start,
624 block: block_origin + margin.block_start + pb.block_start,
625 },
626 size: content_size,
627 };
628 let mut new_fragment = BoxFragment::new(
629 context.base_fragment_info(),
630 style,
631 fragments,
632 content_rect.as_physical(Some(containing_block)),
633 pbm.padding.to_physical(containing_block_writing_mode),
634 pbm.border.to_physical(containing_block_writing_mode),
635 margin.to_physical(containing_block_writing_mode),
636 specific_layout_info,
637 );
638
639 positioning_context.layout_collected_children(layout_context, &mut new_fragment);
643
644 positioning_context.adjust_static_position_of_hoisted_fragments_with_offset(
650 &new_fragment.base.rect.origin.to_vector(),
651 PositioningContextLength::zero(),
652 );
653
654 hoisted_absolutes_from_children.extend(positioning_context.absolutes);
655
656 let fragment = Fragment::Box(ArcRefCell::new(new_fragment));
657 context.base.set_fragment(fragment.clone());
658 fragment
659 }
660
661 fn static_position_rect(&self) -> PhysicalRect<Au> {
662 self.adjusted_static_position_rect
663 .unwrap_or_else(|| self.fragment.borrow().original_static_position_rect)
664 }
665
666 fn adjust_static_position_with_offset(&mut self, offset: &PhysicalVec<Au>) {
667 self.adjusted_static_position_rect = Some(self.static_position_rect().translate(*offset));
668 }
669}
670
671#[derive(Clone, Copy, Debug)]
672struct RectAxis {
673 origin: Au,
674 length: Au,
675}
676
677impl LogicalRect<Au> {
678 fn get_axis(&self, axis: Direction) -> RectAxis {
679 match axis {
680 Direction::Block => RectAxis {
681 origin: self.start_corner.block,
682 length: self.size.block,
683 },
684 Direction::Inline => RectAxis {
685 origin: self.start_corner.inline,
686 length: self.size.inline,
687 },
688 }
689 }
690}
691
692struct AbsoluteAxisSolver {
693 axis: Direction,
694 containing_size: Au,
695 padding_border_sum: Au,
696 computed_margin_start: AuOrAuto,
697 computed_margin_end: AuOrAuto,
698 computed_sizes: Sizes,
699 avoid_negative_margin_start: bool,
700 box_offsets: LogicalSides1D<AuOrAuto>,
701 static_position_rect_axis: RectAxis,
702 alignment: AlignFlags,
703 flip_anchor: bool,
704 is_table_or_replaced: bool,
705}
706
707impl AbsoluteAxisSolver {
708 fn inset_sum(&self) -> Au {
713 match (
714 self.box_offsets.start.non_auto(),
715 self.box_offsets.end.non_auto(),
716 ) {
717 (None, None) => {
718 if self.flip_anchor {
719 self.containing_size -
720 self.static_position_rect_axis.origin -
721 self.static_position_rect_axis.length
722 } else {
723 self.static_position_rect_axis.origin
724 }
725 },
726 (Some(start), None) => start,
727 (None, Some(end)) => end,
728 (Some(start), Some(end)) => start + end,
729 }
730 }
731
732 #[inline]
735 fn available_space(&self) -> Au {
736 Au::zero().max(self.containing_size - self.inset_sum())
737 }
738
739 #[inline]
740 fn automatic_size(&self) -> Size<Au> {
741 match self.alignment.value() {
742 _ if self.box_offsets.either_auto() => Size::FitContent,
743 AlignFlags::NORMAL | AlignFlags::AUTO if !self.is_table_or_replaced => Size::Stretch,
744 AlignFlags::STRETCH => Size::Stretch,
745 _ => Size::FitContent,
746 }
747 }
748
749 #[inline]
750 fn stretch_size(&self) -> Au {
751 Au::zero().max(
752 self.available_space() -
753 self.padding_border_sum -
754 self.computed_margin_start.auto_is(Au::zero) -
755 self.computed_margin_end.auto_is(Au::zero),
756 )
757 }
758
759 fn solve_margins(&self, size: Au) -> LogicalSides1D<Au> {
760 if self.box_offsets.either_auto() {
761 LogicalSides1D::new(
762 self.computed_margin_start.auto_is(Au::zero),
763 self.computed_margin_end.auto_is(Au::zero),
764 )
765 } else {
766 let free_space = self.available_space() - self.padding_border_sum - size;
767 match (self.computed_margin_start, self.computed_margin_end) {
768 (AuOrAuto::Auto, AuOrAuto::Auto) => {
769 if self.avoid_negative_margin_start && free_space < Au::zero() {
770 LogicalSides1D::new(Au::zero(), free_space)
771 } else {
772 let margin_start = free_space / 2;
773 LogicalSides1D::new(margin_start, free_space - margin_start)
774 }
775 },
776 (AuOrAuto::Auto, AuOrAuto::LengthPercentage(end)) => {
777 LogicalSides1D::new(free_space - end, end)
778 },
779 (AuOrAuto::LengthPercentage(start), AuOrAuto::Auto) => {
780 LogicalSides1D::new(start, free_space - start)
781 },
782 (AuOrAuto::LengthPercentage(start), AuOrAuto::LengthPercentage(end)) => {
783 LogicalSides1D::new(start, end)
784 },
785 }
786 }
787 }
788
789 fn origin_for_margin_box(
790 &self,
791 size: Au,
792 self_writing_mode: WritingMode,
793 original_parent_writing_mode: WritingMode,
794 containing_block_writing_mode: WritingMode,
795 ) -> Au {
796 let (alignment_container, alignment_container_writing_mode, flip_anchor, offsets) = match (
797 self.box_offsets.start.non_auto(),
798 self.box_offsets.end.non_auto(),
799 ) {
800 (None, None) => (
801 self.static_position_rect_axis,
802 original_parent_writing_mode,
803 self.flip_anchor,
804 None,
805 ),
806 (Some(start), Some(end)) => {
807 let alignment_container = RectAxis {
808 origin: start,
809 length: self.available_space(),
810 };
811 (
812 alignment_container,
813 containing_block_writing_mode,
814 false,
815 Some(LogicalSides1D { start, end }),
816 )
817 },
818 (Some(start), None) => return start,
822 (None, Some(end)) => {
823 return self.containing_size - size - end;
824 },
825 };
826
827 assert_eq!(
828 self_writing_mode.is_horizontal(),
829 original_parent_writing_mode.is_horizontal(),
830 "Mixed horizontal and vertical writing modes are not supported yet"
831 );
832 assert_eq!(
833 self_writing_mode.is_horizontal(),
834 containing_block_writing_mode.is_horizontal(),
835 "Mixed horizontal and vertical writing modes are not supported yet"
836 );
837 let self_value_matches_container = || {
838 self.axis == Direction::Block ||
839 self_writing_mode.is_bidi_ltr() == alignment_container_writing_mode.is_bidi_ltr()
840 };
841
842 let alignment = match self.alignment.value() {
847 AlignFlags::CENTER | AlignFlags::SPACE_AROUND | AlignFlags::SPACE_EVENLY => {
851 AlignFlags::CENTER
852 },
853 AlignFlags::SELF_START if self_value_matches_container() => AlignFlags::START,
855 AlignFlags::SELF_START => AlignFlags::END,
856 AlignFlags::SELF_END if self_value_matches_container() => AlignFlags::END,
858 AlignFlags::SELF_END => AlignFlags::START,
859 AlignFlags::LEFT if alignment_container_writing_mode.is_bidi_ltr() => AlignFlags::START,
861 AlignFlags::LEFT => AlignFlags::END,
862 AlignFlags::RIGHT if alignment_container_writing_mode.is_bidi_ltr() => AlignFlags::END,
864 AlignFlags::RIGHT => AlignFlags::START,
865 AlignFlags::END | AlignFlags::FLEX_END | AlignFlags::LAST_BASELINE => AlignFlags::END,
869 _ => AlignFlags::START,
873 };
874
875 let alignment = match alignment {
876 AlignFlags::START if flip_anchor => AlignFlags::END,
877 AlignFlags::END if flip_anchor => AlignFlags::START,
878 alignment => alignment,
879 };
880
881 let free_space = alignment_container.length - size;
882 let flags = self.alignment.flags();
883 let alignment = if flags == AlignFlags::SAFE && free_space < Au::zero() {
884 AlignFlags::START
885 } else {
886 alignment
887 };
888
889 let origin = match alignment {
890 AlignFlags::START => alignment_container.origin,
891 AlignFlags::CENTER => alignment_container.origin + free_space / 2,
892 AlignFlags::END => alignment_container.origin + free_space,
893 _ => unreachable!(),
894 };
895 if matches!(flags, AlignFlags::SAFE | AlignFlags::UNSAFE) ||
896 matches!(
897 self.alignment,
898 AlignFlags::NORMAL | AlignFlags::AUTO | AlignFlags::STRETCH
899 )
900 {
901 return origin;
902 }
903 let Some(offsets) = offsets else {
904 return origin;
905 };
906
907 let min = Au::zero().min(offsets.start);
910 let max = self.containing_size - Au::zero().min(offsets.end) - size;
911 origin.clamp_between_extremums(min, Some(max))
912 }
913}
914
915pub(crate) fn relative_adjustement(
917 style: &ComputedValues,
918 containing_block: &ContainingBlock,
919) -> LogicalVec2<Au> {
920 let cbis = containing_block.size.inline;
924 let cbbs = containing_block.size.block;
925 let box_offsets = style
926 .box_offsets(containing_block.style.writing_mode)
927 .map_inline_and_block_axes(
928 |value| value.map(|value| value.to_used_value(cbis)),
929 |value| match cbbs {
930 SizeConstraint::Definite(cbbs) => value.map(|value| value.to_used_value(cbbs)),
931 _ => match value.non_auto().and_then(|value| value.to_length()) {
932 Some(value) => AuOrAuto::LengthPercentage(value.into()),
933 None => AuOrAuto::Auto,
934 },
935 },
936 );
937 fn adjust(start: AuOrAuto, end: AuOrAuto) -> Au {
938 match (start, end) {
939 (AuOrAuto::Auto, AuOrAuto::Auto) => Au::zero(),
940 (AuOrAuto::Auto, AuOrAuto::LengthPercentage(end)) => -end,
941 (AuOrAuto::LengthPercentage(start), _) => start,
942 }
943 }
944 LogicalVec2 {
945 inline: adjust(box_offsets.inline_start, box_offsets.inline_end),
946 block: adjust(box_offsets.block_start, box_offsets.block_end),
947 }
948}