1use crate::geometry::Edges;
4use crate::style::ComputedStyle;
5
6#[derive(Debug, Clone, Copy, PartialEq, Default)]
8pub struct BoxModel {
9 pub margin: Edges,
10 pub border: Edges,
11 pub padding: Edges,
12}
13
14impl BoxModel {
15 pub fn resolve_border(style: &ComputedStyle) -> Edges {
17 Edges::new(
18 style.border_top_width,
19 style.border_right_width,
20 style.border_bottom_width,
21 style.border_left_width,
22 )
23 }
24
25 pub fn resolve_padding(style: &ComputedStyle, containing_block_width: f32) -> Edges {
27 Edges::new(
30 style.padding_top.resolve(containing_block_width),
31 style.padding_right.resolve(containing_block_width),
32 style.padding_bottom.resolve(containing_block_width),
33 style.padding_left.resolve(containing_block_width),
34 )
35 }
36
37 pub fn resolve_margin(style: &ComputedStyle, containing_block_width: f32) -> Edges {
40 Edges::new(
43 style
44 .margin_top
45 .resolve(containing_block_width)
46 .unwrap_or(0.0),
47 style
48 .margin_right
49 .resolve(containing_block_width)
50 .unwrap_or(0.0),
51 style
52 .margin_bottom
53 .resolve(containing_block_width)
54 .unwrap_or(0.0),
55 style
56 .margin_left
57 .resolve(containing_block_width)
58 .unwrap_or(0.0),
59 )
60 }
61
62 pub fn resolve(style: &ComputedStyle, containing_block_width: f32) -> Self {
64 Self {
65 margin: Self::resolve_margin(style, containing_block_width),
66 border: Self::resolve_border(style),
67 padding: Self::resolve_padding(style, containing_block_width),
68 }
69 }
70
71 pub fn horizontal_total(&self) -> f32 {
73 self.margin.horizontal() + self.border.horizontal() + self.padding.horizontal()
74 }
75
76 pub fn vertical_total(&self) -> f32 {
78 self.margin.vertical() + self.border.vertical() + self.padding.vertical()
79 }
80
81 pub fn horizontal_border_padding(&self) -> f32 {
83 self.border.horizontal() + self.padding.horizontal()
84 }
85
86 pub fn vertical_border_padding(&self) -> f32 {
88 self.border.vertical() + self.padding.vertical()
89 }
90}
91
92pub fn resolve_block_width(style: &ComputedStyle, containing_block_width: f32) -> (f32, Edges) {
98 let border = BoxModel::resolve_border(style);
99 let padding = BoxModel::resolve_padding(style, containing_block_width);
100
101 let border_padding_h = border.horizontal() + padding.horizontal();
102
103 let specified_width = style.width.resolve(containing_block_width);
105
106 let margin_left_specified = style.margin_left.resolve(containing_block_width);
108 let margin_right_specified = style.margin_right.resolve(containing_block_width);
109
110 let (content_width, margin_left, margin_right) = match specified_width {
111 Some(mut w) => {
112 if style.box_sizing == crate::style::BoxSizing::BorderBox {
114 w = (w - border_padding_h).max(0.0);
115 }
116
117 let min_w = style.min_width.resolve(containing_block_width);
119 let max_w = style
120 .max_width
121 .resolve(containing_block_width)
122 .unwrap_or(f32::INFINITY);
123 w = w.max(min_w).min(max_w);
124
125 let remaining = containing_block_width - w - border_padding_h;
126
127 match (margin_left_specified, margin_right_specified) {
128 (Some(ml), Some(mr)) => {
129 let _total = ml + mr;
131 let actual_mr = remaining - ml;
132 (w, ml, actual_mr)
133 }
134 (None, Some(mr)) => {
135 let ml = remaining - mr;
136 (w, ml, mr)
137 }
138 (Some(ml), None) => {
139 let mr = remaining - ml;
140 (w, ml, mr)
141 }
142 (None, None) => {
143 let each = remaining / 2.0;
145 (w, each, each)
146 }
147 }
148 }
149 None => {
150 let ml = margin_left_specified.unwrap_or(0.0);
152 let mr = margin_right_specified.unwrap_or(0.0);
153 let mut w = containing_block_width - border_padding_h - ml - mr;
154
155 let min_w = style.min_width.resolve(containing_block_width);
157 let max_w = style
158 .max_width
159 .resolve(containing_block_width)
160 .unwrap_or(f32::INFINITY);
161 w = w.max(min_w).min(max_w);
162
163 (w.max(0.0), ml, mr)
164 }
165 };
166
167 let margin = Edges::new(
168 style
169 .margin_top
170 .resolve(containing_block_width)
171 .unwrap_or(0.0),
172 margin_right,
173 style
174 .margin_bottom
175 .resolve(containing_block_width)
176 .unwrap_or(0.0),
177 margin_left,
178 );
179
180 (content_width, margin)
181}
182
183#[cfg(test)]
184mod tests {
185 use super::*;
186 use crate::style::ComputedStyle;
187 use crate::values::LengthPercentageAuto;
188
189 #[test]
190 fn test_block_width_auto_fills_container() {
191 let style = ComputedStyle::block();
192 let (width, margin) = resolve_block_width(&style, 800.0);
193 assert_eq!(width, 800.0);
194 assert_eq!(margin.left, 0.0);
195 assert_eq!(margin.right, 0.0);
196 }
197
198 #[test]
199 fn test_block_width_fixed_centers_with_auto_margins() {
200 let mut style = ComputedStyle::block();
201 style.width = LengthPercentageAuto::px(400.0);
202 style.margin_left = LengthPercentageAuto::Auto;
203 style.margin_right = LengthPercentageAuto::Auto;
204 let (width, margin) = resolve_block_width(&style, 800.0);
205 assert_eq!(width, 400.0);
206 assert_eq!(margin.left, 200.0);
207 assert_eq!(margin.right, 200.0);
208 }
209
210 #[test]
211 fn test_block_width_with_padding() {
212 let mut style = ComputedStyle::block();
213 style.padding_left = crate::values::LengthPercentage::px(20.0);
214 style.padding_right = crate::values::LengthPercentage::px(20.0);
215 let (width, _margin) = resolve_block_width(&style, 800.0);
216 assert_eq!(width, 760.0); }
218
219 #[test]
220 fn test_block_width_border_box() {
221 let mut style = ComputedStyle::block();
222 style.width = LengthPercentageAuto::px(400.0);
223 style.box_sizing = crate::style::BoxSizing::BorderBox;
224 style.padding_left = crate::values::LengthPercentage::px(20.0);
225 style.padding_right = crate::values::LengthPercentage::px(20.0);
226 style.border_left_width = 5.0;
227 style.border_right_width = 5.0;
228 let (width, _margin) = resolve_block_width(&style, 800.0);
229 assert_eq!(width, 350.0); }
231}