aboutsummaryrefslogtreecommitdiffstats
path: root/components/layout_2020/sizing.rs
blob: 76cc56c7bd7c4376828f8c5d4dc34db209b76b0e (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */

//! https://drafts.csswg.org/css-sizing/

use serde::Serialize;
use style::logical_geometry::WritingMode;
use style::properties::longhands::box_sizing::computed_value::T as BoxSizing;
use style::properties::ComputedValues;
use style::values::computed::Length;
use style::Zero;

use crate::style_ext::ComputedValuesExt;

#[derive(Clone, Debug, Serialize)]
pub(crate) struct ContentSizes {
    pub min_content: Length,
    pub max_content: Length,
}

/// https://drafts.csswg.org/css-sizing/#intrinsic-sizes
impl ContentSizes {
    pub fn zero() -> Self {
        Self {
            min_content: Length::zero(),
            max_content: Length::zero(),
        }
    }

    pub fn map(&self, f: impl Fn(Length) -> Length) -> Self {
        Self {
            min_content: f(self.min_content),
            max_content: f(self.max_content),
        }
    }

    pub fn max(&self, other: Self) -> Self {
        Self {
            min_content: self.min_content.max(other.min_content),
            max_content: self.max_content.max(other.max_content),
        }
    }

    pub fn add(&self, other: &Self) -> Self {
        Self {
            min_content: self.min_content.max(other.min_content),
            max_content: self.max_content + other.max_content,
        }
    }
}

impl ContentSizes {
    /// https://drafts.csswg.org/css2/visudet.html#shrink-to-fit-float
    pub fn shrink_to_fit(&self, available_size: Length) -> Length {
        available_size.max(self.min_content).min(self.max_content)
    }
}

pub(crate) fn outer_inline(
    style: &ComputedValues,
    containing_block_writing_mode: WritingMode,
    get_content_size: impl FnOnce() -> ContentSizes,
) -> ContentSizes {
    let padding = style.padding(containing_block_writing_mode);
    let border = style.border_width(containing_block_writing_mode);
    let margin = style.margin(containing_block_writing_mode);

    // For margins and paddings, a cyclic percentage is resolved against zero
    // for determining intrinsic size contributions.
    // https://drafts.csswg.org/css-sizing-3/#min-percentage-contribution
    let zero = Length::zero();
    let pb_lengths = border.inline_sum() +
        padding.inline_start.percentage_relative_to(zero) +
        padding.inline_end.percentage_relative_to(zero);
    let mut m_lengths = zero;
    if let Some(m) = margin.inline_start.non_auto() {
        m_lengths += m.percentage_relative_to(zero)
    }
    if let Some(m) = margin.inline_end.non_auto() {
        m_lengths += m.percentage_relative_to(zero)
    }

    let box_sizing = style.get_position().box_sizing;
    let inline_size = style
        .box_size(containing_block_writing_mode)
        .inline
        .non_auto()
        // Percentages for 'width' are treated as 'auto'
        .and_then(|lp| lp.to_length());
    let min_inline_size = style
        .min_box_size(containing_block_writing_mode)
        .inline
        // Percentages for 'min-width' are treated as zero
        .percentage_relative_to(zero)
        // FIXME: 'auto' is not zero in Flexbox
        .auto_is(Length::zero);
    let max_inline_size = style
        .max_box_size(containing_block_writing_mode)
        .inline
        // Percentages for 'max-width' are treated as 'none'
        .and_then(|lp| lp.to_length());
    let clamp = |l: Length| l.clamp_between_extremums(min_inline_size, max_inline_size);

    let border_box_sizes = match inline_size {
        Some(non_auto) => {
            let clamped = clamp(non_auto);
            let border_box_size = match box_sizing {
                BoxSizing::ContentBox => clamped + pb_lengths,
                BoxSizing::BorderBox => clamped,
            };
            ContentSizes {
                min_content: border_box_size,
                max_content: border_box_size,
            }
        },
        None => get_content_size().map(|content_box_size| {
            match box_sizing {
                // Clamp to 'min-width' and 'max-width', which are sizing the…
                BoxSizing::ContentBox => clamp(content_box_size) + pb_lengths,
                BoxSizing::BorderBox => clamp(content_box_size + pb_lengths),
            }
        }),
    };

    border_box_sizes.map(|s| s + m_lengths)
}