1use std::cell::{LazyCell, OnceCell};
8use std::ops::{Add, AddAssign};
9
10use app_units::{Au, MAX_AU};
11use malloc_size_of_derive::MallocSizeOf;
12use style::Zero;
13use style::logical_geometry::Direction;
14use style::values::computed::{
15 LengthPercentage, MaxSize as StyleMaxSize, Percentage, Size as StyleSize,
16};
17
18use crate::context::LayoutContext;
19use crate::style_ext::{AspectRatio, Clamp, ComputedValuesExt, ContentBoxSizesAndPBM, LayoutStyle};
20use crate::{ConstraintSpace, IndefiniteContainingBlock, LogicalVec2};
21
22#[derive(PartialEq)]
23pub(crate) enum IntrinsicSizingMode {
24 Contribution,
30 Size,
36}
37
38#[derive(Clone, Copy, Debug, Default, MallocSizeOf)]
39pub(crate) struct ContentSizes {
40 pub min_content: Au,
41 pub max_content: Au,
42}
43
44impl ContentSizes {
46 pub fn max(&self, other: Self) -> Self {
47 Self {
48 min_content: self.min_content.max(other.min_content),
49 max_content: self.max_content.max(other.max_content),
50 }
51 }
52
53 pub fn max_assign(&mut self, other: Self) {
54 *self = self.max(other);
55 }
56
57 pub fn union(&self, other: &Self) -> Self {
58 Self {
59 min_content: self.min_content.max(other.min_content),
60 max_content: self.max_content + other.max_content,
61 }
62 }
63
64 pub fn map(&self, f: impl Fn(Au) -> Au) -> Self {
65 Self {
66 min_content: f(self.min_content),
67 max_content: f(self.max_content),
68 }
69 }
70}
71
72impl Zero for ContentSizes {
73 fn zero() -> Self {
74 Au::zero().into()
75 }
76
77 fn is_zero(&self) -> bool {
78 self.min_content.is_zero() && self.max_content.is_zero()
79 }
80}
81
82impl Add for ContentSizes {
83 type Output = Self;
84
85 fn add(self, rhs: Self) -> Self {
86 Self {
87 min_content: self.min_content + rhs.min_content,
88 max_content: self.max_content + rhs.max_content,
89 }
90 }
91}
92
93impl AddAssign for ContentSizes {
94 fn add_assign(&mut self, rhs: Self) {
95 *self = self.add(rhs)
96 }
97}
98
99impl ContentSizes {
100 pub fn shrink_to_fit(&self, available_size: Au) -> Au {
105 available_size.min(self.max_content).max(self.min_content)
109 }
110}
111
112impl From<Au> for ContentSizes {
113 fn from(size: Au) -> Self {
114 Self {
115 min_content: size,
116 max_content: size,
117 }
118 }
119}
120
121#[allow(clippy::too_many_arguments)]
122pub(crate) fn outer_inline(
123 layout_style: &LayoutStyle,
124 containing_block: &IndefiniteContainingBlock,
125 auto_minimum: &LogicalVec2<Au>,
126 auto_block_size_stretches_to_containing_block: bool,
127 is_replaced: bool,
128 establishes_containing_block: bool,
129 get_preferred_aspect_ratio: impl FnOnce(&LogicalVec2<Au>) -> Option<AspectRatio>,
130 get_inline_content_size: impl FnOnce(&ConstraintSpace) -> InlineContentSizesResult,
131 get_tentative_block_content_size: impl FnOnce(Option<AspectRatio>) -> Option<ContentSizes>,
132) -> InlineContentSizesResult {
133 let ContentBoxSizesAndPBM {
134 content_box_sizes,
135 pbm,
136 mut depends_on_block_constraints,
137 preferred_size_computes_to_auto,
138 } = layout_style.content_box_sizes_and_padding_border_margin(containing_block);
139 let margin = pbm.margin.map(|v| v.auto_is(Au::zero));
140 let pbm_sums = LogicalVec2 {
141 block: pbm.padding_border_sums.block + margin.block_sum(),
142 inline: pbm.padding_border_sums.inline + margin.inline_sum(),
143 };
144 let style = layout_style.style();
145 let is_table = layout_style.is_table();
146 let content_size = LazyCell::new(|| {
147 let constraint_space = if establishes_containing_block {
148 let available_block_size = containing_block
149 .size
150 .block
151 .map(|v| Au::zero().max(v - pbm_sums.block));
152 let automatic_size = if preferred_size_computes_to_auto.block &&
153 auto_block_size_stretches_to_containing_block
154 {
155 depends_on_block_constraints = true;
156 Size::Stretch
157 } else {
158 Size::FitContent
159 };
160 let aspect_ratio = get_preferred_aspect_ratio(&pbm.padding_border_sums);
161 let block_size =
162 if let Some(block_content_size) = get_tentative_block_content_size(aspect_ratio) {
163 SizeConstraint::Definite(content_box_sizes.block.resolve(
164 Direction::Block,
165 automatic_size,
166 || auto_minimum.block,
167 available_block_size,
168 || block_content_size,
169 is_table,
170 ))
171 } else {
172 content_box_sizes.block.resolve_extrinsic(
173 automatic_size,
174 auto_minimum.block,
175 available_block_size,
176 )
177 };
178 ConstraintSpace::new(block_size, style, aspect_ratio)
179 } else {
180 ConstraintSpace::new(
184 containing_block.size.block.into(),
185 containing_block.style,
186 None,
187 )
188 };
189 get_inline_content_size(&constraint_space)
190 });
191 let resolve_non_initial = |inline_size, stretch_values| {
192 Some(match inline_size {
193 Size::Initial => return None,
194 Size::Numeric(numeric) => (numeric, numeric, false),
195 Size::MinContent => (
196 content_size.sizes.min_content,
197 content_size.sizes.min_content,
198 content_size.depends_on_block_constraints,
199 ),
200 Size::MaxContent => (
201 content_size.sizes.max_content,
202 content_size.sizes.max_content,
203 content_size.depends_on_block_constraints,
204 ),
205 Size::FitContent => (
206 content_size.sizes.min_content,
207 content_size.sizes.max_content,
208 content_size.depends_on_block_constraints,
209 ),
210 Size::FitContentFunction(size) => {
211 let size = content_size.sizes.shrink_to_fit(size);
212 (size, size, content_size.depends_on_block_constraints)
213 },
214 Size::Stretch => return stretch_values,
215 })
216 };
217 let (mut preferred_min_content, preferred_max_content, preferred_depends_on_block_constraints) =
218 resolve_non_initial(content_box_sizes.inline.preferred, None)
219 .unwrap_or_else(|| resolve_non_initial(Size::FitContent, None).unwrap());
220 let (mut min_min_content, mut min_max_content, mut min_depends_on_block_constraints) =
221 resolve_non_initial(
222 content_box_sizes.inline.min,
223 Some((Au::zero(), Au::zero(), false)),
224 )
225 .unwrap_or((auto_minimum.inline, auto_minimum.inline, false));
226 let (mut max_min_content, max_max_content, max_depends_on_block_constraints) =
227 resolve_non_initial(content_box_sizes.inline.max, None)
228 .map(|(min_content, max_content, depends_on_block_constraints)| {
229 (
230 Some(min_content),
231 Some(max_content),
232 depends_on_block_constraints,
233 )
234 })
235 .unwrap_or_default();
236
237 if is_replaced {
245 let has_percentage = |size: Size<LengthPercentage>| {
246 matches!(size, Size::Numeric(numeric) if numeric.has_percentage())
248 };
249 let writing_mode = containing_block.style.writing_mode;
250 if content_box_sizes.inline.preferred.is_initial() &&
251 has_percentage(style.box_size(writing_mode).inline)
252 {
253 preferred_min_content = Au::zero();
254 }
255 if content_box_sizes.inline.max.is_initial() &&
256 has_percentage(style.max_box_size(writing_mode).inline)
257 {
258 max_min_content = Some(Au::zero());
259 }
260 }
261
262 if is_table {
265 min_min_content.max_assign(content_size.sizes.min_content);
266 min_max_content.max_assign(content_size.sizes.min_content);
267 min_depends_on_block_constraints |= content_size.depends_on_block_constraints;
268 }
269
270 InlineContentSizesResult {
271 sizes: ContentSizes {
272 min_content: preferred_min_content
273 .clamp_between_extremums(min_min_content, max_min_content) +
274 pbm_sums.inline,
275 max_content: preferred_max_content
276 .clamp_between_extremums(min_max_content, max_max_content) +
277 pbm_sums.inline,
278 },
279 depends_on_block_constraints: depends_on_block_constraints &&
280 (preferred_depends_on_block_constraints ||
281 min_depends_on_block_constraints ||
282 max_depends_on_block_constraints),
283 }
284}
285
286#[derive(Clone, Copy, Debug, MallocSizeOf)]
287pub(crate) struct InlineContentSizesResult {
288 pub sizes: ContentSizes,
289 pub depends_on_block_constraints: bool,
290}
291
292pub(crate) trait ComputeInlineContentSizes {
293 fn compute_inline_content_sizes(
294 &self,
295 layout_context: &LayoutContext,
296 constraint_space: &ConstraintSpace,
297 ) -> InlineContentSizesResult;
298
299 fn compute_inline_content_sizes_with_fixup(
303 &self,
304 layout_context: &LayoutContext,
305 constraint_space: &ConstraintSpace,
306 ) -> InlineContentSizesResult {
307 let mut result = self.compute_inline_content_sizes(layout_context, constraint_space);
308 let sizes = &mut result.sizes;
309 sizes.max_content.max_assign(sizes.min_content);
310 result
311 }
312}
313
314#[derive(Clone, Debug, PartialEq)]
317pub(crate) enum Size<T> {
318 Initial,
323 MinContent,
325 MaxContent,
327 FitContent,
329 FitContentFunction(T),
331 Stretch,
333 Numeric(T),
336}
337
338impl<T: Copy> Copy for Size<T> {}
339
340impl<T> Default for Size<T> {
341 #[inline]
342 fn default() -> Self {
343 Self::Initial
344 }
345}
346
347impl<T> Size<T> {
348 #[inline]
349 pub(crate) fn is_initial(&self) -> bool {
350 matches!(self, Self::Initial)
351 }
352}
353
354impl<T: Clone> Size<T> {
355 #[inline]
356 pub(crate) fn to_numeric(&self) -> Option<T> {
357 match self {
358 Self::Numeric(numeric) => Some(numeric).cloned(),
359 _ => None,
360 }
361 }
362
363 #[inline]
364 pub(crate) fn map<U>(&self, f: impl FnOnce(T) -> U) -> Size<U> {
365 match self {
366 Size::Initial => Size::Initial,
367 Size::MinContent => Size::MinContent,
368 Size::MaxContent => Size::MaxContent,
369 Size::FitContent => Size::FitContent,
370 Size::FitContentFunction(size) => Size::FitContentFunction(f(size.clone())),
371 Size::Stretch => Size::Stretch,
372 Size::Numeric(numeric) => Size::Numeric(f(numeric.clone())),
373 }
374 }
375}
376
377impl From<StyleSize> for Size<LengthPercentage> {
378 fn from(size: StyleSize) -> Self {
379 match size {
380 StyleSize::LengthPercentage(lp) => Size::Numeric(lp.0),
381 StyleSize::Auto => Size::Initial,
382 StyleSize::MinContent => Size::MinContent,
383 StyleSize::MaxContent => Size::MaxContent,
384 StyleSize::FitContent => Size::FitContent,
385 StyleSize::FitContentFunction(lp) => Size::FitContentFunction(lp.0),
386 StyleSize::Stretch | StyleSize::WebkitFillAvailable => Size::Stretch,
387 StyleSize::AnchorSizeFunction(_) | StyleSize::AnchorContainingCalcFunction(_) => {
388 unreachable!("anchor-size() should be disabled")
389 },
390 }
391 }
392}
393
394impl From<StyleMaxSize> for Size<LengthPercentage> {
395 fn from(max_size: StyleMaxSize) -> Self {
396 match max_size {
397 StyleMaxSize::LengthPercentage(lp) => Size::Numeric(lp.0),
398 StyleMaxSize::None => Size::Initial,
399 StyleMaxSize::MinContent => Size::MinContent,
400 StyleMaxSize::MaxContent => Size::MaxContent,
401 StyleMaxSize::FitContent => Size::FitContent,
402 StyleMaxSize::FitContentFunction(lp) => Size::FitContentFunction(lp.0),
403 StyleMaxSize::Stretch | StyleMaxSize::WebkitFillAvailable => Size::Stretch,
404 StyleMaxSize::AnchorSizeFunction(_) | StyleMaxSize::AnchorContainingCalcFunction(_) => {
405 unreachable!("anchor-size() should be disabled")
406 },
407 }
408 }
409}
410
411impl Size<LengthPercentage> {
412 #[inline]
413 pub(crate) fn to_percentage(&self) -> Option<Percentage> {
414 self.to_numeric()
415 .and_then(|length_percentage| length_percentage.to_percentage())
416 }
417
418 #[inline]
423 pub(crate) fn resolve_percentages_for_preferred(&self, basis: Option<Au>) -> Size<Au> {
424 match self {
425 Size::Numeric(numeric) => numeric
426 .maybe_to_used_value(basis)
427 .map_or(Size::Initial, Size::Numeric),
428 Size::FitContentFunction(numeric) => {
429 numeric
431 .maybe_to_used_value(basis)
432 .map_or(Size::FitContent, Size::FitContentFunction)
433 },
434 _ => self.map(|_| unreachable!("This shouldn't be called for keywords")),
435 }
436 }
437
438 #[inline]
443 pub(crate) fn resolve_percentages_for_max(&self, basis: Option<Au>) -> Size<Au> {
444 match self {
445 Size::Numeric(numeric) => numeric
446 .maybe_to_used_value(basis)
447 .map_or(Size::Initial, Size::Numeric),
448 Size::FitContentFunction(numeric) => {
449 numeric
451 .maybe_to_used_value(basis)
452 .map_or(Size::MaxContent, Size::FitContentFunction)
453 },
454 _ => self.map(|_| unreachable!("This shouldn't be called for keywords")),
455 }
456 }
457}
458
459impl LogicalVec2<Size<LengthPercentage>> {
460 pub(crate) fn percentages_relative_to_basis(
461 &self,
462 basis: &LogicalVec2<Au>,
463 ) -> LogicalVec2<Size<Au>> {
464 LogicalVec2 {
465 inline: self.inline.map(|value| value.to_used_value(basis.inline)),
466 block: self.block.map(|value| value.to_used_value(basis.block)),
467 }
468 }
469}
470
471impl Size<Au> {
472 #[inline]
475 pub(crate) fn resolve_for_preferred<F: FnOnce() -> ContentSizes>(
476 &self,
477 automatic_size: Size<Au>,
478 stretch_size: Option<Au>,
479 content_size: &LazyCell<ContentSizes, F>,
480 ) -> Au {
481 match self {
482 Self::Initial => {
483 assert!(!automatic_size.is_initial());
484 automatic_size.resolve_for_preferred(automatic_size, stretch_size, content_size)
485 },
486 Self::MinContent => content_size.min_content,
487 Self::MaxContent => content_size.max_content,
488 Self::FitContentFunction(size) => content_size.shrink_to_fit(*size),
489 Self::FitContent => {
490 content_size.shrink_to_fit(stretch_size.unwrap_or_else(|| content_size.max_content))
491 },
492 Self::Stretch => stretch_size.unwrap_or_else(|| content_size.max_content),
493 Self::Numeric(numeric) => *numeric,
494 }
495 }
496
497 #[inline]
500 pub(crate) fn resolve_for_min<F: FnOnce() -> ContentSizes>(
501 &self,
502 get_automatic_minimum_size: impl FnOnce() -> Au,
503 stretch_size: Option<Au>,
504 content_size: &LazyCell<ContentSizes, F>,
505 is_table: bool,
506 ) -> Au {
507 let result = match self {
508 Self::Initial => get_automatic_minimum_size(),
509 Self::MinContent => content_size.min_content,
510 Self::MaxContent => content_size.max_content,
511 Self::FitContentFunction(size) => content_size.shrink_to_fit(*size),
512 Self::FitContent => content_size.shrink_to_fit(stretch_size.unwrap_or_default()),
513 Self::Stretch => stretch_size.unwrap_or_default(),
514 Self::Numeric(numeric) => *numeric,
515 };
516 if is_table {
517 result.max(content_size.min_content)
526 } else {
527 result
528 }
529 }
530
531 #[inline]
534 pub(crate) fn resolve_for_max<F: FnOnce() -> ContentSizes>(
535 &self,
536 stretch_size: Option<Au>,
537 content_size: &LazyCell<ContentSizes, F>,
538 ) -> Option<Au> {
539 Some(match self {
540 Self::Initial => return None,
541 Self::MinContent => content_size.min_content,
542 Self::MaxContent => content_size.max_content,
543 Self::FitContentFunction(size) => content_size.shrink_to_fit(*size),
544 Self::FitContent => content_size.shrink_to_fit(stretch_size.unwrap_or(MAX_AU)),
545 Self::Stretch => return stretch_size,
546 Self::Numeric(numeric) => *numeric,
547 })
548 }
549
550 #[inline]
560 pub(crate) fn maybe_resolve_extrinsic(&self, stretch_size: Option<Au>) -> Option<Au> {
561 match self {
562 Self::Initial |
563 Self::MinContent |
564 Self::MaxContent |
565 Self::FitContent |
566 Self::FitContentFunction(_) => None,
567 Self::Stretch => stretch_size,
568 Self::Numeric(numeric) => Some(*numeric),
569 }
570 }
571}
572
573#[derive(Clone, Copy, Debug, MallocSizeOf, PartialEq)]
576pub(crate) enum SizeConstraint {
577 Definite(Au),
579 MinMax(Au, Option<Au>),
582}
583
584impl Default for SizeConstraint {
585 #[inline]
586 fn default() -> Self {
587 Self::MinMax(Au::default(), None)
588 }
589}
590
591impl SizeConstraint {
592 #[inline]
593 pub(crate) fn new(preferred_size: Option<Au>, min_size: Au, max_size: Option<Au>) -> Self {
594 preferred_size.map_or_else(
595 || Self::MinMax(min_size, max_size),
596 |size| Self::Definite(size.clamp_between_extremums(min_size, max_size)),
597 )
598 }
599
600 #[inline]
601 pub(crate) fn is_definite(self) -> bool {
602 matches!(self, Self::Definite(_))
603 }
604
605 #[inline]
606 pub(crate) fn to_definite(self) -> Option<Au> {
607 match self {
608 Self::Definite(size) => Some(size),
609 _ => None,
610 }
611 }
612
613 #[inline]
614 pub(crate) fn definite_or_min(self) -> Au {
615 match self {
616 Self::Definite(size) => size,
617 Self::MinMax(min, _) => min,
618 }
619 }
620}
621
622impl From<Option<Au>> for SizeConstraint {
623 fn from(size: Option<Au>) -> Self {
624 size.map(SizeConstraint::Definite).unwrap_or_default()
625 }
626}
627
628#[derive(Clone, Debug, Default)]
629pub(crate) struct Sizes {
630 pub preferred: Size<Au>,
632 pub min: Size<Au>,
634 pub max: Size<Au>,
636}
637
638impl Sizes {
639 #[inline]
640 pub(crate) fn new(preferred: Size<Au>, min: Size<Au>, max: Size<Au>) -> Self {
641 Self {
642 preferred,
643 min,
644 max,
645 }
646 }
647
648 #[inline]
650 pub(crate) fn resolve(
651 &self,
652 axis: Direction,
653 automatic_size: Size<Au>,
654 get_automatic_minimum_size: impl FnOnce() -> Au,
655 stretch_size: Option<Au>,
656 get_content_size: impl FnOnce() -> ContentSizes,
657 is_table: bool,
658 ) -> Au {
659 if is_table && axis == Direction::Block {
660 return get_content_size().max_content;
665 }
666 let (preferred, min, max) = self.resolve_each(
667 automatic_size,
668 get_automatic_minimum_size,
669 stretch_size,
670 get_content_size,
671 is_table,
672 );
673 preferred.clamp_between_extremums(min, max)
674 }
675
676 #[inline]
681 pub(crate) fn resolve_each(
682 &self,
683 automatic_size: Size<Au>,
684 get_automatic_minimum_size: impl FnOnce() -> Au,
685 stretch_size: Option<Au>,
686 get_content_size: impl FnOnce() -> ContentSizes,
687 is_table: bool,
688 ) -> (Au, Au, Option<Au>) {
689 let content_size = LazyCell::new(get_content_size);
692 (
693 self.preferred
694 .resolve_for_preferred(automatic_size, stretch_size, &content_size),
695 self.min.resolve_for_min(
696 get_automatic_minimum_size,
697 stretch_size,
698 &content_size,
699 is_table,
700 ),
701 self.max.resolve_for_max(stretch_size, &content_size),
702 )
703 }
704
705 #[inline]
711 pub(crate) fn resolve_extrinsic(
712 &self,
713 automatic_size: Size<Au>,
714 automatic_minimum_size: Au,
715 stretch_size: Option<Au>,
716 ) -> SizeConstraint {
717 let (preferred, min, max) =
718 self.resolve_each_extrinsic(automatic_size, automatic_minimum_size, stretch_size);
719 SizeConstraint::new(preferred, min, max)
720 }
721
722 #[inline]
732 pub(crate) fn resolve_each_extrinsic(
733 &self,
734 automatic_size: Size<Au>,
735 automatic_minimum_size: Au,
736 stretch_size: Option<Au>,
737 ) -> (Option<Au>, Au, Option<Au>) {
738 (
739 if self.preferred.is_initial() {
740 automatic_size.maybe_resolve_extrinsic(stretch_size)
741 } else {
742 self.preferred.maybe_resolve_extrinsic(stretch_size)
743 },
744 self.min
745 .maybe_resolve_extrinsic(stretch_size)
746 .unwrap_or(automatic_minimum_size),
747 self.max.maybe_resolve_extrinsic(stretch_size),
748 )
749 }
750}
751
752struct LazySizeData<'a> {
753 sizes: &'a Sizes,
754 axis: Direction,
755 automatic_size: Size<Au>,
756 get_automatic_minimum_size: fn() -> Au,
757 stretch_size: Option<Au>,
758 is_table: bool,
759}
760
761pub(crate) struct LazySize<'a> {
765 result: OnceCell<Au>,
766 data: Option<LazySizeData<'a>>,
767}
768
769impl<'a> LazySize<'a> {
770 pub(crate) fn new(
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 ) -> Self {
778 Self {
779 result: OnceCell::new(),
780 data: Some(LazySizeData {
781 sizes,
782 axis,
783 automatic_size,
784 get_automatic_minimum_size,
785 stretch_size,
786 is_table,
787 }),
788 }
789 }
790
791 pub(crate) fn intrinsic() -> Self {
799 Self {
800 result: OnceCell::new(),
801 data: None,
802 }
803 }
804
805 pub(crate) fn resolve(&self, get_content_size: impl FnOnce() -> Au) -> Au {
810 *self.result.get_or_init(|| {
811 let Some(ref data) = self.data else {
812 return get_content_size();
813 };
814 data.sizes.resolve(
815 data.axis,
816 data.automatic_size,
817 data.get_automatic_minimum_size,
818 data.stretch_size,
819 || get_content_size().into(),
820 data.is_table,
821 )
822 })
823 }
824}
825
826impl From<Au> for LazySize<'_> {
827 fn from(value: Au) -> Self {
830 let result = OnceCell::new();
831 result.set(value).unwrap();
832 LazySize { result, data: None }
833 }
834}