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 start_offset = match &parent_fragment {
152 Fragment::Box(fragment) | Fragment::Float(fragment) => {
153 fragment.borrow().content_rect.origin
154 },
155 Fragment::AbsoluteOrFixedPositioned(_) => return,
156 Fragment::Positioning(fragment) => fragment.borrow().rect.origin,
157 _ => unreachable!(),
158 };
159 self.adjust_static_position_of_hoisted_fragments_with_offset(
160 &start_offset.to_vector(),
161 index,
162 );
163 }
164
165 pub(crate) fn adjust_static_position_of_hoisted_fragments_with_offset(
167 &mut self,
168 offset: &PhysicalVec<Au>,
169 index: PositioningContextLength,
170 ) {
171 self.absolutes
172 .iter_mut()
173 .skip(index.0)
174 .for_each(|hoisted_box| {
175 hoisted_box.adjust_static_position_with_offset(offset);
176 })
177 }
178
179 pub(crate) fn layout_maybe_position_relative_fragment(
183 &mut self,
184 layout_context: &LayoutContext,
185 containing_block: &ContainingBlock,
186 base: &LayoutBoxBase,
187 fragment_layout_fn: impl FnOnce(&mut Self) -> BoxFragment,
188 ) -> BoxFragment {
189 let establishes_containing_block_for_absolutes = base
192 .style
193 .establishes_containing_block_for_absolute_descendants(base.base_fragment_info.flags);
194 if !establishes_containing_block_for_absolutes {
195 return fragment_layout_fn(self);
196 }
197
198 let mut new_context = PositioningContext::default();
199 let mut new_fragment = fragment_layout_fn(&mut new_context);
200
201 new_context.layout_collected_children(layout_context, &mut new_fragment);
204 self.append(new_context);
205
206 if base.style.clone_position() == Position::Relative {
207 new_fragment.content_rect.origin += relative_adjustement(&base.style, containing_block)
208 .to_physical_vector(containing_block.style.writing_mode)
209 }
210
211 new_fragment
212 }
213
214 fn take_boxes_for_fragment(
215 &mut self,
216 new_fragment: &BoxFragment,
217 boxes_to_layout_out: &mut Vec<HoistedAbsolutelyPositionedBox>,
218 boxes_to_continue_hoisting_out: &mut Vec<HoistedAbsolutelyPositionedBox>,
219 ) {
220 debug_assert!(
221 new_fragment
222 .style
223 .establishes_containing_block_for_absolute_descendants(new_fragment.base.flags)
224 );
225
226 if new_fragment
227 .style
228 .establishes_containing_block_for_all_descendants(new_fragment.base.flags)
229 {
230 boxes_to_layout_out.append(&mut self.absolutes);
231 return;
232 }
233
234 let (mut boxes_to_layout, mut boxes_to_continue_hoisting) = self
236 .absolutes
237 .drain(..)
238 .partition(|hoisted_box| hoisted_box.position() != Position::Fixed);
239 boxes_to_layout_out.append(&mut boxes_to_layout);
240 boxes_to_continue_hoisting_out.append(&mut boxes_to_continue_hoisting);
241 }
242
243 pub(crate) fn layout_collected_children(
246 &mut self,
247 layout_context: &LayoutContext,
248 new_fragment: &mut BoxFragment,
249 ) {
250 if self.absolutes.is_empty() {
251 return;
252 }
253
254 if !new_fragment
263 .style
264 .establishes_containing_block_for_absolute_descendants(new_fragment.base.flags)
265 {
266 return;
267 }
268
269 let padding_rect = PhysicalRect::new(
270 PhysicalPoint::origin(),
272 new_fragment.content_rect.size,
273 )
274 .outer_rect(new_fragment.padding);
275 let containing_block = DefiniteContainingBlock {
276 size: padding_rect
277 .size
278 .to_logical(new_fragment.style.writing_mode),
279 style: &new_fragment.style,
280 };
281
282 let mut fixed_position_boxes_to_hoist = Vec::new();
283 let mut boxes_to_layout = Vec::new();
284 self.take_boxes_for_fragment(
285 new_fragment,
286 &mut boxes_to_layout,
287 &mut fixed_position_boxes_to_hoist,
288 );
289
290 while !boxes_to_layout.is_empty() {
296 HoistedAbsolutelyPositionedBox::layout_many(
297 layout_context,
298 std::mem::take(&mut boxes_to_layout),
299 &mut new_fragment.children,
300 &mut self.absolutes,
301 &containing_block,
302 new_fragment.padding,
303 );
304
305 self.take_boxes_for_fragment(
306 new_fragment,
307 &mut boxes_to_layout,
308 &mut fixed_position_boxes_to_hoist,
309 );
310 }
311
312 self.absolutes = fixed_position_boxes_to_hoist;
316 }
317
318 pub(crate) fn push(&mut self, hoisted_box: HoistedAbsolutelyPositionedBox) {
319 debug_assert!(hoisted_box.position().is_absolutely_positioned());
320 self.absolutes.push(hoisted_box);
321 }
322
323 pub(crate) fn append(&mut self, mut other: Self) {
324 if other.absolutes.is_empty() {
325 return;
326 }
327 if self.absolutes.is_empty() {
328 self.absolutes = other.absolutes;
329 } else {
330 self.absolutes.append(&mut other.absolutes)
331 }
332 }
333
334 pub(crate) fn layout_initial_containing_block_children(
335 &mut self,
336 layout_context: &LayoutContext,
337 initial_containing_block: &DefiniteContainingBlock,
338 fragments: &mut Vec<Fragment>,
339 ) {
340 while !self.absolutes.is_empty() {
345 HoistedAbsolutelyPositionedBox::layout_many(
346 layout_context,
347 mem::take(&mut self.absolutes),
348 fragments,
349 &mut self.absolutes,
350 initial_containing_block,
351 Default::default(),
352 )
353 }
354 }
355
356 pub(crate) fn len(&self) -> PositioningContextLength {
358 PositioningContextLength(self.absolutes.len())
359 }
360
361 pub(crate) fn truncate(&mut self, length: &PositioningContextLength) {
365 self.absolutes.truncate(length.0)
366 }
367}
368
369#[derive(Clone, Copy, Debug, PartialEq)]
371pub(crate) struct PositioningContextLength(usize);
372
373impl Zero for PositioningContextLength {
374 fn zero() -> Self {
375 Self(0)
376 }
377
378 fn is_zero(&self) -> bool {
379 self.0.is_zero()
380 }
381}
382
383impl HoistedAbsolutelyPositionedBox {
384 fn position(&self) -> Position {
385 let position = self
386 .absolutely_positioned_box
387 .borrow()
388 .context
389 .style()
390 .clone_position();
391 assert!(position.is_absolutely_positioned());
392 position
393 }
394
395 pub(crate) fn layout_many(
396 layout_context: &LayoutContext,
397 mut boxes: Vec<Self>,
398 fragments: &mut Vec<Fragment>,
399 for_nearest_containing_block_for_all_descendants: &mut Vec<HoistedAbsolutelyPositionedBox>,
400 containing_block: &DefiniteContainingBlock,
401 containing_block_padding: PhysicalSides<Au>,
402 ) {
403 if layout_context.use_rayon {
404 let mut new_fragments = Vec::new();
405 let mut new_hoisted_boxes = Vec::new();
406
407 boxes
408 .par_iter_mut()
409 .map(|hoisted_box| {
410 let mut new_hoisted_boxes: Vec<HoistedAbsolutelyPositionedBox> = Vec::new();
411 let new_fragment = hoisted_box.layout(
412 layout_context,
413 &mut new_hoisted_boxes,
414 containing_block,
415 containing_block_padding,
416 );
417
418 hoisted_box.fragment.borrow_mut().fragment = Some(new_fragment.clone());
419 (new_fragment, new_hoisted_boxes)
420 })
421 .unzip_into_vecs(&mut new_fragments, &mut new_hoisted_boxes);
422
423 fragments.extend(new_fragments);
424 for_nearest_containing_block_for_all_descendants
425 .extend(new_hoisted_boxes.into_iter().flatten());
426 } else {
427 fragments.extend(boxes.iter_mut().map(|box_| {
428 let new_fragment = box_.layout(
429 layout_context,
430 for_nearest_containing_block_for_all_descendants,
431 containing_block,
432 containing_block_padding,
433 );
434
435 box_.fragment.borrow_mut().fragment = Some(new_fragment.clone());
436 new_fragment
437 }))
438 }
439 }
440
441 pub(crate) fn layout(
442 &mut self,
443 layout_context: &LayoutContext,
444 hoisted_absolutes_from_children: &mut Vec<HoistedAbsolutelyPositionedBox>,
445 containing_block: &DefiniteContainingBlock,
446 containing_block_padding: PhysicalSides<Au>,
447 ) -> Fragment {
448 let cbis = containing_block.size.inline;
449 let cbbs = containing_block.size.block;
450 let containing_block_writing_mode = containing_block.style.writing_mode;
451 let absolutely_positioned_box = self.absolutely_positioned_box.borrow();
452 let context = &absolutely_positioned_box.context;
453 let style = context.style().clone();
454 let layout_style = context.layout_style();
455 let ContentBoxSizesAndPBM {
456 content_box_sizes,
457 pbm,
458 ..
459 } = layout_style.content_box_sizes_and_padding_border_margin(&containing_block.into());
460 let containing_block = &containing_block.into();
461 let is_table = layout_style.is_table();
462 let is_table_or_replaced = is_table || context.is_replaced();
463 let preferred_aspect_ratio = context.preferred_aspect_ratio(&pbm.padding_border_sums);
464
465 let mut static_position_rect = self
469 .static_position_rect()
470 .outer_rect(-containing_block_padding);
471 static_position_rect.size = static_position_rect.size.max(PhysicalSize::zero());
472 let static_position_rect = static_position_rect.to_logical(containing_block);
473
474 let box_offset = style.box_offsets(containing_block.style.writing_mode);
475
476 let inline_box_offsets = box_offset.inline_sides().percentages_relative_to(cbis);
479 let inline_alignment = match inline_box_offsets.either_specified() {
480 true => style.clone_justify_self().0,
481 false => self.resolved_alignment.inline,
482 };
483
484 let inline_axis_solver = AbsoluteAxisSolver {
485 axis: Direction::Inline,
486 containing_size: cbis,
487 padding_border_sum: pbm.padding_border_sums.inline,
488 computed_margin_start: pbm.margin.inline_start,
489 computed_margin_end: pbm.margin.inline_end,
490 computed_sizes: content_box_sizes.inline,
491 avoid_negative_margin_start: true,
492 box_offsets: inline_box_offsets,
493 static_position_rect_axis: static_position_rect.get_axis(Direction::Inline),
494 alignment: inline_alignment,
495 flip_anchor: self.original_parent_writing_mode.is_bidi_ltr() !=
496 containing_block_writing_mode.is_bidi_ltr(),
497 is_table_or_replaced,
498 };
499
500 let block_box_offsets = box_offset.block_sides().percentages_relative_to(cbbs);
503 let block_alignment = match block_box_offsets.either_specified() {
504 true => style.clone_align_self().0,
505 false => self.resolved_alignment.block,
506 };
507 let block_axis_solver = AbsoluteAxisSolver {
508 axis: Direction::Block,
509 containing_size: cbbs,
510 padding_border_sum: pbm.padding_border_sums.block,
511 computed_margin_start: pbm.margin.block_start,
512 computed_margin_end: pbm.margin.block_end,
513 computed_sizes: content_box_sizes.block,
514 avoid_negative_margin_start: false,
515 box_offsets: block_box_offsets,
516 static_position_rect_axis: static_position_rect.get_axis(Direction::Block),
517 alignment: block_alignment,
518 flip_anchor: false,
519 is_table_or_replaced,
520 };
521
522 let block_automatic_size = block_axis_solver.automatic_size();
525 let block_stretch_size = Some(block_axis_solver.stretch_size());
526 let tentative_block_content_size =
527 context.tentative_block_content_size(preferred_aspect_ratio);
528 let tentative_block_size = if let Some(block_content_size) = tentative_block_content_size {
529 SizeConstraint::Definite(block_axis_solver.computed_sizes.resolve(
530 Direction::Block,
531 block_automatic_size,
532 Au::zero,
533 block_stretch_size,
534 || block_content_size,
535 is_table,
536 ))
537 } else {
538 block_axis_solver.computed_sizes.resolve_extrinsic(
539 block_automatic_size,
540 Au::zero(),
541 block_stretch_size,
542 )
543 };
544
545 let get_inline_content_size = || {
548 let constraint_space =
549 ConstraintSpace::new(tentative_block_size, &style, preferred_aspect_ratio);
550 context
551 .inline_content_sizes(layout_context, &constraint_space)
552 .sizes
553 };
554 let inline_size = inline_axis_solver.computed_sizes.resolve(
555 Direction::Inline,
556 inline_axis_solver.automatic_size(),
557 Au::zero,
558 Some(inline_axis_solver.stretch_size()),
559 get_inline_content_size,
560 is_table,
561 );
562
563 let containing_block_for_children = ContainingBlock {
564 size: ContainingBlockSize {
565 inline: inline_size,
566 block: tentative_block_size,
567 },
568 style: &style,
569 };
570 assert_eq!(
572 containing_block_writing_mode.is_horizontal(),
573 style.writing_mode.is_horizontal(),
574 "Mixed horizontal and vertical writing modes are not supported yet"
575 );
576
577 let mut positioning_context = PositioningContext::default();
578 let lazy_block_size = LazySize::new(
579 &block_axis_solver.computed_sizes,
580 Direction::Block,
581 block_automatic_size,
582 Au::zero,
583 block_stretch_size,
584 is_table,
585 );
586 let CacheableLayoutResult {
587 content_inline_size_for_table,
588 content_block_size,
589 fragments,
590 specific_layout_info,
591 ..
592 } = context.layout(
593 layout_context,
594 &mut positioning_context,
595 &containing_block_for_children,
596 containing_block,
597 preferred_aspect_ratio,
598 &lazy_block_size,
599 );
600
601 let content_size = LogicalVec2 {
602 inline: content_inline_size_for_table.unwrap_or(inline_size),
604
605 block: lazy_block_size.resolve(|| content_block_size),
607 };
608
609 let inline_margins = inline_axis_solver.solve_margins(content_size.inline);
610 let block_margins = block_axis_solver.solve_margins(content_size.block);
611 let margin = LogicalSides {
612 inline_start: inline_margins.start,
613 inline_end: inline_margins.end,
614 block_start: block_margins.start,
615 block_end: block_margins.end,
616 };
617
618 let pb = pbm.padding + pbm.border;
619 let margin_rect_size = content_size + pbm.padding_border_sums + margin.sum();
620 let inline_origin = inline_axis_solver.origin_for_margin_box(
621 margin_rect_size.inline,
622 style.writing_mode,
623 self.original_parent_writing_mode,
624 containing_block_writing_mode,
625 );
626 let block_origin = block_axis_solver.origin_for_margin_box(
627 margin_rect_size.block,
628 style.writing_mode,
629 self.original_parent_writing_mode,
630 containing_block_writing_mode,
631 );
632
633 let content_rect = LogicalRect {
634 start_corner: LogicalVec2 {
635 inline: inline_origin + margin.inline_start + pb.inline_start,
636 block: block_origin + margin.block_start + pb.block_start,
637 },
638 size: content_size,
639 };
640 let mut new_fragment = BoxFragment::new(
641 context.base_fragment_info(),
642 style,
643 fragments,
644 content_rect.as_physical(Some(containing_block)),
645 pbm.padding.to_physical(containing_block_writing_mode),
646 pbm.border.to_physical(containing_block_writing_mode),
647 margin.to_physical(containing_block_writing_mode),
648 specific_layout_info,
649 );
650
651 positioning_context.layout_collected_children(layout_context, &mut new_fragment);
655
656 positioning_context.adjust_static_position_of_hoisted_fragments_with_offset(
662 &new_fragment.content_rect.origin.to_vector(),
663 PositioningContextLength::zero(),
664 );
665
666 hoisted_absolutes_from_children.extend(positioning_context.absolutes);
667
668 let fragment = Fragment::Box(ArcRefCell::new(new_fragment));
669 context.base.set_fragment(fragment.clone());
670 fragment
671 }
672
673 fn static_position_rect(&self) -> PhysicalRect<Au> {
674 self.adjusted_static_position_rect
675 .unwrap_or_else(|| self.fragment.borrow().original_static_position_rect)
676 }
677
678 fn adjust_static_position_with_offset(&mut self, offset: &PhysicalVec<Au>) {
679 self.adjusted_static_position_rect = Some(self.static_position_rect().translate(*offset));
680 }
681}
682
683#[derive(Clone, Copy, Debug)]
684struct RectAxis {
685 origin: Au,
686 length: Au,
687}
688
689impl LogicalRect<Au> {
690 fn get_axis(&self, axis: Direction) -> RectAxis {
691 match axis {
692 Direction::Block => RectAxis {
693 origin: self.start_corner.block,
694 length: self.size.block,
695 },
696 Direction::Inline => RectAxis {
697 origin: self.start_corner.inline,
698 length: self.size.inline,
699 },
700 }
701 }
702}
703
704struct AbsoluteAxisSolver {
705 axis: Direction,
706 containing_size: Au,
707 padding_border_sum: Au,
708 computed_margin_start: AuOrAuto,
709 computed_margin_end: AuOrAuto,
710 computed_sizes: Sizes,
711 avoid_negative_margin_start: bool,
712 box_offsets: LogicalSides1D<AuOrAuto>,
713 static_position_rect_axis: RectAxis,
714 alignment: AlignFlags,
715 flip_anchor: bool,
716 is_table_or_replaced: bool,
717}
718
719impl AbsoluteAxisSolver {
720 fn inset_sum(&self) -> Au {
725 match (
726 self.box_offsets.start.non_auto(),
727 self.box_offsets.end.non_auto(),
728 ) {
729 (None, None) => {
730 if self.flip_anchor {
731 self.containing_size -
732 self.static_position_rect_axis.origin -
733 self.static_position_rect_axis.length
734 } else {
735 self.static_position_rect_axis.origin
736 }
737 },
738 (Some(start), None) => start,
739 (None, Some(end)) => end,
740 (Some(start), Some(end)) => start + end,
741 }
742 }
743
744 #[inline]
747 fn available_space(&self) -> Au {
748 Au::zero().max(self.containing_size - self.inset_sum())
749 }
750
751 #[inline]
752 fn automatic_size(&self) -> Size<Au> {
753 match self.alignment.value() {
754 _ if self.box_offsets.either_auto() => Size::FitContent,
755 AlignFlags::NORMAL | AlignFlags::AUTO if !self.is_table_or_replaced => Size::Stretch,
756 AlignFlags::STRETCH => Size::Stretch,
757 _ => Size::FitContent,
758 }
759 }
760
761 #[inline]
762 fn stretch_size(&self) -> Au {
763 Au::zero().max(
764 self.available_space() -
765 self.padding_border_sum -
766 self.computed_margin_start.auto_is(Au::zero) -
767 self.computed_margin_end.auto_is(Au::zero),
768 )
769 }
770
771 fn solve_margins(&self, size: Au) -> LogicalSides1D<Au> {
772 if self.box_offsets.either_auto() {
773 LogicalSides1D::new(
774 self.computed_margin_start.auto_is(Au::zero),
775 self.computed_margin_end.auto_is(Au::zero),
776 )
777 } else {
778 let free_space = self.available_space() - self.padding_border_sum - size;
779 match (self.computed_margin_start, self.computed_margin_end) {
780 (AuOrAuto::Auto, AuOrAuto::Auto) => {
781 if self.avoid_negative_margin_start && free_space < Au::zero() {
782 LogicalSides1D::new(Au::zero(), free_space)
783 } else {
784 let margin_start = free_space / 2;
785 LogicalSides1D::new(margin_start, free_space - margin_start)
786 }
787 },
788 (AuOrAuto::Auto, AuOrAuto::LengthPercentage(end)) => {
789 LogicalSides1D::new(free_space - end, end)
790 },
791 (AuOrAuto::LengthPercentage(start), AuOrAuto::Auto) => {
792 LogicalSides1D::new(start, free_space - start)
793 },
794 (AuOrAuto::LengthPercentage(start), AuOrAuto::LengthPercentage(end)) => {
795 LogicalSides1D::new(start, end)
796 },
797 }
798 }
799 }
800
801 fn origin_for_margin_box(
802 &self,
803 size: Au,
804 self_writing_mode: WritingMode,
805 original_parent_writing_mode: WritingMode,
806 containing_block_writing_mode: WritingMode,
807 ) -> Au {
808 let (alignment_container, alignment_container_writing_mode, flip_anchor, offsets) = match (
809 self.box_offsets.start.non_auto(),
810 self.box_offsets.end.non_auto(),
811 ) {
812 (None, None) => (
813 self.static_position_rect_axis,
814 original_parent_writing_mode,
815 self.flip_anchor,
816 None,
817 ),
818 (Some(start), Some(end)) => {
819 let alignment_container = RectAxis {
820 origin: start,
821 length: self.available_space(),
822 };
823 (
824 alignment_container,
825 containing_block_writing_mode,
826 false,
827 Some(LogicalSides1D { start, end }),
828 )
829 },
830 (Some(start), None) => return start,
834 (None, Some(end)) => {
835 return self.containing_size - size - end;
836 },
837 };
838
839 assert_eq!(
840 self_writing_mode.is_horizontal(),
841 original_parent_writing_mode.is_horizontal(),
842 "Mixed horizontal and vertical writing modes are not supported yet"
843 );
844 assert_eq!(
845 self_writing_mode.is_horizontal(),
846 containing_block_writing_mode.is_horizontal(),
847 "Mixed horizontal and vertical writing modes are not supported yet"
848 );
849 let self_value_matches_container = || {
850 self.axis == Direction::Block ||
851 self_writing_mode.is_bidi_ltr() == alignment_container_writing_mode.is_bidi_ltr()
852 };
853
854 let alignment = match self.alignment.value() {
859 AlignFlags::CENTER | AlignFlags::SPACE_AROUND | AlignFlags::SPACE_EVENLY => {
863 AlignFlags::CENTER
864 },
865 AlignFlags::SELF_START if self_value_matches_container() => AlignFlags::START,
867 AlignFlags::SELF_START => AlignFlags::END,
868 AlignFlags::SELF_END if self_value_matches_container() => AlignFlags::END,
870 AlignFlags::SELF_END => AlignFlags::START,
871 AlignFlags::LEFT if alignment_container_writing_mode.is_bidi_ltr() => AlignFlags::START,
873 AlignFlags::LEFT => AlignFlags::END,
874 AlignFlags::RIGHT if alignment_container_writing_mode.is_bidi_ltr() => AlignFlags::END,
876 AlignFlags::RIGHT => AlignFlags::START,
877 AlignFlags::END | AlignFlags::FLEX_END => AlignFlags::END,
880 _ => AlignFlags::START,
883 };
884
885 let alignment = match alignment {
886 AlignFlags::START if flip_anchor => AlignFlags::END,
887 AlignFlags::END if flip_anchor => AlignFlags::START,
888 alignment => alignment,
889 };
890
891 let free_space = alignment_container.length - size;
892 let flags = self.alignment.flags();
893 let alignment = if flags == AlignFlags::SAFE && free_space < Au::zero() {
894 AlignFlags::START
895 } else {
896 alignment
897 };
898
899 let origin = match alignment {
900 AlignFlags::START => alignment_container.origin,
901 AlignFlags::CENTER => alignment_container.origin + free_space / 2,
902 AlignFlags::END => alignment_container.origin + free_space,
903 _ => unreachable!(),
904 };
905 if matches!(flags, AlignFlags::SAFE | AlignFlags::UNSAFE) ||
906 matches!(
907 self.alignment,
908 AlignFlags::NORMAL | AlignFlags::AUTO | AlignFlags::STRETCH
909 )
910 {
911 return origin;
912 }
913 let Some(offsets) = offsets else {
914 return origin;
915 };
916
917 let min = Au::zero().min(offsets.start);
920 let max = self.containing_size - Au::zero().min(offsets.end) - size;
921 origin.clamp_between_extremums(min, Some(max))
922 }
923}
924
925pub(crate) fn relative_adjustement(
927 style: &ComputedValues,
928 containing_block: &ContainingBlock,
929) -> LogicalVec2<Au> {
930 let cbis = containing_block.size.inline;
934 let cbbs = containing_block.size.block;
935 let box_offsets = style
936 .box_offsets(containing_block.style.writing_mode)
937 .map_inline_and_block_axes(
938 |value| value.map(|value| value.to_used_value(cbis)),
939 |value| match cbbs {
940 SizeConstraint::Definite(cbbs) => value.map(|value| value.to_used_value(cbbs)),
941 _ => match value.non_auto().and_then(|value| value.to_length()) {
942 Some(value) => AuOrAuto::LengthPercentage(value.into()),
943 None => AuOrAuto::Auto,
944 },
945 },
946 );
947 fn adjust(start: AuOrAuto, end: AuOrAuto) -> Au {
948 match (start, end) {
949 (AuOrAuto::Auto, AuOrAuto::Auto) => Au::zero(),
950 (AuOrAuto::Auto, AuOrAuto::LengthPercentage(end)) => -end,
951 (AuOrAuto::LengthPercentage(start), _) => start,
952 }
953 }
954 LogicalVec2 {
955 inline: adjust(box_offsets.inline_start, box_offsets.inline_end),
956 block: adjust(box_offsets.block_start, box_offsets.block_end),
957 }
958}