1use app_units::Au;
6use layout_api::AxesOverflow;
7use malloc_size_of_derive::MallocSizeOf;
8use style::Zero;
9use style::color::AbsoluteColor;
10use style::computed_values::direction::T as Direction;
11use style::computed_values::isolation::T as ComputedIsolation;
12use style::computed_values::mix_blend_mode::T as ComputedMixBlendMode;
13use style::computed_values::position::T as ComputedPosition;
14use style::computed_values::transform_style::T as ComputedTransformStyle;
15use style::computed_values::unicode_bidi::T as UnicodeBidi;
16use style::logical_geometry::{Direction as AxisDirection, PhysicalSide, WritingMode};
17use style::properties::ComputedValues;
18use style::properties::longhands::backface_visibility::computed_value::T as BackfaceVisiblity;
19use style::properties::longhands::box_sizing::computed_value::T as BoxSizing;
20use style::properties::longhands::column_span::computed_value::T as ColumnSpan;
21use style::properties::style_structs::Border;
22use style::servo::selector_parser::PseudoElement;
23use style::values::CSSFloat;
24use style::values::computed::basic_shape::ClipPath;
25use style::values::computed::image::Image as ComputedImageLayer;
26use style::values::computed::{
27 BorderSideWidth, BorderStyle, Color, Inset, ItemPlacement, LengthPercentage, Margin,
28 SelfAlignment,
29};
30use style::values::generics::box_::Perspective;
31use style::values::generics::position::{GenericAspectRatio, PreferredRatio};
32use style::values::generics::transform::{GenericRotate, GenericScale, GenericTranslate};
33use style::values::specified::align::AlignFlags;
34use style::values::specified::{Overflow, WillChangeBits, box_ as stylo};
35use unicode_bidi::Level;
36use webrender_api as wr;
37use webrender_api::units::LayoutTransform;
38
39use crate::dom_traversal::Contents;
40use crate::fragment_tree::FragmentFlags;
41use crate::geom::{
42 AuOrAuto, LengthPercentageOrAuto, LogicalSides, LogicalSides1D, LogicalVec2, PhysicalSides,
43 PhysicalSize,
44};
45use crate::sizing::{Size, Sizes};
46use crate::table::TableLayoutStyle;
47use crate::{ContainingBlock, IndefiniteContainingBlock};
48
49#[derive(Clone, Copy, Eq, PartialEq)]
50pub(crate) enum Display {
51 None,
52 Contents,
53 GeneratingBox(DisplayGeneratingBox),
54}
55
56#[derive(Clone, Copy, Debug, Eq, PartialEq)]
57pub(crate) enum DisplayGeneratingBox {
58 OutsideInside {
59 outside: DisplayOutside,
60 inside: DisplayInside,
61 },
62 LayoutInternal(DisplayLayoutInternal),
64}
65impl DisplayGeneratingBox {
66 pub(crate) fn display_inside(&self) -> DisplayInside {
67 match *self {
68 DisplayGeneratingBox::OutsideInside { inside, .. } => inside,
69 DisplayGeneratingBox::LayoutInternal(layout_internal) => {
70 layout_internal.display_inside()
71 },
72 }
73 }
74
75 pub(crate) fn used_value_for_contents(&self, contents: &Contents) -> Self {
76 if matches!(self, Self::LayoutInternal(_)) && contents.is_replaced() {
81 Self::OutsideInside {
82 outside: DisplayOutside::Inline,
83 inside: DisplayInside::Flow {
84 is_list_item: false,
85 },
86 }
87 } else if matches!(contents, Contents::Widget(_)) {
88 if let DisplayGeneratingBox::OutsideInside { outside, .. } = self {
92 DisplayGeneratingBox::OutsideInside {
93 outside: *outside,
94 inside: DisplayInside::FlowRoot {
95 is_list_item: false,
96 },
97 }
98 } else {
99 *self
100 }
101 } else {
102 *self
103 }
104 }
105}
106
107#[derive(Clone, Copy, Debug, Eq, PartialEq)]
108pub(crate) enum DisplayOutside {
109 Block,
110 Inline,
111}
112
113#[derive(Clone, Copy, Debug, Eq, PartialEq)]
114pub(crate) enum DisplayInside {
115 Flow { is_list_item: bool },
118 FlowRoot { is_list_item: bool },
119 Flex,
120 Grid,
121 Table,
122}
123
124#[derive(Clone, Copy, Debug, Eq, PartialEq)]
125#[expect(clippy::enum_variant_names)]
126pub(crate) enum DisplayLayoutInternal {
128 TableCaption,
129 TableCell,
130 TableColumn,
131 TableColumnGroup,
132 TableFooterGroup,
133 TableHeaderGroup,
134 TableRow,
135 TableRowGroup,
136}
137
138impl DisplayLayoutInternal {
139 pub(crate) fn display_inside(&self) -> DisplayInside {
141 DisplayInside::FlowRoot {
145 is_list_item: false,
146 }
147 }
148}
149
150#[derive(Clone, Debug)]
152pub(crate) struct PaddingBorderMargin {
153 pub padding: LogicalSides<Au>,
154 pub border: LogicalSides<Au>,
155 pub margin: LogicalSides<AuOrAuto>,
156
157 pub padding_border_sums: LogicalVec2<Au>,
159}
160
161impl PaddingBorderMargin {
162 pub(crate) fn zero() -> Self {
163 Self {
164 padding: LogicalSides::zero(),
165 border: LogicalSides::zero(),
166 margin: LogicalSides::zero(),
167 padding_border_sums: LogicalVec2::zero(),
168 }
169 }
170
171 pub(crate) fn sums_auto_is_zero(
172 &self,
173 ignore_block_margins: LogicalSides1D<bool>,
174 ) -> LogicalVec2<Au> {
175 let margin = self.margin.auto_is(Au::zero);
176 let mut sums = self.padding_border_sums;
177 sums.inline += margin.inline_sum();
178 if !ignore_block_margins.start {
179 sums.block += margin.block_start;
180 }
181 if !ignore_block_margins.end {
182 sums.block += margin.block_end;
183 }
184 sums
185 }
186}
187
188#[derive(Clone, Copy, Debug)]
192pub(crate) struct AspectRatio {
193 box_sizing_adjustment: LogicalVec2<Au>,
198 i_over_b: CSSFloat,
200}
201
202impl AspectRatio {
203 pub(crate) fn compute_dependent_size(
205 &self,
206 ratio_dependent_axis: AxisDirection,
207 ratio_determining_size: Au,
208 ) -> Au {
209 match ratio_dependent_axis {
210 AxisDirection::Inline => {
212 (ratio_determining_size + self.box_sizing_adjustment.block).scale_by(self.i_over_b) -
213 self.box_sizing_adjustment.inline
214 },
215 AxisDirection::Block => {
217 (ratio_determining_size + self.box_sizing_adjustment.inline)
218 .scale_by(1.0 / self.i_over_b) -
219 self.box_sizing_adjustment.block
220 },
221 }
222 }
223
224 pub(crate) fn from_logical_content_ratio(i_over_b: CSSFloat) -> Self {
225 Self {
226 box_sizing_adjustment: LogicalVec2::zero(),
227 i_over_b,
228 }
229 }
230}
231
232#[derive(Clone)]
233pub(crate) struct ContentBoxSizesAndPBM {
234 pub content_box_sizes: LogicalVec2<Sizes>,
235 pub pbm: PaddingBorderMargin,
236 pub depends_on_block_constraints: bool,
237 pub preferred_size_computes_to_auto: LogicalVec2<bool>,
238}
239
240#[derive(Clone, Debug, MallocSizeOf, PartialEq)]
241pub(crate) struct BorderStyleColor {
242 pub style: BorderStyle,
243 pub color: AbsoluteColor,
244}
245
246impl BorderStyleColor {
247 pub(crate) fn new(style: BorderStyle, color: AbsoluteColor) -> Self {
248 Self { style, color }
249 }
250
251 pub(crate) fn from_border(
252 border: &Border,
253 current_color: &AbsoluteColor,
254 ) -> PhysicalSides<Self> {
255 let resolve = |color: &Color| color.resolve_to_absolute(current_color);
256 PhysicalSides::<Self>::new(
257 Self::new(border.border_top_style, resolve(&border.border_top_color)),
258 Self::new(
259 border.border_right_style,
260 resolve(&border.border_right_color),
261 ),
262 Self::new(
263 border.border_bottom_style,
264 resolve(&border.border_bottom_color),
265 ),
266 Self::new(border.border_left_style, resolve(&border.border_left_color)),
267 )
268 }
269
270 pub(crate) fn hidden() -> Self {
271 Self::new(BorderStyle::Hidden, AbsoluteColor::TRANSPARENT_BLACK)
272 }
273}
274
275impl Default for BorderStyleColor {
276 fn default() -> Self {
277 Self::new(BorderStyle::None, AbsoluteColor::TRANSPARENT_BLACK)
278 }
279}
280
281pub(crate) struct OverflowDirection {
285 pub rightward: bool,
287 pub downward: bool,
289}
290
291pub(crate) trait ComputedValuesExt {
292 fn physical_box_offsets(&self) -> PhysicalSides<LengthPercentageOrAuto<'_>>;
293 fn box_offsets(&self, writing_mode: WritingMode) -> LogicalSides<LengthPercentageOrAuto<'_>>;
294 fn box_size(
295 &self,
296 containing_block_writing_mode: WritingMode,
297 ) -> LogicalVec2<Size<LengthPercentage>>;
298 fn min_box_size(
299 &self,
300 containing_block_writing_mode: WritingMode,
301 ) -> LogicalVec2<Size<LengthPercentage>>;
302 fn max_box_size(
303 &self,
304 containing_block_writing_mode: WritingMode,
305 ) -> LogicalVec2<Size<LengthPercentage>>;
306 fn content_box_size_for_box_size(
307 &self,
308 box_size: LogicalVec2<Size<Au>>,
309 pbm: &PaddingBorderMargin,
310 ) -> LogicalVec2<Size<Au>>;
311 fn content_min_box_size_for_min_size(
312 &self,
313 box_size: LogicalVec2<Size<Au>>,
314 pbm: &PaddingBorderMargin,
315 ) -> LogicalVec2<Size<Au>>;
316 fn content_max_box_size_for_max_size(
317 &self,
318 box_size: LogicalVec2<Size<Au>>,
319 pbm: &PaddingBorderMargin,
320 ) -> LogicalVec2<Size<Au>>;
321 fn border_style_color(
322 &self,
323 containing_block_writing_mode: WritingMode,
324 ) -> LogicalSides<BorderStyleColor>;
325 fn physical_margin(&self) -> PhysicalSides<LengthPercentageOrAuto<'_>>;
326 fn margin(
327 &self,
328 containing_block_writing_mode: WritingMode,
329 ) -> LogicalSides<LengthPercentageOrAuto<'_>>;
330 fn is_transformable(&self, fragment_flags: FragmentFlags) -> bool;
331 fn has_transform_or_perspective_style(&self) -> bool;
332 fn has_effective_transform_or_perspective(&self, fragment_flags: FragmentFlags) -> bool;
333 fn z_index_applies(&self, fragment_flags: FragmentFlags) -> bool;
334 fn effective_z_index(&self, fragment_flags: FragmentFlags) -> i32;
335 fn effective_overflow(&self, fragment_flags: FragmentFlags) -> AxesOverflow;
336 fn establishes_block_formatting_context(&self, fragment_flags: FragmentFlags) -> bool;
337 fn establishes_stacking_context(&self, fragment_flags: FragmentFlags) -> bool;
338 fn establishes_scroll_container(&self, fragment_flags: FragmentFlags) -> bool;
339 fn establishes_containing_block_for_absolute_descendants(
340 &self,
341 fragment_flags: FragmentFlags,
342 ) -> bool;
343 fn establishes_containing_block_for_all_descendants(
344 &self,
345 fragment_flags: FragmentFlags,
346 ) -> bool;
347 fn preferred_aspect_ratio(
348 &self,
349 natural_aspect_ratio: Option<CSSFloat>,
350 padding_border_sums: &LogicalVec2<Au>,
351 ) -> Option<AspectRatio>;
352 fn background_is_transparent(&self) -> bool;
353 fn get_webrender_primitive_flags(&self) -> wr::PrimitiveFlags;
354 fn bidi_control_chars(&self) -> (&'static str, &'static str);
355 fn resolve_align_self(
356 &self,
357 resolved_auto_value: ItemPlacement,
358 resolved_normal_value: AlignFlags,
359 ) -> SelfAlignment;
360 fn depends_on_block_constraints_due_to_relative_positioning(
361 &self,
362 writing_mode: WritingMode,
363 ) -> bool;
364 fn is_inline_box(&self, fragment_flags: FragmentFlags) -> bool;
365 fn overflow_direction(&self) -> OverflowDirection;
366 fn to_bidi_level(&self) -> Level;
367}
368
369impl ComputedValuesExt for ComputedValues {
370 fn physical_box_offsets(&self) -> PhysicalSides<LengthPercentageOrAuto<'_>> {
371 fn convert(inset: &Inset) -> LengthPercentageOrAuto<'_> {
372 match inset {
373 Inset::LengthPercentage(v) => LengthPercentageOrAuto::LengthPercentage(v),
374 Inset::Auto => LengthPercentageOrAuto::Auto,
375 Inset::AnchorFunction(_) => unreachable!("anchor() should be disabled"),
376 Inset::AnchorSizeFunction(_) => unreachable!("anchor-size() should be disabled"),
377 Inset::AnchorContainingCalcFunction(_) => {
378 unreachable!("anchor() and anchor-size() should be disabled")
379 },
380 }
381 }
382 let position = self.get_position();
383 PhysicalSides::new(
384 convert(&position.top),
385 convert(&position.right),
386 convert(&position.bottom),
387 convert(&position.left),
388 )
389 }
390
391 fn box_offsets(&self, writing_mode: WritingMode) -> LogicalSides<LengthPercentageOrAuto<'_>> {
392 LogicalSides::from_physical(&self.physical_box_offsets(), writing_mode)
393 }
394
395 fn box_size(
396 &self,
397 containing_block_writing_mode: WritingMode,
398 ) -> LogicalVec2<Size<LengthPercentage>> {
399 let position = self.get_position();
400 LogicalVec2::from_physical_size(
401 &PhysicalSize::new(
402 position.clone_width().into(),
403 position.clone_height().into(),
404 ),
405 containing_block_writing_mode,
406 )
407 }
408
409 fn min_box_size(
410 &self,
411 containing_block_writing_mode: WritingMode,
412 ) -> LogicalVec2<Size<LengthPercentage>> {
413 let position = self.get_position();
414 LogicalVec2::from_physical_size(
415 &PhysicalSize::new(
416 position.clone_min_width().into(),
417 position.clone_min_height().into(),
418 ),
419 containing_block_writing_mode,
420 )
421 }
422
423 fn max_box_size(
424 &self,
425 containing_block_writing_mode: WritingMode,
426 ) -> LogicalVec2<Size<LengthPercentage>> {
427 let position = self.get_position();
428 LogicalVec2::from_physical_size(
429 &PhysicalSize::new(
430 position.clone_max_width().into(),
431 position.clone_max_height().into(),
432 ),
433 containing_block_writing_mode,
434 )
435 }
436
437 fn content_box_size_for_box_size(
438 &self,
439 box_size: LogicalVec2<Size<Au>>,
440 pbm: &PaddingBorderMargin,
441 ) -> LogicalVec2<Size<Au>> {
442 match self.get_position().box_sizing {
443 BoxSizing::ContentBox => box_size,
444 BoxSizing::BorderBox => box_size.map_inline_and_block_sizes(
447 |value| value - pbm.padding_border_sums.inline,
448 |value| value - pbm.padding_border_sums.block,
449 ),
450 }
451 }
452
453 fn content_min_box_size_for_min_size(
454 &self,
455 min_box_size: LogicalVec2<Size<Au>>,
456 pbm: &PaddingBorderMargin,
457 ) -> LogicalVec2<Size<Au>> {
458 match self.get_position().box_sizing {
459 BoxSizing::ContentBox => min_box_size,
460 BoxSizing::BorderBox => min_box_size.map_inline_and_block_sizes(
462 |value| Au::zero().max(value - pbm.padding_border_sums.inline),
463 |value| Au::zero().max(value - pbm.padding_border_sums.block),
464 ),
465 }
466 }
467
468 fn content_max_box_size_for_max_size(
469 &self,
470 max_box_size: LogicalVec2<Size<Au>>,
471 pbm: &PaddingBorderMargin,
472 ) -> LogicalVec2<Size<Au>> {
473 match self.get_position().box_sizing {
474 BoxSizing::ContentBox => max_box_size,
475 BoxSizing::BorderBox => max_box_size.map_inline_and_block_sizes(
478 |value| value - pbm.padding_border_sums.inline,
479 |value| value - pbm.padding_border_sums.block,
480 ),
481 }
482 }
483
484 fn border_style_color(
485 &self,
486 containing_block_writing_mode: WritingMode,
487 ) -> LogicalSides<BorderStyleColor> {
488 let current_color = self.get_inherited_text().clone_color();
489 LogicalSides::from_physical(
490 &BorderStyleColor::from_border(self.get_border(), ¤t_color),
491 containing_block_writing_mode,
492 )
493 }
494
495 fn physical_margin(&self) -> PhysicalSides<LengthPercentageOrAuto<'_>> {
496 fn convert(inset: &Margin) -> LengthPercentageOrAuto<'_> {
497 match inset {
498 Margin::LengthPercentage(v) => LengthPercentageOrAuto::LengthPercentage(v),
499 Margin::Auto => LengthPercentageOrAuto::Auto,
500 Margin::AnchorSizeFunction(_) | Margin::AnchorContainingCalcFunction(_) => {
501 unreachable!("anchor-size() should be disabled")
502 },
503 }
504 }
505 let margin = self.get_margin();
506 PhysicalSides::new(
507 convert(&margin.margin_top),
508 convert(&margin.margin_right),
509 convert(&margin.margin_bottom),
510 convert(&margin.margin_left),
511 )
512 }
513
514 fn margin(
515 &self,
516 containing_block_writing_mode: WritingMode,
517 ) -> LogicalSides<LengthPercentageOrAuto<'_>> {
518 LogicalSides::from_physical(&self.physical_margin(), containing_block_writing_mode)
519 }
520
521 fn is_inline_box(&self, fragment_flags: FragmentFlags) -> bool {
522 self.get_box().display.is_inline_flow() &&
523 !fragment_flags.intersects(FragmentFlags::IS_REPLACED | FragmentFlags::IS_WIDGET)
524 }
525
526 fn is_transformable(&self, fragment_flags: FragmentFlags) -> bool {
528 !self.is_inline_box(fragment_flags)
538 }
539
540 fn has_transform_or_perspective_style(&self) -> bool {
542 !self.get_box().transform.0.is_empty() ||
543 self.get_box().scale != GenericScale::None ||
544 self.get_box().rotate != GenericRotate::None ||
545 self.get_box().translate != GenericTranslate::None ||
546 self.get_box().perspective != Perspective::None
547 }
548
549 #[inline]
552 fn has_effective_transform_or_perspective(&self, fragment_flags: FragmentFlags) -> bool {
553 self.is_transformable(fragment_flags) && self.has_transform_or_perspective_style()
554 }
555
556 fn z_index_applies(&self, fragment_flags: FragmentFlags) -> bool {
558 if self.get_box().position != ComputedPosition::Static {
561 return true;
562 }
563 fragment_flags.contains(FragmentFlags::IS_FLEX_OR_GRID_ITEM)
575 }
576
577 fn effective_z_index(&self, fragment_flags: FragmentFlags) -> i32 {
581 if self.z_index_applies(fragment_flags) {
582 self.get_position().z_index.integer_or(0)
583 } else {
584 0
585 }
586 }
587
588 fn effective_overflow(&self, fragment_flags: FragmentFlags) -> AxesOverflow {
592 if fragment_flags.contains(FragmentFlags::PROPAGATED_OVERFLOW_TO_VIEWPORT) {
595 return AxesOverflow::default();
596 }
597
598 let mut overflow = AxesOverflow::from(self);
599
600 if fragment_flags.contains(FragmentFlags::IS_REPLACED) {
603 if overflow.x != Overflow::Visible {
604 overflow.x = Overflow::Clip;
605 }
606 if overflow.y != Overflow::Visible {
607 overflow.y = Overflow::Clip;
608 }
609 return overflow;
610 }
611
612 let ignores_overflow = match self.get_box().display.inside() {
613 stylo::DisplayInside::Flow => self.is_inline_box(fragment_flags),
616
617 stylo::DisplayInside::Table => {
625 !matches!(self.pseudo(), Some(PseudoElement::ServoTableGrid)) ||
626 matches!(overflow.x, Overflow::Auto | Overflow::Scroll) ||
627 matches!(overflow.y, Overflow::Auto | Overflow::Scroll)
628 },
629
630 stylo::DisplayInside::TableColumn |
633 stylo::DisplayInside::TableColumnGroup |
634 stylo::DisplayInside::TableRow |
635 stylo::DisplayInside::TableRowGroup |
636 stylo::DisplayInside::TableHeaderGroup |
637 stylo::DisplayInside::TableFooterGroup => true,
638
639 _ => false,
640 };
641 if ignores_overflow {
642 return AxesOverflow::default();
643 }
644
645 overflow
646 }
647
648 fn establishes_block_formatting_context(&self, fragment_flags: FragmentFlags) -> bool {
655 if self.establishes_scroll_container(fragment_flags) {
656 return true;
657 }
658
659 if self.get_column().is_multicol() {
660 return true;
661 }
662
663 if self.get_column().column_span == ColumnSpan::All {
664 return true;
665 }
666
667 if self.get_position().align_content.primary() != AlignFlags::NORMAL {
673 return true;
674 }
675
676 false
678 }
679
680 fn establishes_scroll_container(&self, fragment_flags: FragmentFlags) -> bool {
682 self.effective_overflow(fragment_flags).x.is_scrollable()
685 }
686
687 fn establishes_stacking_context(&self, fragment_flags: FragmentFlags) -> bool {
689 let will_change_bits = self.clone_will_change().bits;
693 if will_change_bits
694 .intersects(WillChangeBits::STACKING_CONTEXT_UNCONDITIONAL | WillChangeBits::OPACITY)
695 {
696 return true;
697 }
698
699 if self.z_index_applies(fragment_flags) &&
702 (!self.get_position().z_index.is_auto() ||
703 will_change_bits.intersects(WillChangeBits::Z_INDEX))
704 {
705 return true;
706 }
707
708 if matches!(
711 self.get_box().position,
712 ComputedPosition::Fixed | ComputedPosition::Sticky
713 ) {
714 return true;
715 }
716
717 if self.is_transformable(fragment_flags) &&
732 (self.has_transform_or_perspective_style() ||
733 self.get_box().transform_style == ComputedTransformStyle::Preserve3d ||
734 will_change_bits
735 .intersects(WillChangeBits::TRANSFORM | WillChangeBits::PERSPECTIVE))
736 {
737 return true;
738 }
739
740 let effects = self.get_effects();
744 if effects.opacity != 1.0 {
745 return true;
746 }
747
748 if !effects.filter.0.is_empty() {
752 return true;
753 }
754
755 if effects.mix_blend_mode != ComputedMixBlendMode::Normal {
759 return true;
760 }
761
762 if self.get_svg().clip_path != ClipPath::None {
766 return true;
767 }
768
769 if self.get_box().isolation == ComputedIsolation::Isolate {
773 return true;
774 }
775
776 if fragment_flags.contains(FragmentFlags::IS_ROOT_ELEMENT) {
779 return true;
780 }
781
782 false
784 }
785
786 fn establishes_containing_block_for_absolute_descendants(
793 &self,
794 fragment_flags: FragmentFlags,
795 ) -> bool {
796 if self.establishes_containing_block_for_all_descendants(fragment_flags) {
797 return true;
798 }
799
800 if self
805 .clone_will_change()
806 .bits
807 .intersects(WillChangeBits::POSITION)
808 {
809 return true;
810 }
811
812 self.clone_position() != ComputedPosition::Static
813 }
814
815 fn establishes_containing_block_for_all_descendants(
820 &self,
821 fragment_flags: FragmentFlags,
822 ) -> bool {
823 let will_change_bits = self.clone_will_change().bits;
828
829 if self.is_transformable(fragment_flags) &&
845 (self.has_transform_or_perspective_style() ||
846 self.get_box().transform_style == ComputedTransformStyle::Preserve3d ||
847 will_change_bits
848 .intersects(WillChangeBits::TRANSFORM | WillChangeBits::PERSPECTIVE))
849 {
850 return true;
851 }
852
853 if !fragment_flags.contains(FragmentFlags::IS_ROOT_ELEMENT) &&
858 (!self.get_effects().filter.0.is_empty() ||
859 will_change_bits.intersects(WillChangeBits::FIXPOS_CB_NON_SVG))
860 {
861 return true;
862 }
863
864 false
866 }
867
868 fn preferred_aspect_ratio(
872 &self,
873 natural_aspect_ratio: Option<CSSFloat>,
874 padding_border_sums: &LogicalVec2<Au>,
875 ) -> Option<AspectRatio> {
876 let GenericAspectRatio {
877 auto,
878 ratio: mut preferred_ratio,
879 } = self.clone_aspect_ratio();
880
881 if matches!(preferred_ratio, PreferredRatio::Ratio(ratio) if ratio.is_degenerate()) {
884 preferred_ratio = PreferredRatio::None;
885 }
886
887 let to_logical_ratio = |physical_ratio| {
888 if self.writing_mode.is_horizontal() {
889 physical_ratio
890 } else {
891 1.0 / physical_ratio
892 }
893 };
894
895 match (auto, preferred_ratio) {
896 (_, PreferredRatio::None) => natural_aspect_ratio
904 .map(to_logical_ratio)
905 .map(AspectRatio::from_logical_content_ratio),
906 (true, PreferredRatio::Ratio(preferred_ratio)) => Some({
913 let physical_ratio = natural_aspect_ratio
914 .unwrap_or_else(|| (preferred_ratio.0).0 / (preferred_ratio.1).0);
915 AspectRatio::from_logical_content_ratio(to_logical_ratio(physical_ratio))
916 }),
917
918 (false, PreferredRatio::Ratio(preferred_ratio)) => {
922 let box_sizing_adjustment = match self.clone_box_sizing() {
925 BoxSizing::ContentBox => LogicalVec2::zero(),
926 BoxSizing::BorderBox => *padding_border_sums,
927 };
928 Some(AspectRatio {
929 i_over_b: to_logical_ratio((preferred_ratio.0).0 / (preferred_ratio.1).0),
930 box_sizing_adjustment,
931 })
932 },
933 }
934 }
935
936 fn background_is_transparent(&self) -> bool {
938 let background = self.get_background();
939 let color = self.resolve_color(&background.background_color);
940 color.alpha == 0.0 &&
941 background
942 .background_image
943 .0
944 .iter()
945 .all(|layer| matches!(layer, ComputedImageLayer::None))
946 }
947
948 fn get_webrender_primitive_flags(&self) -> wr::PrimitiveFlags {
951 match self.get_box().backface_visibility {
952 BackfaceVisiblity::Visible => wr::PrimitiveFlags::default(),
953 BackfaceVisiblity::Hidden => wr::PrimitiveFlags::empty(),
954 }
955 }
956
957 fn bidi_control_chars(&self) -> (&'static str, &'static str) {
961 match (
962 self.get_text().unicode_bidi,
963 self.get_inherited_box().direction,
964 ) {
965 (UnicodeBidi::Normal, _) => ("", ""),
966 (UnicodeBidi::Embed, Direction::Ltr) => ("\u{202a}", "\u{202c}"),
967 (UnicodeBidi::Embed, Direction::Rtl) => ("\u{202b}", "\u{202c}"),
968 (UnicodeBidi::Isolate, Direction::Ltr) => ("\u{2066}", "\u{2069}"),
969 (UnicodeBidi::Isolate, Direction::Rtl) => ("\u{2067}", "\u{2069}"),
970 (UnicodeBidi::BidiOverride, Direction::Ltr) => ("\u{202d}", "\u{202c}"),
971 (UnicodeBidi::BidiOverride, Direction::Rtl) => ("\u{202e}", "\u{202c}"),
972 (UnicodeBidi::IsolateOverride, Direction::Ltr) => {
973 ("\u{2068}\u{202d}", "\u{202c}\u{2069}")
974 },
975 (UnicodeBidi::IsolateOverride, Direction::Rtl) => {
976 ("\u{2068}\u{202e}", "\u{202c}\u{2069}")
977 },
978 (UnicodeBidi::Plaintext, _) => ("\u{2068}", "\u{2069}"),
979 }
980 }
981
982 fn resolve_align_self(
983 &self,
984 resolved_auto_value: ItemPlacement,
985 resolved_normal_value: AlignFlags,
986 ) -> SelfAlignment {
987 SelfAlignment(match self.clone_align_self().0 {
988 AlignFlags::AUTO => resolved_auto_value.0,
989 AlignFlags::NORMAL => resolved_normal_value,
990 value => value,
991 })
992 }
993
994 fn depends_on_block_constraints_due_to_relative_positioning(
995 &self,
996 writing_mode: WritingMode,
997 ) -> bool {
998 if !matches!(
999 self.get_box().position,
1000 ComputedPosition::Relative | ComputedPosition::Sticky
1001 ) {
1002 return false;
1003 }
1004 let box_offsets = self.box_offsets(writing_mode);
1005 let has_percentage = |offset: LengthPercentageOrAuto<'_>| {
1006 offset
1007 .non_auto()
1008 .is_some_and(LengthPercentage::has_percentage)
1009 };
1010 has_percentage(box_offsets.block_start) || has_percentage(box_offsets.block_end)
1011 }
1012
1013 fn overflow_direction(&self) -> OverflowDirection {
1015 let inline_end_direction = self.writing_mode.inline_end_physical_side();
1016 let block_end_direction = self.writing_mode.block_end_physical_side();
1017
1018 let rightward = inline_end_direction == PhysicalSide::Right ||
1019 block_end_direction == PhysicalSide::Right;
1020 let downward = inline_end_direction == PhysicalSide::Bottom ||
1021 block_end_direction == PhysicalSide::Bottom;
1022
1023 OverflowDirection {
1025 rightward,
1026 downward,
1027 }
1028 }
1029
1030 fn to_bidi_level(&self) -> Level {
1034 if self.writing_mode.is_bidi_ltr() {
1035 Level::ltr()
1036 } else {
1037 Level::rtl()
1038 }
1039 }
1040}
1041
1042pub(crate) enum LayoutStyle<'a> {
1043 Default(&'a ComputedValues),
1044 Table(TableLayoutStyle<'a>),
1045}
1046
1047impl LayoutStyle<'_> {
1048 #[inline]
1049 pub(crate) fn style(&self) -> &ComputedValues {
1050 match self {
1051 Self::Default(style) => style,
1052 Self::Table(table) => table.style(),
1053 }
1054 }
1055
1056 #[inline]
1057 pub(crate) fn is_table(&self) -> bool {
1058 matches!(self, Self::Table(_))
1059 }
1060
1061 pub(crate) fn content_box_sizes_and_padding_border_margin(
1062 &self,
1063 containing_block: &IndefiniteContainingBlock,
1064 ) -> ContentBoxSizesAndPBM {
1065 let containing_block_size_or_zero =
1071 containing_block.size.map(|value| value.unwrap_or_default());
1072 let writing_mode = containing_block.style.writing_mode;
1073 let pbm = self.padding_border_margin_with_writing_mode_and_containing_block_inline_size(
1074 writing_mode,
1075 containing_block_size_or_zero.inline,
1076 );
1077 let style = self.style();
1078 let box_size = style.box_size(writing_mode);
1079 let min_size = style.min_box_size(writing_mode);
1080 let max_size = style.max_box_size(writing_mode);
1081 let preferred_size_computes_to_auto = box_size.map(|size| size.is_initial());
1082
1083 let depends_on_block_constraints = |size: &Size<LengthPercentage>| {
1084 match size {
1085 Size::Stretch => true,
1093 Size::Numeric(length_percentage) => length_percentage.has_percentage(),
1094 _ => false,
1095 }
1096 };
1097 let depends_on_block_constraints = depends_on_block_constraints(&box_size.block) ||
1098 depends_on_block_constraints(&min_size.block) ||
1099 depends_on_block_constraints(&max_size.block) ||
1100 style.depends_on_block_constraints_due_to_relative_positioning(writing_mode);
1101
1102 let box_size = box_size.map_with(&containing_block.size, |size, basis| {
1103 size.resolve_percentages_for_preferred(*basis)
1104 });
1105 let content_box_size = style.content_box_size_for_box_size(box_size, &pbm);
1106 let min_size = min_size.percentages_relative_to_basis(&containing_block_size_or_zero);
1107 let content_min_box_size = style.content_min_box_size_for_min_size(min_size, &pbm);
1108 let max_size = max_size.map_with(&containing_block.size, |size, basis| {
1109 size.resolve_percentages_for_max(*basis)
1110 });
1111 let content_max_box_size = style.content_max_box_size_for_max_size(max_size, &pbm);
1112 ContentBoxSizesAndPBM {
1113 content_box_sizes: LogicalVec2 {
1114 block: Sizes::new(
1115 content_box_size.block,
1116 content_min_box_size.block,
1117 content_max_box_size.block,
1118 ),
1119 inline: Sizes::new(
1120 content_box_size.inline,
1121 content_min_box_size.inline,
1122 content_max_box_size.inline,
1123 ),
1124 },
1125 pbm,
1126 depends_on_block_constraints,
1127 preferred_size_computes_to_auto,
1128 }
1129 }
1130
1131 pub(crate) fn padding_border_margin(
1132 &self,
1133 containing_block: &ContainingBlock,
1134 ) -> PaddingBorderMargin {
1135 self.padding_border_margin_with_writing_mode_and_containing_block_inline_size(
1136 containing_block.style.writing_mode,
1137 containing_block.size.inline,
1138 )
1139 }
1140
1141 pub(crate) fn padding_border_margin_with_writing_mode_and_containing_block_inline_size(
1142 &self,
1143 writing_mode: WritingMode,
1144 containing_block_inline_size: Au,
1145 ) -> PaddingBorderMargin {
1146 let padding = self
1147 .padding(writing_mode)
1148 .percentages_relative_to(containing_block_inline_size);
1149 let style = self.style();
1150 let border = self.border_width(writing_mode);
1151 let margin = style
1152 .margin(writing_mode)
1153 .percentages_relative_to(containing_block_inline_size);
1154 PaddingBorderMargin {
1155 padding_border_sums: LogicalVec2 {
1156 inline: padding.inline_sum() + border.inline_sum(),
1157 block: padding.block_sum() + border.block_sum(),
1158 },
1159 padding,
1160 border,
1161 margin,
1162 }
1163 }
1164
1165 pub(crate) fn padding(
1166 &self,
1167 containing_block_writing_mode: WritingMode,
1168 ) -> LogicalSides<LengthPercentage> {
1169 if matches!(self, Self::Table(table) if table.collapses_borders()) {
1170 return LogicalSides::zero();
1173 }
1174 let padding = self.style().get_padding().clone();
1175 LogicalSides::from_physical(
1176 &PhysicalSides::new(
1177 padding.padding_top.0,
1178 padding.padding_right.0,
1179 padding.padding_bottom.0,
1180 padding.padding_left.0,
1181 ),
1182 containing_block_writing_mode,
1183 )
1184 }
1185
1186 pub(crate) fn border_width(
1187 &self,
1188 containing_block_writing_mode: WritingMode,
1189 ) -> LogicalSides<Au> {
1190 let border_width = match self {
1191 Self::Table(table) if table.collapses_borders() => table
1195 .halved_collapsed_border_widths()
1196 .to_physical(self.style().writing_mode),
1197 _ => {
1198 let border = self.style().get_border();
1199 let resolve = |width: &BorderSideWidth, style: BorderStyle| {
1200 if style.none_or_hidden() {
1201 Au::zero()
1202 } else {
1203 width.0
1204 }
1205 };
1206 PhysicalSides::new(
1207 resolve(&border.border_top_width, border.border_top_style),
1208 resolve(&border.border_right_width, border.border_right_style),
1209 resolve(&border.border_bottom_width, border.border_bottom_style),
1210 resolve(&border.border_left_width, border.border_left_style),
1211 )
1212 },
1213 };
1214 LogicalSides::from_physical(&border_width, containing_block_writing_mode)
1215 }
1216}
1217
1218impl From<stylo::Display> for Display {
1219 fn from(packed: stylo::Display) -> Self {
1220 let outside = packed.outside();
1221 let inside = packed.inside();
1222
1223 let outside = match outside {
1224 stylo::DisplayOutside::Block => DisplayOutside::Block,
1225 stylo::DisplayOutside::Inline => DisplayOutside::Inline,
1226 stylo::DisplayOutside::TableCaption => {
1227 return Display::GeneratingBox(DisplayGeneratingBox::LayoutInternal(
1228 DisplayLayoutInternal::TableCaption,
1229 ));
1230 },
1231 stylo::DisplayOutside::InternalTable => {
1232 let internal = match inside {
1233 stylo::DisplayInside::TableRowGroup => DisplayLayoutInternal::TableRowGroup,
1234 stylo::DisplayInside::TableColumn => DisplayLayoutInternal::TableColumn,
1235 stylo::DisplayInside::TableColumnGroup => {
1236 DisplayLayoutInternal::TableColumnGroup
1237 },
1238 stylo::DisplayInside::TableHeaderGroup => {
1239 DisplayLayoutInternal::TableHeaderGroup
1240 },
1241 stylo::DisplayInside::TableFooterGroup => {
1242 DisplayLayoutInternal::TableFooterGroup
1243 },
1244 stylo::DisplayInside::TableRow => DisplayLayoutInternal::TableRow,
1245 stylo::DisplayInside::TableCell => DisplayLayoutInternal::TableCell,
1246 _ => unreachable!("Non-internal DisplayInside found"),
1247 };
1248 return Display::GeneratingBox(DisplayGeneratingBox::LayoutInternal(internal));
1249 },
1250 stylo::DisplayOutside::None if inside == stylo::DisplayInside::Contents => {
1253 return Display::Contents;
1254 },
1255 stylo::DisplayOutside::None => return Display::None,
1256 };
1257
1258 let inside = match packed.inside() {
1259 stylo::DisplayInside::Flow => DisplayInside::Flow {
1260 is_list_item: packed.is_list_item(),
1261 },
1262 stylo::DisplayInside::FlowRoot => DisplayInside::FlowRoot {
1263 is_list_item: packed.is_list_item(),
1264 },
1265 stylo::DisplayInside::Flex => DisplayInside::Flex,
1266 stylo::DisplayInside::Grid => DisplayInside::Grid,
1267
1268 stylo::DisplayInside::None => return Display::None,
1270 stylo::DisplayInside::Contents => return Display::Contents,
1271
1272 stylo::DisplayInside::Table => DisplayInside::Table,
1273 stylo::DisplayInside::TableRowGroup |
1274 stylo::DisplayInside::TableColumn |
1275 stylo::DisplayInside::TableColumnGroup |
1276 stylo::DisplayInside::TableHeaderGroup |
1277 stylo::DisplayInside::TableFooterGroup |
1278 stylo::DisplayInside::TableRow |
1279 stylo::DisplayInside::TableCell => unreachable!("Internal DisplayInside found"),
1280 };
1281 Display::GeneratingBox(DisplayGeneratingBox::OutsideInside { outside, inside })
1282 }
1283}
1284
1285pub(crate) trait Clamp: Sized {
1286 fn clamp_below_max(self, max: Option<Self>) -> Self;
1287 fn clamp_between_extremums(self, min: Self, max: Option<Self>) -> Self;
1288}
1289
1290impl Clamp for Au {
1291 fn clamp_below_max(self, max: Option<Self>) -> Self {
1292 match max {
1293 None => self,
1294 Some(max) => self.min(max),
1295 }
1296 }
1297
1298 fn clamp_between_extremums(self, min: Self, max: Option<Self>) -> Self {
1299 self.clamp_below_max(max).max(min)
1300 }
1301}
1302
1303pub(crate) trait TransformExt {
1304 fn change_basis(&self, x: f32, y: f32, z: f32) -> Self;
1305}
1306
1307impl TransformExt for LayoutTransform {
1308 fn change_basis(&self, x: f32, y: f32, z: f32) -> Self {
1310 let pre_translation = Self::translation(x, y, z);
1311 let post_translation = Self::translation(-x, -y, -z);
1312 post_translation.then(self).then(&pre_translation)
1313 }
1314}