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
44 pub fragment: ArcRefCell<HoistedSharedFragment>,
48}
49
50impl AbsolutelyPositionedBox {
51 pub fn new(context: IndependentFormattingContext) -> Self {
52 Self { context }
53 }
54
55 pub fn construct(
56 context: &LayoutContext,
57 node_info: &NodeAndStyleInfo,
58 display_inside: DisplayInside,
59 contents: Contents,
60 ) -> Self {
61 Self {
62 context: IndependentFormattingContext::construct(
63 context,
64 node_info,
65 display_inside,
66 contents,
67 PropagatedBoxTreeData::default(),
71 ),
72 }
73 }
74
75 pub(crate) fn to_hoisted(
76 absolutely_positioned_box: ArcRefCell<Self>,
77 static_position_rectangle: PhysicalRect<Au>,
78 resolved_alignment: LogicalVec2<AlignFlags>,
79 original_parent_writing_mode: WritingMode,
80 ) -> HoistedAbsolutelyPositionedBox {
81 HoistedAbsolutelyPositionedBox {
82 fragment: ArcRefCell::new(HoistedSharedFragment::new(
83 static_position_rectangle,
84 resolved_alignment,
85 original_parent_writing_mode,
86 )),
87 absolutely_positioned_box,
88 }
89 }
90}
91
92#[derive(Clone, Default, MallocSizeOf)]
93pub(crate) struct PositioningContext {
94 absolutes: Vec<HoistedAbsolutelyPositionedBox>,
95}
96
97impl PositioningContext {
98 #[inline]
99 pub(crate) fn new_for_layout_box_base(layout_box_base: &LayoutBoxBase) -> Option<Self> {
100 Self::new_for_style_and_fragment_flags(
101 &layout_box_base.style,
102 &layout_box_base.base_fragment_info.flags,
103 )
104 }
105
106 fn new_for_style_and_fragment_flags(
107 style: &ComputedValues,
108 flags: &FragmentFlags,
109 ) -> Option<Self> {
110 if style.establishes_containing_block_for_absolute_descendants(*flags) {
111 Some(Self::default())
112 } else {
113 None
114 }
115 }
116
117 pub(crate) fn adjust_static_position_of_hoisted_fragments(
130 &mut self,
131 parent_fragment: &Fragment,
132 index: PositioningContextLength,
133 ) {
134 let start_offset = match &parent_fragment {
135 Fragment::Box(fragment) | Fragment::Float(fragment) => {
136 fragment.borrow().content_rect.origin
137 },
138 Fragment::AbsoluteOrFixedPositioned(_) => return,
139 Fragment::Positioning(fragment) => fragment.borrow().rect.origin,
140 _ => unreachable!(),
141 };
142 self.adjust_static_position_of_hoisted_fragments_with_offset(
143 &start_offset.to_vector(),
144 index,
145 );
146 }
147
148 pub(crate) fn adjust_static_position_of_hoisted_fragments_with_offset(
150 &mut self,
151 offset: &PhysicalVec<Au>,
152 index: PositioningContextLength,
153 ) {
154 self.absolutes
155 .iter_mut()
156 .skip(index.0)
157 .for_each(|hoisted_fragment| {
158 hoisted_fragment
159 .fragment
160 .borrow_mut()
161 .adjust_offsets(offset)
162 })
163 }
164
165 pub(crate) fn layout_maybe_position_relative_fragment(
169 &mut self,
170 layout_context: &LayoutContext,
171 containing_block: &ContainingBlock,
172 base: &LayoutBoxBase,
173 fragment_layout_fn: impl FnOnce(&mut Self) -> BoxFragment,
174 ) -> BoxFragment {
175 let establishes_containing_block_for_absolutes = base
178 .style
179 .establishes_containing_block_for_absolute_descendants(base.base_fragment_info.flags);
180 if !establishes_containing_block_for_absolutes {
181 return fragment_layout_fn(self);
182 }
183
184 let mut new_context = PositioningContext::default();
185 let mut new_fragment = fragment_layout_fn(&mut new_context);
186
187 new_context.layout_collected_children(layout_context, &mut new_fragment);
190 self.append(new_context);
191
192 if base.style.clone_position() == Position::Relative {
193 new_fragment.content_rect.origin += relative_adjustement(&base.style, containing_block)
194 .to_physical_vector(containing_block.style.writing_mode)
195 }
196
197 new_fragment
198 }
199
200 fn take_boxes_for_fragment(
201 &mut self,
202 new_fragment: &BoxFragment,
203 boxes_to_layout_out: &mut Vec<HoistedAbsolutelyPositionedBox>,
204 boxes_to_continue_hoisting_out: &mut Vec<HoistedAbsolutelyPositionedBox>,
205 ) {
206 debug_assert!(
207 new_fragment
208 .style
209 .establishes_containing_block_for_absolute_descendants(new_fragment.base.flags)
210 );
211
212 if new_fragment
213 .style
214 .establishes_containing_block_for_all_descendants(new_fragment.base.flags)
215 {
216 boxes_to_layout_out.append(&mut self.absolutes);
217 return;
218 }
219
220 let (mut boxes_to_layout, mut boxes_to_continue_hoisting) = self
222 .absolutes
223 .drain(..)
224 .partition(|hoisted_box| hoisted_box.position() != Position::Fixed);
225 boxes_to_layout_out.append(&mut boxes_to_layout);
226 boxes_to_continue_hoisting_out.append(&mut boxes_to_continue_hoisting);
227 }
228
229 pub(crate) fn layout_collected_children(
232 &mut self,
233 layout_context: &LayoutContext,
234 new_fragment: &mut BoxFragment,
235 ) {
236 if self.absolutes.is_empty() {
237 return;
238 }
239
240 if !new_fragment
249 .style
250 .establishes_containing_block_for_absolute_descendants(new_fragment.base.flags)
251 {
252 return;
253 }
254
255 let padding_rect = PhysicalRect::new(
256 PhysicalPoint::origin(),
258 new_fragment.content_rect.size,
259 )
260 .outer_rect(new_fragment.padding);
261 let containing_block = DefiniteContainingBlock {
262 size: padding_rect
263 .size
264 .to_logical(new_fragment.style.writing_mode),
265 style: &new_fragment.style,
266 };
267
268 let mut fixed_position_boxes_to_hoist = Vec::new();
269 let mut boxes_to_layout = Vec::new();
270 self.take_boxes_for_fragment(
271 new_fragment,
272 &mut boxes_to_layout,
273 &mut fixed_position_boxes_to_hoist,
274 );
275
276 while !boxes_to_layout.is_empty() {
282 HoistedAbsolutelyPositionedBox::layout_many(
283 layout_context,
284 std::mem::take(&mut boxes_to_layout),
285 &mut new_fragment.children,
286 &mut self.absolutes,
287 &containing_block,
288 new_fragment.padding,
289 );
290
291 self.take_boxes_for_fragment(
292 new_fragment,
293 &mut boxes_to_layout,
294 &mut fixed_position_boxes_to_hoist,
295 );
296 }
297
298 self.absolutes = fixed_position_boxes_to_hoist;
302 }
303
304 pub(crate) fn push(&mut self, hoisted_box: HoistedAbsolutelyPositionedBox) {
305 debug_assert!(hoisted_box.position().is_absolutely_positioned());
306 self.absolutes.push(hoisted_box);
307 }
308
309 pub(crate) fn append(&mut self, mut other: Self) {
310 if other.absolutes.is_empty() {
311 return;
312 }
313 if self.absolutes.is_empty() {
314 self.absolutes = other.absolutes;
315 } else {
316 self.absolutes.append(&mut other.absolutes)
317 }
318 }
319
320 pub(crate) fn layout_initial_containing_block_children(
321 &mut self,
322 layout_context: &LayoutContext,
323 initial_containing_block: &DefiniteContainingBlock,
324 fragments: &mut Vec<Fragment>,
325 ) {
326 while !self.absolutes.is_empty() {
331 HoistedAbsolutelyPositionedBox::layout_many(
332 layout_context,
333 mem::take(&mut self.absolutes),
334 fragments,
335 &mut self.absolutes,
336 initial_containing_block,
337 Default::default(),
338 )
339 }
340 }
341
342 pub(crate) fn len(&self) -> PositioningContextLength {
344 PositioningContextLength(self.absolutes.len())
345 }
346
347 pub(crate) fn truncate(&mut self, length: &PositioningContextLength) {
351 self.absolutes.truncate(length.0)
352 }
353}
354
355#[derive(Clone, Copy, Debug, PartialEq)]
357pub(crate) struct PositioningContextLength(usize);
358
359impl Zero for PositioningContextLength {
360 fn zero() -> Self {
361 Self(0)
362 }
363
364 fn is_zero(&self) -> bool {
365 self.0.is_zero()
366 }
367}
368
369impl HoistedAbsolutelyPositionedBox {
370 fn position(&self) -> Position {
371 let position = self
372 .absolutely_positioned_box
373 .borrow()
374 .context
375 .style()
376 .clone_position();
377 assert!(position.is_absolutely_positioned());
378 position
379 }
380
381 pub(crate) fn layout_many(
382 layout_context: &LayoutContext,
383 mut boxes: Vec<Self>,
384 fragments: &mut Vec<Fragment>,
385 for_nearest_containing_block_for_all_descendants: &mut Vec<HoistedAbsolutelyPositionedBox>,
386 containing_block: &DefiniteContainingBlock,
387 containing_block_padding: PhysicalSides<Au>,
388 ) {
389 if layout_context.use_rayon {
390 let mut new_fragments = Vec::new();
391 let mut new_hoisted_boxes = Vec::new();
392
393 boxes
394 .par_iter_mut()
395 .map(|hoisted_box| {
396 let mut new_hoisted_boxes: Vec<HoistedAbsolutelyPositionedBox> = Vec::new();
397 let new_fragment = hoisted_box.layout(
398 layout_context,
399 &mut new_hoisted_boxes,
400 containing_block,
401 containing_block_padding,
402 );
403
404 hoisted_box.fragment.borrow_mut().fragment = Some(new_fragment.clone());
405 (new_fragment, new_hoisted_boxes)
406 })
407 .unzip_into_vecs(&mut new_fragments, &mut new_hoisted_boxes);
408
409 fragments.extend(new_fragments);
410 for_nearest_containing_block_for_all_descendants
411 .extend(new_hoisted_boxes.into_iter().flatten());
412 } else {
413 fragments.extend(boxes.iter_mut().map(|box_| {
414 let new_fragment = box_.layout(
415 layout_context,
416 for_nearest_containing_block_for_all_descendants,
417 containing_block,
418 containing_block_padding,
419 );
420
421 box_.fragment.borrow_mut().fragment = Some(new_fragment.clone());
422 new_fragment
423 }))
424 }
425 }
426
427 pub(crate) fn layout(
428 &mut self,
429 layout_context: &LayoutContext,
430 hoisted_absolutes_from_children: &mut Vec<HoistedAbsolutelyPositionedBox>,
431 containing_block: &DefiniteContainingBlock,
432 containing_block_padding: PhysicalSides<Au>,
433 ) -> Fragment {
434 let cbis = containing_block.size.inline;
435 let cbbs = containing_block.size.block;
436 let containing_block_writing_mode = containing_block.style.writing_mode;
437 let absolutely_positioned_box = self.absolutely_positioned_box.borrow();
438 let context = &absolutely_positioned_box.context;
439 let style = context.style().clone();
440 let layout_style = context.layout_style();
441 let ContentBoxSizesAndPBM {
442 content_box_sizes,
443 pbm,
444 ..
445 } = layout_style.content_box_sizes_and_padding_border_margin(&containing_block.into());
446 let containing_block = &containing_block.into();
447 let is_table = layout_style.is_table();
448 let is_table_or_replaced = is_table || context.is_replaced();
449 let preferred_aspect_ratio = context.preferred_aspect_ratio(&pbm.padding_border_sums);
450 let shared_fragment = self.fragment.borrow();
451
452 let mut static_position_rect = shared_fragment
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();
466 let inline_alignment = match inline_box_offsets.either_specified() {
467 true => style.clone_justify_self().0.0,
468 false => shared_fragment.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: shared_fragment.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();
490 let block_alignment = match block_box_offsets.either_specified() {
491 true => style.clone_align_self().0.0,
492 false => shared_fragment.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 tentative_block_content_size =
514 context.tentative_block_content_size(preferred_aspect_ratio);
515 let tentative_block_size = if let Some(block_content_size) = tentative_block_content_size {
516 SizeConstraint::Definite(block_axis_solver.computed_sizes.resolve(
517 Direction::Block,
518 block_automatic_size,
519 Au::zero,
520 block_stretch_size,
521 || block_content_size,
522 is_table,
523 ))
524 } else {
525 block_axis_solver.computed_sizes.resolve_extrinsic(
526 block_automatic_size,
527 Au::zero(),
528 block_stretch_size,
529 )
530 };
531
532 let get_inline_content_size = || {
535 let constraint_space = ConstraintSpace::new(
536 tentative_block_size,
537 style.writing_mode,
538 preferred_aspect_ratio,
539 );
540 context
541 .inline_content_sizes(layout_context, &constraint_space)
542 .sizes
543 };
544 let inline_size = inline_axis_solver.computed_sizes.resolve(
545 Direction::Inline,
546 inline_axis_solver.automatic_size(),
547 Au::zero,
548 Some(inline_axis_solver.stretch_size()),
549 get_inline_content_size,
550 is_table,
551 );
552
553 let containing_block_for_children = ContainingBlock {
554 size: ContainingBlockSize {
555 inline: inline_size,
556 block: tentative_block_size,
557 },
558 style: &style,
559 };
560 assert_eq!(
562 containing_block_writing_mode.is_horizontal(),
563 style.writing_mode.is_horizontal(),
564 "Mixed horizontal and vertical writing modes are not supported yet"
565 );
566
567 let mut positioning_context = PositioningContext::default();
568 let lazy_block_size = LazySize::new(
569 &block_axis_solver.computed_sizes,
570 Direction::Block,
571 block_automatic_size,
572 Au::zero,
573 block_stretch_size,
574 is_table,
575 );
576 let CacheableLayoutResult {
577 content_inline_size_for_table,
578 content_block_size,
579 fragments,
580 specific_layout_info,
581 ..
582 } = context.layout(
583 layout_context,
584 &mut positioning_context,
585 &containing_block_for_children,
586 containing_block,
587 preferred_aspect_ratio,
588 &lazy_block_size,
589 );
590
591 let content_size = LogicalVec2 {
592 inline: content_inline_size_for_table.unwrap_or(inline_size),
594
595 block: lazy_block_size.resolve(|| content_block_size),
597 };
598
599 let inline_margins = inline_axis_solver.solve_margins(content_size.inline);
600 let block_margins = block_axis_solver.solve_margins(content_size.block);
601 let margin = LogicalSides {
602 inline_start: inline_margins.start,
603 inline_end: inline_margins.end,
604 block_start: block_margins.start,
605 block_end: block_margins.end,
606 };
607
608 let pb = pbm.padding + pbm.border;
609 let margin_rect_size = content_size + pbm.padding_border_sums + margin.sum();
610 let inline_origin = inline_axis_solver.origin_for_margin_box(
611 margin_rect_size.inline,
612 style.writing_mode,
613 shared_fragment.original_parent_writing_mode,
614 containing_block_writing_mode,
615 );
616 let block_origin = block_axis_solver.origin_for_margin_box(
617 margin_rect_size.block,
618 style.writing_mode,
619 shared_fragment.original_parent_writing_mode,
620 containing_block_writing_mode,
621 );
622
623 let content_rect = LogicalRect {
624 start_corner: LogicalVec2 {
625 inline: inline_origin + margin.inline_start + pb.inline_start,
626 block: block_origin + margin.block_start + pb.block_start,
627 },
628 size: content_size,
629 };
630 let mut new_fragment = BoxFragment::new(
631 context.base_fragment_info(),
632 style,
633 fragments,
634 content_rect.as_physical(Some(containing_block)),
635 pbm.padding.to_physical(containing_block_writing_mode),
636 pbm.border.to_physical(containing_block_writing_mode),
637 margin.to_physical(containing_block_writing_mode),
638 specific_layout_info,
639 );
640
641 positioning_context.layout_collected_children(layout_context, &mut new_fragment);
645
646 positioning_context.adjust_static_position_of_hoisted_fragments_with_offset(
652 &new_fragment.content_rect.origin.to_vector(),
653 PositioningContextLength::zero(),
654 );
655
656 hoisted_absolutes_from_children.extend(positioning_context.absolutes);
657
658 let fragment = Fragment::Box(ArcRefCell::new(new_fragment));
659 context.base.set_fragment(fragment.clone());
660 fragment
661 }
662}
663
664#[derive(Clone, Copy, Debug)]
665struct RectAxis {
666 origin: Au,
667 length: Au,
668}
669
670impl LogicalRect<Au> {
671 fn get_axis(&self, axis: Direction) -> RectAxis {
672 match axis {
673 Direction::Block => RectAxis {
674 origin: self.start_corner.block,
675 length: self.size.block,
676 },
677 Direction::Inline => RectAxis {
678 origin: self.start_corner.inline,
679 length: self.size.inline,
680 },
681 }
682 }
683}
684
685struct AbsoluteAxisSolver<'a> {
686 axis: Direction,
687 containing_size: Au,
688 padding_border_sum: Au,
689 computed_margin_start: AuOrAuto,
690 computed_margin_end: AuOrAuto,
691 computed_sizes: Sizes,
692 avoid_negative_margin_start: bool,
693 box_offsets: LogicalSides1D<LengthPercentageOrAuto<'a>>,
694 static_position_rect_axis: RectAxis,
695 alignment: AlignFlags,
696 flip_anchor: bool,
697 is_table_or_replaced: bool,
698}
699
700impl AbsoluteAxisSolver<'_> {
701 fn inset_sum(&self) -> Au {
706 match (
707 self.box_offsets.start.non_auto(),
708 self.box_offsets.end.non_auto(),
709 ) {
710 (None, None) => {
711 if self.flip_anchor {
712 self.containing_size -
713 self.static_position_rect_axis.origin -
714 self.static_position_rect_axis.length
715 } else {
716 self.static_position_rect_axis.origin
717 }
718 },
719 (Some(start), None) => start.to_used_value(self.containing_size),
720 (None, Some(end)) => end.to_used_value(self.containing_size),
721 (Some(start), Some(end)) => {
722 start.to_used_value(self.containing_size) + end.to_used_value(self.containing_size)
723 },
724 }
725 }
726
727 #[inline]
728 fn automatic_size(&self) -> Size<Au> {
729 match self.alignment.value() {
730 _ if self.box_offsets.either_auto() => Size::FitContent,
731 AlignFlags::NORMAL | AlignFlags::AUTO if !self.is_table_or_replaced => Size::Stretch,
732 AlignFlags::STRETCH => Size::Stretch,
733 _ => Size::FitContent,
734 }
735 }
736
737 #[inline]
738 fn stretch_size(&self) -> Au {
739 Au::zero().max(
740 self.containing_size -
741 self.inset_sum() -
742 self.padding_border_sum -
743 self.computed_margin_start.auto_is(Au::zero) -
744 self.computed_margin_end.auto_is(Au::zero),
745 )
746 }
747
748 fn solve_margins(&self, size: Au) -> LogicalSides1D<Au> {
749 if self.box_offsets.either_auto() {
750 LogicalSides1D::new(
751 self.computed_margin_start.auto_is(Au::zero),
752 self.computed_margin_end.auto_is(Au::zero),
753 )
754 } else {
755 let free_space =
756 self.containing_size - self.inset_sum() - self.padding_border_sum - size;
757 match (self.computed_margin_start, self.computed_margin_end) {
758 (AuOrAuto::Auto, AuOrAuto::Auto) => {
759 if self.avoid_negative_margin_start && free_space < Au::zero() {
760 LogicalSides1D::new(Au::zero(), free_space)
761 } else {
762 let margin_start = free_space / 2;
763 LogicalSides1D::new(margin_start, free_space - margin_start)
764 }
765 },
766 (AuOrAuto::Auto, AuOrAuto::LengthPercentage(end)) => {
767 LogicalSides1D::new(free_space - end, end)
768 },
769 (AuOrAuto::LengthPercentage(start), AuOrAuto::Auto) => {
770 LogicalSides1D::new(start, free_space - start)
771 },
772 (AuOrAuto::LengthPercentage(start), AuOrAuto::LengthPercentage(end)) => {
773 LogicalSides1D::new(start, end)
774 },
775 }
776 }
777 }
778
779 fn origin_for_margin_box(
780 &self,
781 size: Au,
782 self_writing_mode: WritingMode,
783 original_parent_writing_mode: WritingMode,
784 containing_block_writing_mode: WritingMode,
785 ) -> Au {
786 let (alignment_container, alignment_container_writing_mode, flip_anchor, offsets) = match (
787 self.box_offsets.start.non_auto(),
788 self.box_offsets.end.non_auto(),
789 ) {
790 (None, None) => (
791 self.static_position_rect_axis,
792 original_parent_writing_mode,
793 self.flip_anchor,
794 None,
795 ),
796 (Some(start), Some(end)) => {
797 let offsets = LogicalSides1D {
798 start: start.to_used_value(self.containing_size),
799 end: end.to_used_value(self.containing_size),
800 };
801 let alignment_container = RectAxis {
802 origin: offsets.start,
803 length: self.containing_size - offsets.sum(),
804 };
805 (
806 alignment_container,
807 containing_block_writing_mode,
808 false,
809 Some(offsets),
810 )
811 },
812 (Some(start), None) => return start.to_used_value(self.containing_size),
816 (None, Some(end)) => {
817 return self.containing_size - size - end.to_used_value(self.containing_size);
818 },
819 };
820
821 assert_eq!(
822 self_writing_mode.is_horizontal(),
823 original_parent_writing_mode.is_horizontal(),
824 "Mixed horizontal and vertical writing modes are not supported yet"
825 );
826 assert_eq!(
827 self_writing_mode.is_horizontal(),
828 containing_block_writing_mode.is_horizontal(),
829 "Mixed horizontal and vertical writing modes are not supported yet"
830 );
831 let self_value_matches_container = || {
832 self.axis == Direction::Block ||
833 self_writing_mode.is_bidi_ltr() == alignment_container_writing_mode.is_bidi_ltr()
834 };
835
836 let alignment = match self.alignment.value() {
841 AlignFlags::CENTER | AlignFlags::SPACE_AROUND | AlignFlags::SPACE_EVENLY => {
845 AlignFlags::CENTER
846 },
847 AlignFlags::SELF_START if self_value_matches_container() => AlignFlags::START,
849 AlignFlags::SELF_START => AlignFlags::END,
850 AlignFlags::SELF_END if self_value_matches_container() => AlignFlags::END,
852 AlignFlags::SELF_END => AlignFlags::START,
853 AlignFlags::LEFT if alignment_container_writing_mode.is_bidi_ltr() => AlignFlags::START,
855 AlignFlags::LEFT => AlignFlags::END,
856 AlignFlags::RIGHT if alignment_container_writing_mode.is_bidi_ltr() => AlignFlags::END,
858 AlignFlags::RIGHT => AlignFlags::START,
859 AlignFlags::END | AlignFlags::FLEX_END => AlignFlags::END,
862 _ => AlignFlags::START,
865 };
866
867 let alignment = match alignment {
868 AlignFlags::START if flip_anchor => AlignFlags::END,
869 AlignFlags::END if flip_anchor => AlignFlags::START,
870 alignment => alignment,
871 };
872
873 let free_space = alignment_container.length - size;
874 let flags = self.alignment.flags();
875 let alignment = if flags == AlignFlags::SAFE && free_space < Au::zero() {
876 AlignFlags::START
877 } else {
878 alignment
879 };
880
881 let origin = match alignment {
882 AlignFlags::START => alignment_container.origin,
883 AlignFlags::CENTER => alignment_container.origin + free_space / 2,
884 AlignFlags::END => alignment_container.origin + free_space,
885 _ => unreachable!(),
886 };
887 if matches!(flags, AlignFlags::SAFE | AlignFlags::UNSAFE) ||
888 matches!(
889 self.alignment,
890 AlignFlags::NORMAL | AlignFlags::AUTO | AlignFlags::STRETCH
891 )
892 {
893 return origin;
894 }
895 let Some(offsets) = offsets else {
896 return origin;
897 };
898
899 let min = Au::zero().min(offsets.start);
902 let max = self.containing_size - Au::zero().min(offsets.end) - size;
903 origin.clamp_between_extremums(min, Some(max))
904 }
905}
906
907pub(crate) fn relative_adjustement(
909 style: &ComputedValues,
910 containing_block: &ContainingBlock,
911) -> LogicalVec2<Au> {
912 let cbis = containing_block.size.inline;
916 let cbbs = containing_block.size.block;
917 let box_offsets = style
918 .box_offsets(containing_block.style.writing_mode)
919 .map_inline_and_block_axes(
920 |value| value.map(|value| value.to_used_value(cbis)),
921 |value| match cbbs {
922 SizeConstraint::Definite(cbbs) => value.map(|value| value.to_used_value(cbbs)),
923 _ => match value.non_auto().and_then(|value| value.to_length()) {
924 Some(value) => AuOrAuto::LengthPercentage(value.into()),
925 None => AuOrAuto::Auto,
926 },
927 },
928 );
929 fn adjust(start: AuOrAuto, end: AuOrAuto) -> Au {
930 match (start, end) {
931 (AuOrAuto::Auto, AuOrAuto::Auto) => Au::zero(),
932 (AuOrAuto::Auto, AuOrAuto::LengthPercentage(end)) => -end,
933 (AuOrAuto::LengthPercentage(start), _) => start,
934 }
935 }
936 LogicalVec2 {
937 inline: adjust(box_offsets.inline_start, box_offsets.inline_end),
938 block: adjust(box_offsets.block_start, box_offsets.block_end),
939 }
940}