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, LengthPercentageOrAuto, LogicalRect, LogicalSides, LogicalSides1D, LogicalVec2,
24 PhysicalPoint, PhysicalRect, PhysicalSides, PhysicalSize, PhysicalVec, ToLogical,
25 ToLogicalWithContainingBlock,
26};
27use crate::layout_box_base::{CacheableLayoutResult, LayoutBoxBase};
28use crate::sizing::{LazySize, Size, SizeConstraint, Sizes};
29use crate::style_ext::{Clamp, ComputedValuesExt, ContentBoxSizesAndPBM, DisplayInside};
30use crate::{
31 ConstraintSpace, ContainingBlock, ContainingBlockSize, DefiniteContainingBlock,
32 PropagatedBoxTreeData,
33};
34
35#[derive(Debug, MallocSizeOf)]
36pub(crate) struct AbsolutelyPositionedBox {
37 pub context: IndependentFormattingContext,
38}
39
40#[derive(Clone, MallocSizeOf)]
41pub(crate) struct HoistedAbsolutelyPositionedBox {
42 absolutely_positioned_box: ArcRefCell<AbsolutelyPositionedBox>,
43 pub fragment: ArcRefCell<HoistedSharedFragment>,
47 pub adjusted_static_position_rect: Option<PhysicalRect<Au>>,
57 pub resolved_alignment: LogicalVec2<AlignFlags>,
62 pub original_parent_writing_mode: WritingMode,
67}
68
69impl AbsolutelyPositionedBox {
70 pub fn new(context: IndependentFormattingContext) -> Self {
71 Self { context }
72 }
73
74 pub fn construct(
75 context: &LayoutContext,
76 node_info: &NodeAndStyleInfo,
77 display_inside: DisplayInside,
78 contents: Contents,
79 ) -> Self {
80 Self {
81 context: IndependentFormattingContext::construct(
82 context,
83 node_info,
84 display_inside,
85 contents,
86 PropagatedBoxTreeData::default(),
90 ),
91 }
92 }
93
94 pub(crate) fn to_hoisted(
95 absolutely_positioned_box: ArcRefCell<Self>,
96 static_position_rect: PhysicalRect<Au>,
97 resolved_alignment: LogicalVec2<AlignFlags>,
98 original_parent_writing_mode: WritingMode,
99 ) -> HoistedAbsolutelyPositionedBox {
100 HoistedAbsolutelyPositionedBox {
101 fragment: ArcRefCell::new(HoistedSharedFragment::new(static_position_rect)),
102 adjusted_static_position_rect: None,
103 resolved_alignment,
104 original_parent_writing_mode,
105 absolutely_positioned_box,
106 }
107 }
108}
109
110#[derive(Clone, Default, MallocSizeOf)]
111pub(crate) struct PositioningContext {
112 absolutes: Vec<HoistedAbsolutelyPositionedBox>,
113}
114
115impl PositioningContext {
116 #[inline]
117 pub(crate) fn new_for_layout_box_base(layout_box_base: &LayoutBoxBase) -> Option<Self> {
118 Self::new_for_style_and_fragment_flags(
119 &layout_box_base.style,
120 &layout_box_base.base_fragment_info.flags,
121 )
122 }
123
124 fn new_for_style_and_fragment_flags(
125 style: &ComputedValues,
126 flags: &FragmentFlags,
127 ) -> Option<Self> {
128 if style.establishes_containing_block_for_absolute_descendants(*flags) {
129 Some(Self::default())
130 } else {
131 None
132 }
133 }
134
135 pub(crate) fn adjust_static_position_of_hoisted_fragments(
148 &mut self,
149 parent_fragment: &Fragment,
150 index: PositioningContextLength,
151 ) {
152 let start_offset = match &parent_fragment {
153 Fragment::Box(fragment) | Fragment::Float(fragment) => {
154 fragment.borrow().content_rect.origin
155 },
156 Fragment::AbsoluteOrFixedPositioned(_) => return,
157 Fragment::Positioning(fragment) => fragment.borrow().rect.origin,
158 _ => unreachable!(),
159 };
160 self.adjust_static_position_of_hoisted_fragments_with_offset(
161 &start_offset.to_vector(),
162 index,
163 );
164 }
165
166 pub(crate) fn adjust_static_position_of_hoisted_fragments_with_offset(
168 &mut self,
169 offset: &PhysicalVec<Au>,
170 index: PositioningContextLength,
171 ) {
172 self.absolutes
173 .iter_mut()
174 .skip(index.0)
175 .for_each(|hoisted_box| {
176 hoisted_box.adjust_static_position_with_offset(offset);
177 })
178 }
179
180 pub(crate) fn layout_maybe_position_relative_fragment(
184 &mut self,
185 layout_context: &LayoutContext,
186 containing_block: &ContainingBlock,
187 base: &LayoutBoxBase,
188 fragment_layout_fn: impl FnOnce(&mut Self) -> BoxFragment,
189 ) -> BoxFragment {
190 let establishes_containing_block_for_absolutes = base
193 .style
194 .establishes_containing_block_for_absolute_descendants(base.base_fragment_info.flags);
195 if !establishes_containing_block_for_absolutes {
196 return fragment_layout_fn(self);
197 }
198
199 let mut new_context = PositioningContext::default();
200 let mut new_fragment = fragment_layout_fn(&mut new_context);
201
202 new_context.layout_collected_children(layout_context, &mut new_fragment);
205 self.append(new_context);
206
207 if base.style.clone_position() == Position::Relative {
208 new_fragment.content_rect.origin += relative_adjustement(&base.style, containing_block)
209 .to_physical_vector(containing_block.style.writing_mode)
210 }
211
212 new_fragment
213 }
214
215 fn take_boxes_for_fragment(
216 &mut self,
217 new_fragment: &BoxFragment,
218 boxes_to_layout_out: &mut Vec<HoistedAbsolutelyPositionedBox>,
219 boxes_to_continue_hoisting_out: &mut Vec<HoistedAbsolutelyPositionedBox>,
220 ) {
221 debug_assert!(
222 new_fragment
223 .style
224 .establishes_containing_block_for_absolute_descendants(new_fragment.base.flags)
225 );
226
227 if new_fragment
228 .style
229 .establishes_containing_block_for_all_descendants(new_fragment.base.flags)
230 {
231 boxes_to_layout_out.append(&mut self.absolutes);
232 return;
233 }
234
235 let (mut boxes_to_layout, mut boxes_to_continue_hoisting) = self
237 .absolutes
238 .drain(..)
239 .partition(|hoisted_box| hoisted_box.position() != Position::Fixed);
240 boxes_to_layout_out.append(&mut boxes_to_layout);
241 boxes_to_continue_hoisting_out.append(&mut boxes_to_continue_hoisting);
242 }
243
244 pub(crate) fn layout_collected_children(
247 &mut self,
248 layout_context: &LayoutContext,
249 new_fragment: &mut BoxFragment,
250 ) {
251 if self.absolutes.is_empty() {
252 return;
253 }
254
255 if !new_fragment
264 .style
265 .establishes_containing_block_for_absolute_descendants(new_fragment.base.flags)
266 {
267 return;
268 }
269
270 let padding_rect = PhysicalRect::new(
271 PhysicalPoint::origin(),
273 new_fragment.content_rect.size,
274 )
275 .outer_rect(new_fragment.padding);
276 let containing_block = DefiniteContainingBlock {
277 size: padding_rect
278 .size
279 .to_logical(new_fragment.style.writing_mode),
280 style: &new_fragment.style,
281 };
282
283 let mut fixed_position_boxes_to_hoist = Vec::new();
284 let mut boxes_to_layout = Vec::new();
285 self.take_boxes_for_fragment(
286 new_fragment,
287 &mut boxes_to_layout,
288 &mut fixed_position_boxes_to_hoist,
289 );
290
291 while !boxes_to_layout.is_empty() {
297 HoistedAbsolutelyPositionedBox::layout_many(
298 layout_context,
299 std::mem::take(&mut boxes_to_layout),
300 &mut new_fragment.children,
301 &mut self.absolutes,
302 &containing_block,
303 new_fragment.padding,
304 );
305
306 self.take_boxes_for_fragment(
307 new_fragment,
308 &mut boxes_to_layout,
309 &mut fixed_position_boxes_to_hoist,
310 );
311 }
312
313 self.absolutes = fixed_position_boxes_to_hoist;
317 }
318
319 pub(crate) fn push(&mut self, hoisted_box: HoistedAbsolutelyPositionedBox) {
320 debug_assert!(hoisted_box.position().is_absolutely_positioned());
321 self.absolutes.push(hoisted_box);
322 }
323
324 pub(crate) fn append(&mut self, mut other: Self) {
325 if other.absolutes.is_empty() {
326 return;
327 }
328 if self.absolutes.is_empty() {
329 self.absolutes = other.absolutes;
330 } else {
331 self.absolutes.append(&mut other.absolutes)
332 }
333 }
334
335 pub(crate) fn layout_initial_containing_block_children(
336 &mut self,
337 layout_context: &LayoutContext,
338 initial_containing_block: &DefiniteContainingBlock,
339 fragments: &mut Vec<Fragment>,
340 ) {
341 while !self.absolutes.is_empty() {
346 HoistedAbsolutelyPositionedBox::layout_many(
347 layout_context,
348 mem::take(&mut self.absolutes),
349 fragments,
350 &mut self.absolutes,
351 initial_containing_block,
352 Default::default(),
353 )
354 }
355 }
356
357 pub(crate) fn len(&self) -> PositioningContextLength {
359 PositioningContextLength(self.absolutes.len())
360 }
361
362 pub(crate) fn truncate(&mut self, length: &PositioningContextLength) {
366 self.absolutes.truncate(length.0)
367 }
368}
369
370#[derive(Clone, Copy, Debug, PartialEq)]
372pub(crate) struct PositioningContextLength(usize);
373
374impl Zero for PositioningContextLength {
375 fn zero() -> Self {
376 Self(0)
377 }
378
379 fn is_zero(&self) -> bool {
380 self.0.is_zero()
381 }
382}
383
384impl HoistedAbsolutelyPositionedBox {
385 fn position(&self) -> Position {
386 let position = self
387 .absolutely_positioned_box
388 .borrow()
389 .context
390 .style()
391 .clone_position();
392 assert!(position.is_absolutely_positioned());
393 position
394 }
395
396 pub(crate) fn layout_many(
397 layout_context: &LayoutContext,
398 mut boxes: Vec<Self>,
399 fragments: &mut Vec<Fragment>,
400 for_nearest_containing_block_for_all_descendants: &mut Vec<HoistedAbsolutelyPositionedBox>,
401 containing_block: &DefiniteContainingBlock,
402 containing_block_padding: PhysicalSides<Au>,
403 ) {
404 if layout_context.use_rayon {
405 let mut new_fragments = Vec::new();
406 let mut new_hoisted_boxes = Vec::new();
407
408 boxes
409 .par_iter_mut()
410 .map(|hoisted_box| {
411 let mut new_hoisted_boxes: Vec<HoistedAbsolutelyPositionedBox> = Vec::new();
412 let new_fragment = hoisted_box.layout(
413 layout_context,
414 &mut new_hoisted_boxes,
415 containing_block,
416 containing_block_padding,
417 );
418
419 hoisted_box.fragment.borrow_mut().fragment = Some(new_fragment.clone());
420 (new_fragment, new_hoisted_boxes)
421 })
422 .unzip_into_vecs(&mut new_fragments, &mut new_hoisted_boxes);
423
424 fragments.extend(new_fragments);
425 for_nearest_containing_block_for_all_descendants
426 .extend(new_hoisted_boxes.into_iter().flatten());
427 } else {
428 fragments.extend(boxes.iter_mut().map(|box_| {
429 let new_fragment = box_.layout(
430 layout_context,
431 for_nearest_containing_block_for_all_descendants,
432 containing_block,
433 containing_block_padding,
434 );
435
436 box_.fragment.borrow_mut().fragment = Some(new_fragment.clone());
437 new_fragment
438 }))
439 }
440 }
441
442 pub(crate) fn layout(
443 &mut self,
444 layout_context: &LayoutContext,
445 hoisted_absolutes_from_children: &mut Vec<HoistedAbsolutelyPositionedBox>,
446 containing_block: &DefiniteContainingBlock,
447 containing_block_padding: PhysicalSides<Au>,
448 ) -> Fragment {
449 let cbis = containing_block.size.inline;
450 let cbbs = containing_block.size.block;
451 let containing_block_writing_mode = containing_block.style.writing_mode;
452 let absolutely_positioned_box = self.absolutely_positioned_box.borrow();
453 let context = &absolutely_positioned_box.context;
454 let style = context.style().clone();
455 let layout_style = context.layout_style();
456 let ContentBoxSizesAndPBM {
457 content_box_sizes,
458 pbm,
459 ..
460 } = layout_style.content_box_sizes_and_padding_border_margin(&containing_block.into());
461 let containing_block = &containing_block.into();
462 let is_table = layout_style.is_table();
463 let is_table_or_replaced = is_table || context.is_replaced();
464 let preferred_aspect_ratio = context.preferred_aspect_ratio(&pbm.padding_border_sums);
465
466 let mut static_position_rect = self
470 .static_position_rect()
471 .outer_rect(-containing_block_padding);
472 static_position_rect.size = static_position_rect.size.max(PhysicalSize::zero());
473 let static_position_rect = static_position_rect.to_logical(containing_block);
474
475 let box_offset = style.box_offsets(containing_block.style.writing_mode);
476
477 let inline_box_offsets = box_offset.inline_sides();
480 let inline_alignment = match inline_box_offsets.either_specified() {
481 true => style.clone_justify_self().0.0,
482 false => self.resolved_alignment.inline,
483 };
484
485 let inline_axis_solver = AbsoluteAxisSolver {
486 axis: Direction::Inline,
487 containing_size: cbis,
488 padding_border_sum: pbm.padding_border_sums.inline,
489 computed_margin_start: pbm.margin.inline_start,
490 computed_margin_end: pbm.margin.inline_end,
491 computed_sizes: content_box_sizes.inline,
492 avoid_negative_margin_start: true,
493 box_offsets: inline_box_offsets,
494 static_position_rect_axis: static_position_rect.get_axis(Direction::Inline),
495 alignment: inline_alignment,
496 flip_anchor: self.original_parent_writing_mode.is_bidi_ltr() !=
497 containing_block_writing_mode.is_bidi_ltr(),
498 is_table_or_replaced,
499 };
500
501 let block_box_offsets = box_offset.block_sides();
504 let block_alignment = match block_box_offsets.either_specified() {
505 true => style.clone_align_self().0.0,
506 false => self.resolved_alignment.block,
507 };
508 let block_axis_solver = AbsoluteAxisSolver {
509 axis: Direction::Block,
510 containing_size: cbbs,
511 padding_border_sum: pbm.padding_border_sums.block,
512 computed_margin_start: pbm.margin.block_start,
513 computed_margin_end: pbm.margin.block_end,
514 computed_sizes: content_box_sizes.block,
515 avoid_negative_margin_start: false,
516 box_offsets: block_box_offsets,
517 static_position_rect_axis: static_position_rect.get_axis(Direction::Block),
518 alignment: block_alignment,
519 flip_anchor: false,
520 is_table_or_replaced,
521 };
522
523 let block_automatic_size = block_axis_solver.automatic_size();
526 let block_stretch_size = Some(block_axis_solver.stretch_size());
527 let tentative_block_content_size =
528 context.tentative_block_content_size(preferred_aspect_ratio);
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_axis_solver.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 CacheableLayoutResult {
588 content_inline_size_for_table,
589 content_block_size,
590 fragments,
591 specific_layout_info,
592 ..
593 } = context.layout(
594 layout_context,
595 &mut positioning_context,
596 &containing_block_for_children,
597 containing_block,
598 preferred_aspect_ratio,
599 &lazy_block_size,
600 );
601
602 let content_size = LogicalVec2 {
603 inline: content_inline_size_for_table.unwrap_or(inline_size),
605
606 block: lazy_block_size.resolve(|| content_block_size),
608 };
609
610 let inline_margins = inline_axis_solver.solve_margins(content_size.inline);
611 let block_margins = block_axis_solver.solve_margins(content_size.block);
612 let margin = LogicalSides {
613 inline_start: inline_margins.start,
614 inline_end: inline_margins.end,
615 block_start: block_margins.start,
616 block_end: block_margins.end,
617 };
618
619 let pb = pbm.padding + pbm.border;
620 let margin_rect_size = content_size + pbm.padding_border_sums + margin.sum();
621 let inline_origin = inline_axis_solver.origin_for_margin_box(
622 margin_rect_size.inline,
623 style.writing_mode,
624 self.original_parent_writing_mode,
625 containing_block_writing_mode,
626 );
627 let block_origin = block_axis_solver.origin_for_margin_box(
628 margin_rect_size.block,
629 style.writing_mode,
630 self.original_parent_writing_mode,
631 containing_block_writing_mode,
632 );
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 let mut new_fragment = BoxFragment::new(
642 context.base_fragment_info(),
643 style,
644 fragments,
645 content_rect.as_physical(Some(containing_block)),
646 pbm.padding.to_physical(containing_block_writing_mode),
647 pbm.border.to_physical(containing_block_writing_mode),
648 margin.to_physical(containing_block_writing_mode),
649 specific_layout_info,
650 );
651
652 positioning_context.layout_collected_children(layout_context, &mut new_fragment);
656
657 positioning_context.adjust_static_position_of_hoisted_fragments_with_offset(
663 &new_fragment.content_rect.origin.to_vector(),
664 PositioningContextLength::zero(),
665 );
666
667 hoisted_absolutes_from_children.extend(positioning_context.absolutes);
668
669 let fragment = Fragment::Box(ArcRefCell::new(new_fragment));
670 context.base.set_fragment(fragment.clone());
671 fragment
672 }
673
674 fn static_position_rect(&self) -> PhysicalRect<Au> {
675 self.adjusted_static_position_rect
676 .unwrap_or_else(|| self.fragment.borrow().original_static_position_rect)
677 }
678
679 fn adjust_static_position_with_offset(&mut self, offset: &PhysicalVec<Au>) {
680 self.adjusted_static_position_rect = Some(self.static_position_rect().translate(*offset));
681 }
682}
683
684#[derive(Clone, Copy, Debug)]
685struct RectAxis {
686 origin: Au,
687 length: Au,
688}
689
690impl LogicalRect<Au> {
691 fn get_axis(&self, axis: Direction) -> RectAxis {
692 match axis {
693 Direction::Block => RectAxis {
694 origin: self.start_corner.block,
695 length: self.size.block,
696 },
697 Direction::Inline => RectAxis {
698 origin: self.start_corner.inline,
699 length: self.size.inline,
700 },
701 }
702 }
703}
704
705struct AbsoluteAxisSolver<'a> {
706 axis: Direction,
707 containing_size: Au,
708 padding_border_sum: Au,
709 computed_margin_start: AuOrAuto,
710 computed_margin_end: AuOrAuto,
711 computed_sizes: Sizes,
712 avoid_negative_margin_start: bool,
713 box_offsets: LogicalSides1D<LengthPercentageOrAuto<'a>>,
714 static_position_rect_axis: RectAxis,
715 alignment: AlignFlags,
716 flip_anchor: bool,
717 is_table_or_replaced: bool,
718}
719
720impl AbsoluteAxisSolver<'_> {
721 fn inset_sum(&self) -> Au {
726 match (
727 self.box_offsets.start.non_auto(),
728 self.box_offsets.end.non_auto(),
729 ) {
730 (None, None) => {
731 if self.flip_anchor {
732 self.containing_size -
733 self.static_position_rect_axis.origin -
734 self.static_position_rect_axis.length
735 } else {
736 self.static_position_rect_axis.origin
737 }
738 },
739 (Some(start), None) => start.to_used_value(self.containing_size),
740 (None, Some(end)) => end.to_used_value(self.containing_size),
741 (Some(start), Some(end)) => {
742 start.to_used_value(self.containing_size) + end.to_used_value(self.containing_size)
743 },
744 }
745 }
746
747 #[inline]
748 fn automatic_size(&self) -> Size<Au> {
749 match self.alignment.value() {
750 _ if self.box_offsets.either_auto() => Size::FitContent,
751 AlignFlags::NORMAL | AlignFlags::AUTO if !self.is_table_or_replaced => Size::Stretch,
752 AlignFlags::STRETCH => Size::Stretch,
753 _ => Size::FitContent,
754 }
755 }
756
757 #[inline]
758 fn stretch_size(&self) -> Au {
759 Au::zero().max(
760 self.containing_size -
761 self.inset_sum() -
762 self.padding_border_sum -
763 self.computed_margin_start.auto_is(Au::zero) -
764 self.computed_margin_end.auto_is(Au::zero),
765 )
766 }
767
768 fn solve_margins(&self, size: Au) -> LogicalSides1D<Au> {
769 if self.box_offsets.either_auto() {
770 LogicalSides1D::new(
771 self.computed_margin_start.auto_is(Au::zero),
772 self.computed_margin_end.auto_is(Au::zero),
773 )
774 } else {
775 let free_space =
776 self.containing_size - self.inset_sum() - self.padding_border_sum - size;
777 match (self.computed_margin_start, self.computed_margin_end) {
778 (AuOrAuto::Auto, AuOrAuto::Auto) => {
779 if self.avoid_negative_margin_start && free_space < Au::zero() {
780 LogicalSides1D::new(Au::zero(), free_space)
781 } else {
782 let margin_start = free_space / 2;
783 LogicalSides1D::new(margin_start, free_space - margin_start)
784 }
785 },
786 (AuOrAuto::Auto, AuOrAuto::LengthPercentage(end)) => {
787 LogicalSides1D::new(free_space - end, end)
788 },
789 (AuOrAuto::LengthPercentage(start), AuOrAuto::Auto) => {
790 LogicalSides1D::new(start, free_space - start)
791 },
792 (AuOrAuto::LengthPercentage(start), AuOrAuto::LengthPercentage(end)) => {
793 LogicalSides1D::new(start, end)
794 },
795 }
796 }
797 }
798
799 fn origin_for_margin_box(
800 &self,
801 size: Au,
802 self_writing_mode: WritingMode,
803 original_parent_writing_mode: WritingMode,
804 containing_block_writing_mode: WritingMode,
805 ) -> Au {
806 let (alignment_container, alignment_container_writing_mode, flip_anchor, offsets) = match (
807 self.box_offsets.start.non_auto(),
808 self.box_offsets.end.non_auto(),
809 ) {
810 (None, None) => (
811 self.static_position_rect_axis,
812 original_parent_writing_mode,
813 self.flip_anchor,
814 None,
815 ),
816 (Some(start), Some(end)) => {
817 let offsets = LogicalSides1D {
818 start: start.to_used_value(self.containing_size),
819 end: end.to_used_value(self.containing_size),
820 };
821 let alignment_container = RectAxis {
822 origin: offsets.start,
823 length: self.containing_size - offsets.sum(),
824 };
825 (
826 alignment_container,
827 containing_block_writing_mode,
828 false,
829 Some(offsets),
830 )
831 },
832 (Some(start), None) => return start.to_used_value(self.containing_size),
836 (None, Some(end)) => {
837 return self.containing_size - size - end.to_used_value(self.containing_size);
838 },
839 };
840
841 assert_eq!(
842 self_writing_mode.is_horizontal(),
843 original_parent_writing_mode.is_horizontal(),
844 "Mixed horizontal and vertical writing modes are not supported yet"
845 );
846 assert_eq!(
847 self_writing_mode.is_horizontal(),
848 containing_block_writing_mode.is_horizontal(),
849 "Mixed horizontal and vertical writing modes are not supported yet"
850 );
851 let self_value_matches_container = || {
852 self.axis == Direction::Block ||
853 self_writing_mode.is_bidi_ltr() == alignment_container_writing_mode.is_bidi_ltr()
854 };
855
856 let alignment = match self.alignment.value() {
861 AlignFlags::CENTER | AlignFlags::SPACE_AROUND | AlignFlags::SPACE_EVENLY => {
865 AlignFlags::CENTER
866 },
867 AlignFlags::SELF_START if self_value_matches_container() => AlignFlags::START,
869 AlignFlags::SELF_START => AlignFlags::END,
870 AlignFlags::SELF_END if self_value_matches_container() => AlignFlags::END,
872 AlignFlags::SELF_END => AlignFlags::START,
873 AlignFlags::LEFT if alignment_container_writing_mode.is_bidi_ltr() => AlignFlags::START,
875 AlignFlags::LEFT => AlignFlags::END,
876 AlignFlags::RIGHT if alignment_container_writing_mode.is_bidi_ltr() => AlignFlags::END,
878 AlignFlags::RIGHT => AlignFlags::START,
879 AlignFlags::END | AlignFlags::FLEX_END => AlignFlags::END,
882 _ => AlignFlags::START,
885 };
886
887 let alignment = match alignment {
888 AlignFlags::START if flip_anchor => AlignFlags::END,
889 AlignFlags::END if flip_anchor => AlignFlags::START,
890 alignment => alignment,
891 };
892
893 let free_space = alignment_container.length - size;
894 let flags = self.alignment.flags();
895 let alignment = if flags == AlignFlags::SAFE && free_space < Au::zero() {
896 AlignFlags::START
897 } else {
898 alignment
899 };
900
901 let origin = match alignment {
902 AlignFlags::START => alignment_container.origin,
903 AlignFlags::CENTER => alignment_container.origin + free_space / 2,
904 AlignFlags::END => alignment_container.origin + free_space,
905 _ => unreachable!(),
906 };
907 if matches!(flags, AlignFlags::SAFE | AlignFlags::UNSAFE) ||
908 matches!(
909 self.alignment,
910 AlignFlags::NORMAL | AlignFlags::AUTO | AlignFlags::STRETCH
911 )
912 {
913 return origin;
914 }
915 let Some(offsets) = offsets else {
916 return origin;
917 };
918
919 let min = Au::zero().min(offsets.start);
922 let max = self.containing_size - Au::zero().min(offsets.end) - size;
923 origin.clamp_between_extremums(min, Some(max))
924 }
925}
926
927pub(crate) fn relative_adjustement(
929 style: &ComputedValues,
930 containing_block: &ContainingBlock,
931) -> LogicalVec2<Au> {
932 let cbis = containing_block.size.inline;
936 let cbbs = containing_block.size.block;
937 let box_offsets = style
938 .box_offsets(containing_block.style.writing_mode)
939 .map_inline_and_block_axes(
940 |value| value.map(|value| value.to_used_value(cbis)),
941 |value| match cbbs {
942 SizeConstraint::Definite(cbbs) => value.map(|value| value.to_used_value(cbbs)),
943 _ => match value.non_auto().and_then(|value| value.to_length()) {
944 Some(value) => AuOrAuto::LengthPercentage(value.into()),
945 None => AuOrAuto::Auto,
946 },
947 },
948 );
949 fn adjust(start: AuOrAuto, end: AuOrAuto) -> Au {
950 match (start, end) {
951 (AuOrAuto::Auto, AuOrAuto::Auto) => Au::zero(),
952 (AuOrAuto::Auto, AuOrAuto::LengthPercentage(end)) => -end,
953 (AuOrAuto::LengthPercentage(start), _) => start,
954 }
955 }
956 LogicalVec2 {
957 inline: adjust(box_offsets.inline_start, box_offsets.inline_end),
958 block: adjust(box_offsets.block_start, box_offsets.block_end),
959 }
960}