1use std::cell::{Cell, LazyCell, OnceCell};
8use std::ops::{Add, AddAssign};
9use std::sync::atomic::Ordering;
10
11use app_units::{Au, MAX_AU};
12use malloc_size_of_derive::MallocSizeOf;
13use style::Zero;
14use style::logical_geometry::Direction;
15use style::values::computed::{
16 LengthPercentage, MaxSize as StyleMaxSize, Percentage, Size as StyleSize,
17};
18
19use crate::context::LayoutContext;
20use crate::layout_box_base::LayoutBoxBase;
21use crate::style_ext::{AspectRatio, Clamp, ComputedValuesExt, ContentBoxSizesAndPBM, LayoutStyle};
22use crate::{ConstraintSpace, IndefiniteContainingBlock, LogicalVec2};
23
24#[derive(PartialEq)]
25pub(crate) enum IntrinsicSizingMode {
26 Contribution,
32 Size,
38}
39
40#[derive(Clone, Copy, Debug, Default, MallocSizeOf)]
41pub(crate) struct ContentSizes {
42 pub min_content: Au,
43 pub max_content: Au,
44}
45
46impl ContentSizes {
48 pub fn max(&self, other: Self) -> Self {
49 Self {
50 min_content: self.min_content.max(other.min_content),
51 max_content: self.max_content.max(other.max_content),
52 }
53 }
54
55 pub fn max_assign(&mut self, other: Self) {
56 *self = self.max(other);
57 }
58
59 pub fn union(&self, other: &Self) -> Self {
60 Self {
61 min_content: self.min_content.max(other.min_content),
62 max_content: self.max_content + other.max_content,
63 }
64 }
65
66 pub fn union_assign(&mut self, other: &Self) {
67 self.min_content.max_assign(other.min_content);
68 self.max_content += other.max_content;
69 }
70
71 pub fn map(&self, f: impl Fn(Au) -> Au) -> Self {
72 Self {
73 min_content: f(self.min_content),
74 max_content: f(self.max_content),
75 }
76 }
77}
78
79impl Zero for ContentSizes {
80 fn zero() -> Self {
81 Au::zero().into()
82 }
83
84 fn is_zero(&self) -> bool {
85 self.min_content.is_zero() && self.max_content.is_zero()
86 }
87}
88
89impl Add for ContentSizes {
90 type Output = Self;
91
92 fn add(self, rhs: Self) -> Self {
93 Self {
94 min_content: self.min_content + rhs.min_content,
95 max_content: self.max_content + rhs.max_content,
96 }
97 }
98}
99
100impl AddAssign for ContentSizes {
101 fn add_assign(&mut self, rhs: Self) {
102 *self = self.add(rhs)
103 }
104}
105
106impl ContentSizes {
107 pub fn shrink_to_fit(&self, available_size: Au) -> Au {
112 available_size.min(self.max_content).max(self.min_content)
116 }
117}
118
119impl From<Au> for ContentSizes {
120 fn from(size: Au) -> Self {
121 Self {
122 min_content: size,
123 max_content: size,
124 }
125 }
126}
127
128#[expect(clippy::too_many_arguments)]
129pub(crate) fn outer_inline(
130 base: &LayoutBoxBase,
131 layout_style: &LayoutStyle,
132 containing_block: &IndefiniteContainingBlock,
133 auto_minimum: &LogicalVec2<Au>,
134 auto_block_size_stretches_to_containing_block: bool,
135 is_replaced: bool,
136 establishes_containing_block: bool,
137 get_preferred_aspect_ratio: impl FnOnce(&LogicalVec2<Au>) -> Option<AspectRatio>,
138 get_inline_content_size: impl FnOnce(&ConstraintSpace) -> InlineContentSizesResult,
139 get_tentative_block_content_size: impl FnOnce(Option<AspectRatio>) -> Option<ContentSizes>,
140) -> InlineContentSizesResult {
141 let ContentBoxSizesAndPBM {
142 content_box_sizes,
143 pbm,
144 mut depends_on_block_constraints,
145 preferred_size_computes_to_auto,
146 } = layout_style.content_box_sizes_and_padding_border_margin(containing_block);
147 let margin = pbm.margin.map(|v| v.auto_is(Au::zero));
148 let pbm_sums = LogicalVec2 {
149 block: pbm.padding_border_sums.block + margin.block_sum(),
150 inline: pbm.padding_border_sums.inline + margin.inline_sum(),
151 };
152 let style = layout_style.style();
153 let is_table = layout_style.is_table();
154 let depends_on_contents = Cell::new(false);
157 let content_size = LazyCell::new(|| {
158 depends_on_contents.set(true);
159 let constraint_space = if establishes_containing_block {
160 let available_block_size = containing_block
161 .size
162 .block
163 .map(|v| Au::zero().max(v - pbm_sums.block));
164 let automatic_size = if preferred_size_computes_to_auto.block &&
165 auto_block_size_stretches_to_containing_block
166 {
167 depends_on_block_constraints = true;
168 Size::Stretch
169 } else {
170 Size::FitContent
171 };
172 let aspect_ratio = get_preferred_aspect_ratio(&pbm.padding_border_sums);
173 let block_size =
174 if let Some(block_content_size) = get_tentative_block_content_size(aspect_ratio) {
175 SizeConstraint::Definite(content_box_sizes.block.resolve(
176 Direction::Block,
177 automatic_size,
178 || auto_minimum.block,
179 available_block_size,
180 || block_content_size,
181 is_table,
182 ))
183 } else {
184 content_box_sizes.block.resolve_extrinsic(
185 automatic_size,
186 auto_minimum.block,
187 available_block_size,
188 )
189 };
190 ConstraintSpace::new(block_size, style, aspect_ratio)
191 } else {
192 depends_on_block_constraints = true;
196 ConstraintSpace::new(
200 containing_block.size.block.into(),
201 containing_block.style,
202 None,
203 )
204 };
205 get_inline_content_size(&constraint_space)
206 });
207 let resolve_non_initial = |inline_size, stretch_values| {
208 Some(match inline_size {
209 Size::Initial => return None,
210 Size::Numeric(numeric) => (numeric, numeric, false),
211 Size::MinContent => (
212 content_size.sizes.min_content,
213 content_size.sizes.min_content,
214 content_size.depends_on_block_constraints,
215 ),
216 Size::MaxContent => (
217 content_size.sizes.max_content,
218 content_size.sizes.max_content,
219 content_size.depends_on_block_constraints,
220 ),
221 Size::FitContent => (
222 content_size.sizes.min_content,
223 content_size.sizes.max_content,
224 content_size.depends_on_block_constraints,
225 ),
226 Size::FitContentFunction(size) => {
227 let size = content_size.sizes.shrink_to_fit(size);
228 (size, size, content_size.depends_on_block_constraints)
229 },
230 Size::Stretch => return stretch_values,
231 })
232 };
233 let (mut preferred_min_content, preferred_max_content, preferred_depends_on_block_constraints) =
234 resolve_non_initial(content_box_sizes.inline.preferred, None)
235 .unwrap_or_else(|| resolve_non_initial(Size::FitContent, None).unwrap());
236 let (mut min_min_content, mut min_max_content, mut min_depends_on_block_constraints) =
237 resolve_non_initial(
238 content_box_sizes.inline.min,
239 Some((Au::zero(), Au::zero(), false)),
240 )
241 .unwrap_or((auto_minimum.inline, auto_minimum.inline, false));
242 let (mut max_min_content, max_max_content, max_depends_on_block_constraints) =
243 resolve_non_initial(content_box_sizes.inline.max, None)
244 .map(|(min_content, max_content, depends_on_block_constraints)| {
245 (
246 Some(min_content),
247 Some(max_content),
248 depends_on_block_constraints,
249 )
250 })
251 .unwrap_or_default();
252
253 if is_replaced {
261 let has_percentage = |size: Size<LengthPercentage>| {
262 matches!(size, Size::Numeric(numeric) if numeric.has_percentage())
264 };
265 let writing_mode = containing_block.style.writing_mode;
266 if content_box_sizes.inline.preferred.is_initial() &&
267 has_percentage(style.box_size(writing_mode).inline)
268 {
269 preferred_min_content = Au::zero();
270 }
271 if content_box_sizes.inline.max.is_initial() &&
272 has_percentage(style.max_box_size(writing_mode).inline)
273 {
274 max_min_content = Some(Au::zero());
275 }
276 }
277
278 if is_table {
281 min_min_content.max_assign(content_size.sizes.min_content);
282 min_max_content.max_assign(content_size.sizes.min_content);
283 min_depends_on_block_constraints |= content_size.depends_on_block_constraints;
284 }
285
286 base.outer_inline_content_sizes_depend_on_content
287 .store(depends_on_contents.get(), Ordering::Relaxed);
288 InlineContentSizesResult {
289 sizes: ContentSizes {
290 min_content: preferred_min_content
291 .clamp_between_extremums(min_min_content, max_min_content) +
292 pbm_sums.inline,
293 max_content: preferred_max_content
294 .clamp_between_extremums(min_max_content, max_max_content) +
295 pbm_sums.inline,
296 },
297 depends_on_block_constraints: depends_on_block_constraints &&
298 (preferred_depends_on_block_constraints ||
299 min_depends_on_block_constraints ||
300 max_depends_on_block_constraints),
301 }
302}
303
304#[derive(Clone, Copy, Debug, MallocSizeOf)]
305pub(crate) struct InlineContentSizesResult {
306 pub sizes: ContentSizes,
307 pub depends_on_block_constraints: bool,
308}
309
310pub(crate) trait ComputeInlineContentSizes {
311 fn compute_inline_content_sizes(
312 &self,
313 layout_context: &LayoutContext,
314 constraint_space: &ConstraintSpace,
315 ) -> InlineContentSizesResult;
316
317 fn compute_inline_content_sizes_with_fixup(
321 &self,
322 layout_context: &LayoutContext,
323 constraint_space: &ConstraintSpace,
324 ) -> InlineContentSizesResult {
325 let mut result = self.compute_inline_content_sizes(layout_context, constraint_space);
326 let sizes = &mut result.sizes;
327 sizes.max_content.max_assign(sizes.min_content);
328 result
329 }
330}
331
332#[derive(Clone, Debug, PartialEq)]
335pub(crate) enum Size<T> {
336 Initial,
341 MinContent,
343 MaxContent,
345 FitContent,
347 FitContentFunction(T),
349 Stretch,
351 Numeric(T),
354}
355
356impl<T: Copy> Copy for Size<T> {}
357
358impl<T> Default for Size<T> {
359 #[inline]
360 fn default() -> Self {
361 Self::Initial
362 }
363}
364
365impl<T> Size<T> {
366 #[inline]
367 pub(crate) fn is_initial(&self) -> bool {
368 matches!(self, Self::Initial)
369 }
370}
371
372impl<T: Clone> Size<T> {
373 #[inline]
374 pub(crate) fn to_numeric(&self) -> Option<T> {
375 match self {
376 Self::Numeric(numeric) => Some(numeric).cloned(),
377 _ => None,
378 }
379 }
380
381 #[inline]
382 pub(crate) fn map<U>(&self, f: impl FnOnce(T) -> U) -> Size<U> {
383 match self {
384 Size::Initial => Size::Initial,
385 Size::MinContent => Size::MinContent,
386 Size::MaxContent => Size::MaxContent,
387 Size::FitContent => Size::FitContent,
388 Size::FitContentFunction(size) => Size::FitContentFunction(f(size.clone())),
389 Size::Stretch => Size::Stretch,
390 Size::Numeric(numeric) => Size::Numeric(f(numeric.clone())),
391 }
392 }
393}
394
395impl From<StyleSize> for Size<LengthPercentage> {
396 fn from(size: StyleSize) -> Self {
397 match size {
398 StyleSize::LengthPercentage(lp) => Size::Numeric(lp.0),
399 StyleSize::Auto => Size::Initial,
400 StyleSize::MinContent => Size::MinContent,
401 StyleSize::MaxContent => Size::MaxContent,
402 StyleSize::FitContent => Size::FitContent,
403 StyleSize::FitContentFunction(lp) => Size::FitContentFunction(lp.0),
404 StyleSize::Stretch | StyleSize::WebkitFillAvailable => Size::Stretch,
405 StyleSize::AnchorSizeFunction(_) | StyleSize::AnchorContainingCalcFunction(_) => {
406 unreachable!("anchor-size() should be disabled")
407 },
408 }
409 }
410}
411
412impl From<StyleMaxSize> for Size<LengthPercentage> {
413 fn from(max_size: StyleMaxSize) -> Self {
414 match max_size {
415 StyleMaxSize::LengthPercentage(lp) => Size::Numeric(lp.0),
416 StyleMaxSize::None => Size::Initial,
417 StyleMaxSize::MinContent => Size::MinContent,
418 StyleMaxSize::MaxContent => Size::MaxContent,
419 StyleMaxSize::FitContent => Size::FitContent,
420 StyleMaxSize::FitContentFunction(lp) => Size::FitContentFunction(lp.0),
421 StyleMaxSize::Stretch | StyleMaxSize::WebkitFillAvailable => Size::Stretch,
422 StyleMaxSize::AnchorSizeFunction(_) | StyleMaxSize::AnchorContainingCalcFunction(_) => {
423 unreachable!("anchor-size() should be disabled")
424 },
425 }
426 }
427}
428
429impl Size<LengthPercentage> {
430 #[inline]
431 pub(crate) fn to_percentage(&self) -> Option<Percentage> {
432 self.to_numeric()
433 .and_then(|length_percentage| length_percentage.to_percentage())
434 }
435
436 #[inline]
441 pub(crate) fn resolve_percentages_for_preferred(&self, basis: Option<Au>) -> Size<Au> {
442 match self {
443 Size::Numeric(numeric) => numeric
444 .maybe_to_used_value(basis)
445 .map_or(Size::Initial, Size::Numeric),
446 Size::FitContentFunction(numeric) => {
447 numeric
449 .maybe_to_used_value(basis)
450 .map_or(Size::FitContent, Size::FitContentFunction)
451 },
452 _ => self.map(|_| unreachable!("This shouldn't be called for keywords")),
453 }
454 }
455
456 #[inline]
461 pub(crate) fn resolve_percentages_for_max(&self, basis: Option<Au>) -> Size<Au> {
462 match self {
463 Size::Numeric(numeric) => numeric
464 .maybe_to_used_value(basis)
465 .map_or(Size::Initial, Size::Numeric),
466 Size::FitContentFunction(numeric) => {
467 numeric
469 .maybe_to_used_value(basis)
470 .map_or(Size::MaxContent, Size::FitContentFunction)
471 },
472 _ => self.map(|_| unreachable!("This shouldn't be called for keywords")),
473 }
474 }
475}
476
477impl LogicalVec2<Size<LengthPercentage>> {
478 pub(crate) fn percentages_relative_to_basis(
479 &self,
480 basis: &LogicalVec2<Au>,
481 ) -> LogicalVec2<Size<Au>> {
482 LogicalVec2 {
483 inline: self.inline.map(|value| value.to_used_value(basis.inline)),
484 block: self.block.map(|value| value.to_used_value(basis.block)),
485 }
486 }
487}
488
489impl Size<Au> {
490 #[inline]
493 pub(crate) fn resolve_for_preferred<F: FnOnce() -> ContentSizes>(
494 &self,
495 automatic_size: Size<Au>,
496 stretch_size: Option<Au>,
497 content_size: &LazyCell<ContentSizes, F>,
498 ) -> Au {
499 match self {
500 Self::Initial => {
501 assert!(!automatic_size.is_initial());
502 automatic_size.resolve_for_preferred(automatic_size, stretch_size, content_size)
503 },
504 Self::MinContent => content_size.min_content,
505 Self::MaxContent => content_size.max_content,
506 Self::FitContentFunction(size) => content_size.shrink_to_fit(*size),
507 Self::FitContent => {
508 content_size.shrink_to_fit(stretch_size.unwrap_or_else(|| content_size.max_content))
509 },
510 Self::Stretch => stretch_size.unwrap_or_else(|| content_size.max_content),
511 Self::Numeric(numeric) => *numeric,
512 }
513 }
514
515 #[inline]
518 pub(crate) fn resolve_for_min<F: FnOnce() -> ContentSizes>(
519 &self,
520 get_automatic_minimum_size: impl FnOnce() -> Au,
521 stretch_size: Option<Au>,
522 content_size: &LazyCell<ContentSizes, F>,
523 is_table: bool,
524 ) -> Au {
525 let result = match self {
526 Self::Initial => get_automatic_minimum_size(),
527 Self::MinContent => content_size.min_content,
528 Self::MaxContent => content_size.max_content,
529 Self::FitContentFunction(size) => content_size.shrink_to_fit(*size),
530 Self::FitContent => content_size.shrink_to_fit(stretch_size.unwrap_or_default()),
531 Self::Stretch => stretch_size.unwrap_or_default(),
532 Self::Numeric(numeric) => *numeric,
533 };
534 if is_table {
535 result.max(content_size.min_content)
544 } else {
545 result
546 }
547 }
548
549 #[inline]
552 pub(crate) fn resolve_for_max<F: FnOnce() -> ContentSizes>(
553 &self,
554 stretch_size: Option<Au>,
555 content_size: &LazyCell<ContentSizes, F>,
556 ) -> Option<Au> {
557 Some(match self {
558 Self::Initial => return None,
559 Self::MinContent => content_size.min_content,
560 Self::MaxContent => content_size.max_content,
561 Self::FitContentFunction(size) => content_size.shrink_to_fit(*size),
562 Self::FitContent => content_size.shrink_to_fit(stretch_size.unwrap_or(MAX_AU)),
563 Self::Stretch => return stretch_size,
564 Self::Numeric(numeric) => *numeric,
565 })
566 }
567
568 #[inline]
578 pub(crate) fn maybe_resolve_extrinsic(&self, stretch_size: Option<Au>) -> Option<Au> {
579 match self {
580 Self::Initial |
581 Self::MinContent |
582 Self::MaxContent |
583 Self::FitContent |
584 Self::FitContentFunction(_) => None,
585 Self::Stretch => stretch_size,
586 Self::Numeric(numeric) => Some(*numeric),
587 }
588 }
589}
590
591#[derive(Clone, Copy, Debug, MallocSizeOf, PartialEq)]
594pub(crate) enum SizeConstraint {
595 Definite(Au),
597 MinMax(Au, Option<Au>),
600}
601
602impl Default for SizeConstraint {
603 #[inline]
604 fn default() -> Self {
605 Self::MinMax(Au::default(), None)
606 }
607}
608
609impl SizeConstraint {
610 #[inline]
611 pub(crate) fn new(preferred_size: Option<Au>, min_size: Au, max_size: Option<Au>) -> Self {
612 preferred_size.map_or_else(
613 || Self::MinMax(min_size, max_size),
614 |size| Self::Definite(size.clamp_between_extremums(min_size, max_size)),
615 )
616 }
617
618 #[inline]
619 pub(crate) fn is_definite(self) -> bool {
620 matches!(self, Self::Definite(_))
621 }
622
623 #[inline]
624 pub(crate) fn to_definite(self) -> Option<Au> {
625 match self {
626 Self::Definite(size) => Some(size),
627 _ => None,
628 }
629 }
630
631 #[inline]
632 pub(crate) fn definite_or_min(self) -> Au {
633 match self {
634 Self::Definite(size) => size,
635 Self::MinMax(min, _) => min,
636 }
637 }
638}
639
640impl From<Option<Au>> for SizeConstraint {
641 fn from(size: Option<Au>) -> Self {
642 size.map(SizeConstraint::Definite).unwrap_or_default()
643 }
644}
645
646#[derive(Clone, Debug, Default)]
647pub(crate) struct Sizes {
648 pub preferred: Size<Au>,
650 pub min: Size<Au>,
652 pub max: Size<Au>,
654}
655
656impl Sizes {
657 #[inline]
658 pub(crate) fn new(preferred: Size<Au>, min: Size<Au>, max: Size<Au>) -> Self {
659 Self {
660 preferred,
661 min,
662 max,
663 }
664 }
665
666 #[inline]
668 pub(crate) fn resolve(
669 &self,
670 axis: Direction,
671 automatic_size: Size<Au>,
672 get_automatic_minimum_size: impl FnOnce() -> Au,
673 stretch_size: Option<Au>,
674 get_content_size: impl FnOnce() -> ContentSizes,
675 is_table: bool,
676 ) -> Au {
677 if is_table && axis == Direction::Block {
678 return get_content_size().max_content;
683 }
684 let (preferred, min, max) = self.resolve_each(
685 automatic_size,
686 get_automatic_minimum_size,
687 stretch_size,
688 get_content_size,
689 is_table,
690 );
691 preferred.clamp_between_extremums(min, max)
692 }
693
694 #[inline]
699 pub(crate) fn resolve_each(
700 &self,
701 automatic_size: Size<Au>,
702 get_automatic_minimum_size: impl FnOnce() -> Au,
703 stretch_size: Option<Au>,
704 get_content_size: impl FnOnce() -> ContentSizes,
705 is_table: bool,
706 ) -> (Au, Au, Option<Au>) {
707 let content_size = LazyCell::new(get_content_size);
710 (
711 self.preferred
712 .resolve_for_preferred(automatic_size, stretch_size, &content_size),
713 self.min.resolve_for_min(
714 get_automatic_minimum_size,
715 stretch_size,
716 &content_size,
717 is_table,
718 ),
719 self.max.resolve_for_max(stretch_size, &content_size),
720 )
721 }
722
723 #[inline]
729 pub(crate) fn resolve_extrinsic(
730 &self,
731 automatic_size: Size<Au>,
732 automatic_minimum_size: Au,
733 stretch_size: Option<Au>,
734 ) -> SizeConstraint {
735 let (preferred, min, max) =
736 self.resolve_each_extrinsic(automatic_size, automatic_minimum_size, stretch_size);
737 SizeConstraint::new(preferred, min, max)
738 }
739
740 #[inline]
750 pub(crate) fn resolve_each_extrinsic(
751 &self,
752 automatic_size: Size<Au>,
753 automatic_minimum_size: Au,
754 stretch_size: Option<Au>,
755 ) -> (Option<Au>, Au, Option<Au>) {
756 (
757 if self.preferred.is_initial() {
758 automatic_size.maybe_resolve_extrinsic(stretch_size)
759 } else {
760 self.preferred.maybe_resolve_extrinsic(stretch_size)
761 },
762 self.min
763 .maybe_resolve_extrinsic(stretch_size)
764 .unwrap_or(automatic_minimum_size),
765 self.max.maybe_resolve_extrinsic(stretch_size),
766 )
767 }
768}
769
770struct LazySizeData<'a> {
771 sizes: &'a Sizes,
772 axis: Direction,
773 automatic_size: Size<Au>,
774 get_automatic_minimum_size: fn() -> Au,
775 stretch_size: Option<Au>,
776 is_table: bool,
777}
778
779pub(crate) struct LazySize<'a> {
783 result: OnceCell<Au>,
784 data: Option<LazySizeData<'a>>,
785}
786
787impl<'a> LazySize<'a> {
788 pub(crate) fn new(
789 sizes: &'a Sizes,
790 axis: Direction,
791 automatic_size: Size<Au>,
792 get_automatic_minimum_size: fn() -> Au,
793 stretch_size: Option<Au>,
794 is_table: bool,
795 ) -> Self {
796 Self {
797 result: OnceCell::new(),
798 data: Some(LazySizeData {
799 sizes,
800 axis,
801 automatic_size,
802 get_automatic_minimum_size,
803 stretch_size,
804 is_table,
805 }),
806 }
807 }
808
809 pub(crate) fn intrinsic() -> Self {
817 Self {
818 result: OnceCell::new(),
819 data: None,
820 }
821 }
822
823 pub(crate) fn resolve(&self, get_content_size: impl FnOnce() -> Au) -> Au {
828 *self.result.get_or_init(|| {
829 let Some(ref data) = self.data else {
830 return get_content_size();
831 };
832 data.sizes.resolve(
833 data.axis,
834 data.automatic_size,
835 data.get_automatic_minimum_size,
836 data.stretch_size,
837 || get_content_size().into(),
838 data.is_table,
839 )
840 })
841 }
842}
843
844impl From<Au> for LazySize<'_> {
845 fn from(value: Au) -> Self {
848 let result = OnceCell::new();
849 result.set(value).unwrap();
850 LazySize { result, data: None }
851 }
852}