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::{IndependentFormattingContextLayoutResult, 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 forget_unhoisted_boxes(&mut self, fragment: &BoxFragment) {
210 let style = fragment.style();
211 debug_assert!(
212 style.establishes_containing_block_for_absolute_descendants(fragment.base.flags)
213 );
214 if style.establishes_containing_block_for_all_descendants(fragment.base.flags) {
215 self.absolutes.clear();
216 } else {
217 self.absolutes
218 .retain(|hoisted_box| hoisted_box.position() == Position::Fixed);
219 }
220 }
221
222 fn take_boxes_for_fragment(
223 &mut self,
224 new_fragment: &BoxFragment,
225 boxes_to_layout_out: &mut Vec<HoistedAbsolutelyPositionedBox>,
226 boxes_to_continue_hoisting_out: &mut Vec<HoistedAbsolutelyPositionedBox>,
227 ) {
228 let style = new_fragment.style();
229 debug_assert!(
230 style.establishes_containing_block_for_absolute_descendants(new_fragment.base.flags)
231 );
232
233 if style.establishes_containing_block_for_all_descendants(new_fragment.base.flags) {
234 boxes_to_layout_out.append(&mut self.absolutes);
235 return;
236 }
237
238 let (mut boxes_to_layout, mut boxes_to_continue_hoisting) = self
240 .absolutes
241 .drain(..)
242 .partition(|hoisted_box| hoisted_box.position() != Position::Fixed);
243 boxes_to_layout_out.append(&mut boxes_to_layout);
244 boxes_to_continue_hoisting_out.append(&mut boxes_to_continue_hoisting);
245 }
246
247 pub(crate) fn layout_collected_children(
250 &mut self,
251 layout_context: &LayoutContext,
252 new_fragment: &mut BoxFragment,
253 ) {
254 if self.absolutes.is_empty() {
255 return;
256 }
257
258 let style = new_fragment.style().clone();
267 if !style.establishes_containing_block_for_absolute_descendants(new_fragment.base.flags) {
268 return;
269 }
270
271 let padding_rect = PhysicalRect::new(
272 PhysicalPoint::origin(),
274 new_fragment.base.rect.size,
275 )
276 .outer_rect(new_fragment.padding);
277 let containing_block = DefiniteContainingBlock {
278 size: padding_rect.size.to_logical(style.writing_mode),
279 style: &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 inline_stretch_size = inline_axis_solver.stretch_size();
527 let tentative_block_content_size =
528 context.tentative_block_content_size(preferred_aspect_ratio, inline_stretch_size);
529 let tentative_block_size = if let Some(block_content_size) = tentative_block_content_size {
530 SizeConstraint::Definite(block_axis_solver.computed_sizes.resolve(
531 Direction::Block,
532 block_automatic_size,
533 Au::zero,
534 block_stretch_size,
535 || block_content_size,
536 is_table,
537 ))
538 } else {
539 block_axis_solver.computed_sizes.resolve_extrinsic(
540 block_automatic_size,
541 Au::zero(),
542 block_stretch_size,
543 )
544 };
545
546 let get_inline_content_size = || {
549 let constraint_space =
550 ConstraintSpace::new(tentative_block_size, &style, preferred_aspect_ratio);
551 context
552 .inline_content_sizes(layout_context, &constraint_space)
553 .sizes
554 };
555 let inline_size = inline_axis_solver.computed_sizes.resolve(
556 Direction::Inline,
557 inline_axis_solver.automatic_size(),
558 Au::zero,
559 Some(inline_stretch_size),
560 get_inline_content_size,
561 is_table,
562 );
563
564 let containing_block_for_children = ContainingBlock {
565 size: ContainingBlockSize {
566 inline: inline_size,
567 block: tentative_block_size,
568 },
569 style: &style,
570 };
571 assert_eq!(
573 containing_block_writing_mode.is_horizontal(),
574 style.writing_mode.is_horizontal(),
575 "Mixed horizontal and vertical writing modes are not supported yet"
576 );
577
578 let mut positioning_context = PositioningContext::default();
579 let lazy_block_size = LazySize::new(
580 &block_axis_solver.computed_sizes,
581 Direction::Block,
582 block_automatic_size,
583 Au::zero,
584 block_stretch_size,
585 is_table,
586 );
587 let (layout, is_cached) = context.layout_and_is_cached(
588 layout_context,
589 &mut positioning_context,
590 &containing_block_for_children,
591 containing_block,
592 preferred_aspect_ratio,
593 &lazy_block_size,
594 );
595 let IndependentFormattingContextLayoutResult {
596 content_inline_size_for_table,
597 content_block_size,
598 fragments,
599 specific_layout_info,
600 ..
601 } = layout;
602
603 let content_size = LogicalVec2 {
604 inline: content_inline_size_for_table.unwrap_or(inline_size),
606
607 block: lazy_block_size.resolve(|| content_block_size),
609 };
610
611 let inline_margins = inline_axis_solver.solve_margins(content_size.inline);
612 let block_margins = block_axis_solver.solve_margins(content_size.block);
613 let margin = LogicalSides {
614 inline_start: inline_margins.start,
615 inline_end: inline_margins.end,
616 block_start: block_margins.start,
617 block_end: block_margins.end,
618 };
619
620 let pb = pbm.padding + pbm.border;
621 let margin_rect_size = content_size + pbm.padding_border_sums + margin.sum();
622 let inline_origin = inline_axis_solver.origin_for_margin_box(
623 margin_rect_size.inline,
624 style.writing_mode,
625 self.original_parent_writing_mode,
626 containing_block_writing_mode,
627 );
628 let block_origin = block_axis_solver.origin_for_margin_box(
629 margin_rect_size.block,
630 style.writing_mode,
631 self.original_parent_writing_mode,
632 containing_block_writing_mode,
633 );
634 let content_rect = LogicalRect {
635 start_corner: LogicalVec2 {
636 inline: inline_origin + margin.inline_start + pb.inline_start,
637 block: block_origin + margin.block_start + pb.block_start,
638 },
639 size: content_size,
640 }
641 .as_physical(Some(containing_block));
642
643 let mut adjust_hoisted_boxes = |mut positioning_context: PositioningContext| {
644 positioning_context.adjust_static_position_of_hoisted_fragments_with_offset(
650 &content_rect.origin.to_vector(),
651 PositioningContextLength::zero(),
652 );
653
654 hoisted_absolutes_from_children.extend(positioning_context.absolutes);
655 };
656
657 if is_cached {
658 if let Some(Fragment::Box(old_fragment)) = context.base.fragments().first() {
659 let old_fragment_borrowed = old_fragment.borrow();
660 if content_rect == old_fragment_borrowed.content_rect() {
661 positioning_context.forget_unhoisted_boxes(&old_fragment_borrowed);
664 adjust_hoisted_boxes(positioning_context);
665 return Fragment::Box(old_fragment.clone());
666 }
667 }
668 }
669
670 let mut new_fragment = BoxFragment::new(
671 context.base_fragment_info(),
672 style,
673 fragments,
674 content_rect,
675 pbm.padding.to_physical(containing_block_writing_mode),
676 pbm.border.to_physical(containing_block_writing_mode),
677 margin.to_physical(containing_block_writing_mode),
678 specific_layout_info,
679 );
680
681 positioning_context.layout_collected_children(layout_context, &mut new_fragment);
685
686 adjust_hoisted_boxes(positioning_context);
687
688 let fragment = Fragment::Box(ArcRefCell::new(new_fragment));
689 context.base.set_fragment(fragment.clone());
690 fragment
691 }
692
693 fn static_position_rect(&self) -> PhysicalRect<Au> {
694 self.adjusted_static_position_rect
695 .unwrap_or_else(|| self.fragment.borrow().original_static_position_rect)
696 }
697
698 fn adjust_static_position_with_offset(&mut self, offset: &PhysicalVec<Au>) {
699 self.adjusted_static_position_rect = Some(self.static_position_rect().translate(*offset));
700 }
701}
702
703#[derive(Clone, Copy, Debug)]
704struct RectAxis {
705 origin: Au,
706 length: Au,
707}
708
709impl LogicalRect<Au> {
710 fn get_axis(&self, axis: Direction) -> RectAxis {
711 match axis {
712 Direction::Block => RectAxis {
713 origin: self.start_corner.block,
714 length: self.size.block,
715 },
716 Direction::Inline => RectAxis {
717 origin: self.start_corner.inline,
718 length: self.size.inline,
719 },
720 }
721 }
722}
723
724struct AbsoluteAxisSolver {
725 axis: Direction,
726 containing_size: Au,
727 padding_border_sum: Au,
728 computed_margin_start: AuOrAuto,
729 computed_margin_end: AuOrAuto,
730 computed_sizes: Sizes,
731 avoid_negative_margin_start: bool,
732 box_offsets: LogicalSides1D<AuOrAuto>,
733 static_position_rect_axis: RectAxis,
734 alignment: AlignFlags,
735 flip_anchor: bool,
736 is_table_or_replaced: bool,
737}
738
739impl AbsoluteAxisSolver {
740 fn inset_sum(&self) -> Au {
745 match (
746 self.box_offsets.start.non_auto(),
747 self.box_offsets.end.non_auto(),
748 ) {
749 (None, None) => {
750 if self.flip_anchor {
751 self.containing_size -
752 self.static_position_rect_axis.origin -
753 self.static_position_rect_axis.length
754 } else {
755 self.static_position_rect_axis.origin
756 }
757 },
758 (Some(start), None) => start,
759 (None, Some(end)) => end,
760 (Some(start), Some(end)) => start + end,
761 }
762 }
763
764 #[inline]
767 fn available_space(&self) -> Au {
768 Au::zero().max(self.containing_size - self.inset_sum())
769 }
770
771 #[inline]
772 fn automatic_size(&self) -> Size<Au> {
773 match self.alignment.value() {
774 _ if self.box_offsets.either_auto() => Size::FitContent,
775 AlignFlags::NORMAL | AlignFlags::AUTO if !self.is_table_or_replaced => Size::Stretch,
776 AlignFlags::STRETCH => Size::Stretch,
777 _ => Size::FitContent,
778 }
779 }
780
781 #[inline]
782 fn stretch_size(&self) -> Au {
783 Au::zero().max(
784 self.available_space() -
785 self.padding_border_sum -
786 self.computed_margin_start.auto_is(Au::zero) -
787 self.computed_margin_end.auto_is(Au::zero),
788 )
789 }
790
791 fn solve_margins(&self, size: Au) -> LogicalSides1D<Au> {
792 if self.box_offsets.either_auto() {
793 LogicalSides1D::new(
794 self.computed_margin_start.auto_is(Au::zero),
795 self.computed_margin_end.auto_is(Au::zero),
796 )
797 } else {
798 let free_space = self.available_space() - self.padding_border_sum - size;
799 match (self.computed_margin_start, self.computed_margin_end) {
800 (AuOrAuto::Auto, AuOrAuto::Auto) => {
801 if self.avoid_negative_margin_start && free_space < Au::zero() {
802 LogicalSides1D::new(Au::zero(), free_space)
803 } else {
804 let margin_start = free_space / 2;
805 LogicalSides1D::new(margin_start, free_space - margin_start)
806 }
807 },
808 (AuOrAuto::Auto, AuOrAuto::LengthPercentage(end)) => {
809 LogicalSides1D::new(free_space - end, end)
810 },
811 (AuOrAuto::LengthPercentage(start), AuOrAuto::Auto) => {
812 LogicalSides1D::new(start, free_space - start)
813 },
814 (AuOrAuto::LengthPercentage(start), AuOrAuto::LengthPercentage(end)) => {
815 LogicalSides1D::new(start, end)
816 },
817 }
818 }
819 }
820
821 fn origin_for_margin_box(
822 &self,
823 size: Au,
824 self_writing_mode: WritingMode,
825 original_parent_writing_mode: WritingMode,
826 containing_block_writing_mode: WritingMode,
827 ) -> Au {
828 let (alignment_container, alignment_container_writing_mode, flip_anchor, offsets) = match (
829 self.box_offsets.start.non_auto(),
830 self.box_offsets.end.non_auto(),
831 ) {
832 (None, None) => (
833 self.static_position_rect_axis,
834 original_parent_writing_mode,
835 self.flip_anchor,
836 None,
837 ),
838 (Some(start), Some(end)) => {
839 let alignment_container = RectAxis {
840 origin: start,
841 length: self.available_space(),
842 };
843 (
844 alignment_container,
845 containing_block_writing_mode,
846 false,
847 Some(LogicalSides1D { start, end }),
848 )
849 },
850 (Some(start), None) => return start,
854 (None, Some(end)) => {
855 return self.containing_size - size - end;
856 },
857 };
858
859 assert_eq!(
860 self_writing_mode.is_horizontal(),
861 original_parent_writing_mode.is_horizontal(),
862 "Mixed horizontal and vertical writing modes are not supported yet"
863 );
864 assert_eq!(
865 self_writing_mode.is_horizontal(),
866 containing_block_writing_mode.is_horizontal(),
867 "Mixed horizontal and vertical writing modes are not supported yet"
868 );
869 let self_value_matches_container = || {
870 self.axis == Direction::Block ||
871 self_writing_mode.is_bidi_ltr() == alignment_container_writing_mode.is_bidi_ltr()
872 };
873
874 let alignment = match self.alignment.value() {
879 AlignFlags::CENTER | AlignFlags::SPACE_AROUND | AlignFlags::SPACE_EVENLY => {
883 AlignFlags::CENTER
884 },
885 AlignFlags::SELF_START if self_value_matches_container() => AlignFlags::START,
887 AlignFlags::SELF_START => AlignFlags::END,
888 AlignFlags::SELF_END if self_value_matches_container() => AlignFlags::END,
890 AlignFlags::SELF_END => AlignFlags::START,
891 AlignFlags::LEFT if alignment_container_writing_mode.is_bidi_ltr() => AlignFlags::START,
893 AlignFlags::LEFT => AlignFlags::END,
894 AlignFlags::RIGHT if alignment_container_writing_mode.is_bidi_ltr() => AlignFlags::END,
896 AlignFlags::RIGHT => AlignFlags::START,
897 AlignFlags::END | AlignFlags::FLEX_END | AlignFlags::LAST_BASELINE => AlignFlags::END,
901 _ => AlignFlags::START,
905 };
906
907 let alignment = match alignment {
908 AlignFlags::START if flip_anchor => AlignFlags::END,
909 AlignFlags::END if flip_anchor => AlignFlags::START,
910 alignment => alignment,
911 };
912
913 let free_space = alignment_container.length - size;
914 let flags = self.alignment.flags();
915 let alignment = if flags == AlignFlags::SAFE && free_space < Au::zero() {
916 AlignFlags::START
917 } else {
918 alignment
919 };
920
921 let origin = match alignment {
922 AlignFlags::START => alignment_container.origin,
923 AlignFlags::CENTER => alignment_container.origin + free_space / 2,
924 AlignFlags::END => alignment_container.origin + free_space,
925 _ => unreachable!(),
926 };
927 if matches!(flags, AlignFlags::SAFE | AlignFlags::UNSAFE) ||
928 matches!(
929 self.alignment,
930 AlignFlags::NORMAL | AlignFlags::AUTO | AlignFlags::STRETCH
931 )
932 {
933 return origin;
934 }
935 let Some(offsets) = offsets else {
936 return origin;
937 };
938
939 let min = Au::zero().min(offsets.start);
942 let max = self.containing_size - Au::zero().min(offsets.end) - size;
943 origin.clamp_between_extremums(min, Some(max))
944 }
945}
946
947pub(crate) fn relative_adjustement(
949 style: &ComputedValues,
950 containing_block: &ContainingBlock,
951) -> LogicalVec2<Au> {
952 let cbis = containing_block.size.inline;
956 let cbbs = containing_block.size.block;
957 let box_offsets = style
958 .box_offsets(containing_block.style.writing_mode)
959 .map_inline_and_block_axes(
960 |value| value.map(|value| value.to_used_value(cbis)),
961 |value| match cbbs {
962 SizeConstraint::Definite(cbbs) => value.map(|value| value.to_used_value(cbbs)),
963 _ => match value.non_auto().and_then(|value| value.to_length()) {
964 Some(value) => AuOrAuto::LengthPercentage(value.into()),
965 None => AuOrAuto::Auto,
966 },
967 },
968 );
969 fn adjust(start: AuOrAuto, end: AuOrAuto) -> Au {
970 match (start, end) {
971 (AuOrAuto::Auto, AuOrAuto::Auto) => Au::zero(),
972 (AuOrAuto::Auto, AuOrAuto::LengthPercentage(end)) => -end,
973 (AuOrAuto::LengthPercentage(start), _) => start,
974 }
975 }
976 LogicalVec2 {
977 inline: adjust(box_offsets.inline_start, box_offsets.inline_end),
978 block: adjust(box_offsets.block_start, box_offsets.block_end),
979 }
980}