1
use std::default::Default;
2

            
3
use crate::css::Unit::Px;
4
use crate::css::Value::{Keyword, Length};
5
use crate::style::{Display, StyledNode};
6

            
7
pub use self::BoxType::{AnonymousBlock, BlockNode, InlineNode};
8

            
9
23
#[derive(Clone, Copy, Default, Debug, PartialEq)]
10
pub struct Rect {
11
    pub x: f32,
12
    pub y: f32,
13
    pub width: f32,
14
    pub height: f32,
15
}
16

            
17
23
#[derive(Clone, Copy, Default, Debug, PartialEq)]
18
pub struct Dimensions {
19
    pub content: Rect,
20
    pub padding: EdgeSizes,
21
    pub border: EdgeSizes,
22
    pub margin: EdgeSizes,
23
}
24

            
25
69
#[derive(Clone, Copy, Default, Debug, PartialEq)]
26
pub struct EdgeSizes {
27
    pub left: f32,
28
    pub right: f32,
29
    pub top: f32,
30
    pub bottom: f32,
31
}
32

            
33
pub struct LayoutBox<'a> {
34
    pub dimensions: Dimensions,
35
    pub box_type: BoxType<'a>,
36
    pub children: Vec<LayoutBox<'a>>,
37
}
38

            
39
#[derive(Debug)]
40
pub enum BoxType<'a> {
41
    BlockNode(&'a StyledNode<'a>),
42
    InlineNode(&'a StyledNode<'a>),
43
    AnonymousBlock,
44
}
45

            
46
impl<'a> LayoutBox<'a> {
47
21
    fn new(box_type: BoxType) -> LayoutBox {
48
21
        LayoutBox {
49
21
            box_type,
50
21
            dimensions: Default::default(),
51
21
            children: Vec::new(),
52
21
        }
53
21
    }
54

            
55
21
    fn get_style_node(&self) -> &'a StyledNode<'a> {
56
21
        match self.box_type {
57
21
            BlockNode(node) | InlineNode(node) => node,
58
            AnonymousBlock => panic!("Anonymous block box has no style node"),
59
        }
60
21
    }
61
}
62

            
63
2
pub fn layout_tree<'a>(
64
2
    node: &'a StyledNode<'a>,
65
2
    mut containing_block: Dimensions,
66
2
) -> LayoutBox<'a> {
67
2
    // The layout algorithm expects the container height to start at 0.
68
2
    // TODO: Save the initial containing block height, for calculating percent heights.
69
2
    containing_block.content.height = 0.0;
70
2

            
71
2
    let mut root_box = build_layout_tree(node);
72
2
    root_box.layout(containing_block);
73
2
    root_box
74
2
}
75

            
76
/// Build the tree of LayoutBoxes, but don't perform any layout calculations yet.
77
17
fn build_layout_tree<'a>(style_node: &'a StyledNode<'a>) -> LayoutBox<'a> {
78
    // Create the root box.
79
17
    let mut root = LayoutBox::new(match style_node.display() {
80
7
        Display::Block => BlockNode(style_node),
81
10
        Display::Inline => InlineNode(style_node),
82
        Display::None => panic!("Root node has display: none."),
83
    });
84

            
85
    // Create the descendant boxes.
86
32
    for child in &style_node.children {
87
15
        match child.display() {
88
5
            Display::Block => root.children.push(build_layout_tree(child)),
89
10
            Display::Inline => root
90
10
                .get_inline_container()
91
10
                .children
92
10
                .push(build_layout_tree(child)),
93
            Display::None => {} // Don't lay out nodes with `display: none;`
94
        }
95
    }
96
17
    root
97
17
}
98

            
99
impl<'a> LayoutBox<'a> {
100
    /// Lay out a box and its descendants.
101
11
    fn layout(&mut self, containing_block: Dimensions) {
102
11
        match self.box_type {
103
7
            BlockNode(_) => self.layout_block(containing_block),
104
4
            InlineNode(_) | AnonymousBlock => {} // TODO
105
        }
106
11
    }
107

            
108
    /// Lay out a block-level element and its descendants.
109
7
    fn layout_block(&mut self, containing_block: Dimensions) {
110
7
        // Child width can depend on parent width, so we need to calculate this box's width before
111
7
        // laying out its children.
112
7
        self.calculate_block_width(containing_block);
113
7

            
114
7
        // Determine where the box is located within its container.
115
7
        self.calculate_block_position(containing_block);
116
7

            
117
7
        // Recursively lay out the children of this box.
118
7
        self.layout_block_children();
119
7

            
120
7
        // Parent height can depend on child height, so `calculate_height` must be called after the
121
7
        // children are laid out.
122
7
        self.calculate_block_height();
123
7
    }
124

            
125
    /// Calculate the width of a block-level non-replaced element in normal flow.
126
    ///
127
    /// http://www.w3.org/TR/CSS2/visudet.html#blockwidth
128
    ///
129
    /// Sets the horizontal margin/padding/border dimensions, and the `width`.
130
7
    fn calculate_block_width(&mut self, containing_block: Dimensions) {
131
7
        let style = self.get_style_node();
132
7

            
133
7
        // `width` has initial value `auto`.
134
7
        let auto = Keyword("auto".to_string());
135
7
        let mut width = style.value("width").unwrap_or(auto.clone());
136
7

            
137
7
        // margin, border, and padding have initial value 0.
138
7
        let zero = Length(0.0, Px);
139
7

            
140
7
        let mut margin_left = style.lookup("margin-left", "margin", &zero);
141
7
        let mut margin_right = style.lookup("margin-right", "margin", &zero);
142
7

            
143
7
        let border_left = style.lookup("border-left-width", "border-width", &zero);
144
7
        let border_right = style.lookup("border-right-width", "border-width", &zero);
145
7

            
146
7
        let padding_left = style.lookup("padding-left", "padding", &zero);
147
7
        let padding_right = style.lookup("padding-right", "padding", &zero);
148
7

            
149
7
        let total = sum([
150
7
            &margin_left,
151
7
            &margin_right,
152
7
            &border_left,
153
7
            &border_right,
154
7
            &padding_left,
155
7
            &padding_right,
156
7
            &width,
157
7
        ]
158
7
        .iter()
159
49
        .map(|v| v.to_px()));
160
7

            
161
7
        // If width is not auto and the total is wider than the container, treat auto margins as 0.
162
7
        if width != auto && total > containing_block.content.width {
163
            if margin_left == auto {
164
                margin_left = Length(0.0, Px);
165
            }
166
            if margin_right == auto {
167
                margin_right = Length(0.0, Px);
168
            }
169
7
        }
170

            
171
        // Adjust used values so that the above sum equals `containing_block.width`.
172
        // Each arm of the `match` should increase the total width by exactly `underflow`,
173
        // and afterward all values should be absolute lengths in px.
174
7
        let underflow = containing_block.content.width - total;
175
7

            
176
7
        match (width == auto, margin_left == auto, margin_right == auto) {
177
            // If the values are overconstrained, calculate margin_right.
178
2
            (false, false, false) => {
179
2
                margin_right = Length(margin_right.to_px() + underflow, Px);
180
2
            }
181

            
182
            // If exactly one size is auto, its used value follows from the equality.
183
            (false, false, true) => {
184
                margin_right = Length(underflow, Px);
185
            }
186
            (false, true, false) => {
187
                margin_left = Length(underflow, Px);
188
            }
189

            
190
            // If width is set to auto, any other auto values become 0.
191
            (true, _, _) => {
192
4
                if margin_left == auto {
193
                    margin_left = Length(0.0, Px);
194
4
                }
195
4
                if margin_right == auto {
196
                    margin_right = Length(0.0, Px);
197
4
                }
198

            
199
4
                if underflow >= 0.0 {
200
4
                    // Expand width to fill the underflow.
201
4
                    width = Length(underflow, Px);
202
4
                } else {
203
                    // Width can't be negative. Adjust the right margin instead.
204
                    width = Length(0.0, Px);
205
                    margin_right = Length(margin_right.to_px() + underflow, Px);
206
                }
207
            }
208

            
209
            // If margin-left and margin-right are both auto, their used values are equal.
210
1
            (false, true, true) => {
211
1
                margin_left = Length(underflow / 2.0, Px);
212
1
                margin_right = Length(underflow / 2.0, Px);
213
1
            }
214
        }
215

            
216
7
        let d = &mut self.dimensions;
217
7
        d.content.width = width.to_px();
218
7

            
219
7
        d.padding.left = padding_left.to_px();
220
7
        d.padding.right = padding_right.to_px();
221
7

            
222
7
        d.border.left = border_left.to_px();
223
7
        d.border.right = border_right.to_px();
224
7

            
225
7
        d.margin.left = margin_left.to_px();
226
7
        d.margin.right = margin_right.to_px();
227
7
    }
228

            
229
    /// Finish calculating the block's edge sizes, and position it within its containing block.
230
    ///
231
    /// http://www.w3.org/TR/CSS2/visudet.html#normal-block
232
    ///
233
    /// Sets the vertical margin/padding/border dimensions, and the `x`, `y` values.
234
7
    fn calculate_block_position(&mut self, containing_block: Dimensions) {
235
7
        let style = self.get_style_node();
236
7
        let d = &mut self.dimensions;
237
7

            
238
7
        // margin, border, and padding have initial value 0.
239
7
        let zero = Length(0.0, Px);
240
7

            
241
7
        // If margin-top or margin-bottom is `auto`, the used value is zero.
242
7
        d.margin.top = style.lookup("margin-top", "margin", &zero).to_px();
243
7
        d.margin.bottom = style.lookup("margin-bottom", "margin", &zero).to_px();
244
7

            
245
7
        d.border.top = style
246
7
            .lookup("border-top-width", "border-width", &zero)
247
7
            .to_px();
248
7
        d.border.bottom = style
249
7
            .lookup("border-bottom-width", "border-width", &zero)
250
7
            .to_px();
251
7

            
252
7
        d.padding.top = style.lookup("padding-top", "padding", &zero).to_px();
253
7
        d.padding.bottom = style.lookup("padding-bottom", "padding", &zero).to_px();
254
7

            
255
7
        d.content.x = containing_block.content.x + d.margin.left + d.border.left + d.padding.left;
256
7

            
257
7
        // Position the box below all the previous boxes in the container.
258
7
        d.content.y = containing_block.content.height
259
7
            + containing_block.content.y
260
7
            + d.margin.top
261
7
            + d.border.top
262
7
            + d.padding.top;
263
7
    }
264

            
265
    /// Lay out the block's children within its content area.
266
    ///
267
    /// Sets `self.dimensions.height` to the total content height.
268
7
    fn layout_block_children(&mut self) {
269
7
        let d = &mut self.dimensions;
270
16
        for child in &mut self.children {
271
9
            child.layout(*d);
272
9
            // Increment the height so each child is laid out below the previous one.
273
9
            d.content.height += child.dimensions.margin_box().height;
274
9
        }
275
7
    }
276

            
277
    /// Height of a block-level non-replaced element in normal flow with overflow visible.
278
    fn calculate_block_height(&mut self) {
279
        // If the height is set to an explicit length, use that exact length.
280
        // Otherwise, just keep the value set by `layout_block_children`.
281
7
        if let Some(Length(h, Px)) = self.get_style_node().value("height") {
282
4
            self.dimensions.content.height = h;
283
4
        }
284
7
    }
285

            
286
    /// Where a new inline child should go.
287
10
    fn get_inline_container(&mut self) -> &mut LayoutBox<'a> {
288
10
        match self.box_type {
289
4
            InlineNode(_) | AnonymousBlock => self,
290
            BlockNode(_) => {
291
                // If we've just generated an anonymous block box, keep using it.
292
                // Otherwise, create a new one.
293
6
                match self.children.last() {
294
                    Some(&LayoutBox {
295
                        box_type: AnonymousBlock,
296
                        ..
297
2
                    }) => {}
298
4
                    _ => self.children.push(LayoutBox::new(AnonymousBlock)),
299
                }
300
6
                self.children.last_mut().unwrap()
301
            }
302
        }
303
10
    }
304
}
305

            
306
impl Rect {
307
27
    pub fn expanded_by(self, edge: EdgeSizes) -> Rect {
308
27
        Rect {
309
27
            x: self.x - edge.left,
310
27
            y: self.y - edge.top,
311
27
            width: self.width + edge.left + edge.right,
312
27
            height: self.height + edge.top + edge.bottom,
313
27
        }
314
27
    }
315
}
316

            
317
impl Dimensions {
318
    /// The area covered by the content area plus its padding.
319
9
    pub fn padding_box(self) -> Rect {
320
9
        self.content.expanded_by(self.padding)
321
9
    }
322
    /// The area covered by the content area plus padding and borders.
323
9
    pub fn border_box(self) -> Rect {
324
9
        self.padding_box().expanded_by(self.border)
325
9
    }
326
    /// The area covered by the content area plus padding, borders, and margin.
327
9
    pub fn margin_box(self) -> Rect {
328
9
        self.border_box().expanded_by(self.margin)
329
9
    }
330
}
331

            
332
7
fn sum<I>(iter: I) -> f32
333
7
where
334
7
    I: Iterator<Item = f32>,
335
7
{
336
49
    iter.fold(0., |a, b| a + b)
337
7
}
338

            
339
#[cfg(test)]
340
mod tests {
341
    use pretty_assertions::assert_eq;
342

            
343
    use crate::css::*;
344
    use crate::dom::*;
345
    use crate::layout::*;
346
    use crate::style::*;
347

            
348
1
    #[test]
349
1
    fn test_layout() {
350
1
        let document = Node::from(
351
1
            r#"
352
1
            <html>
353
1
                <body class="bar">
354
1
                    <h1>Hi!</h1>
355
1
                    <p>Bye!</p>
356
1
                </body>
357
1
            </html>
358
1
        "#,
359
1
        );
360
1

            
361
1
        let style = Sheet::from(
362
1
            r#"
363
1
            html, body, h1, p {
364
1
                display: block;
365
1
            }
366
1

            
367
1
            html {
368
1
                height: 600px;
369
1
            }
370
1

            
371
1
            body.foo, p {
372
1
                margin: auto;
373
1
                width: 24px;
374
1
            }
375
1
        "#,
376
1
        );
377
1

            
378
1
        let style = style_tree(&document, &style);
379
1

            
380
1
        let mut viewport: Dimensions = Default::default();
381
1
        viewport.content.width = 800.0;
382
1
        viewport.content.height = 600.0;
383
1

            
384
1
        let actual = layout_tree(&style, viewport);
385
1

            
386
1
        let body = &actual.children[0];
387
1
        let h1 = &body.children[0];
388
1
        let p = &body.children[1];
389
1

            
390
1
        assert_eq!(actual.dimensions, viewport);
391
1
        assert_eq!(body.dimensions.content.width, 800.0);
392
1
        assert_eq!(h1.dimensions.content.width, 800.0);
393
1
        assert_eq!(p.dimensions.content.width, 24.0);
394
1
    }
395

            
396
1
    #[test]
397
1
    fn test_layout_inline() {
398
1
        let document = Node::from(
399
1
            "
400
1
            <a>
401
1
                <b>
402
1
                    <c>Hello</c>
403
1
                    <c>world!</c>
404
1
                </b>
405
1
                <b>
406
1
                    <c>Bye</c>
407
1
                    <c>all!</c>
408
1
                </b>
409
1
            </a>
410
1
        ",
411
1
        );
412
1

            
413
1
        let style = Sheet::from(
414
1
            "
415
1
            a, b {
416
1
                display: block;
417
1
            }
418
1

            
419
1
            a {
420
1
                height: 600px;
421
1
            }
422
1

            
423
1
            b {
424
1
                background-color: #ff0000;
425
1
                margin: 24px;
426
1
                width: 100px;
427
1
                height: 100px;
428
1
            }
429
1

            
430
1
            c {
431
1
                display: inline;
432
1
                background-color: blue;
433
1
                margin: 24px;
434
1
                width: 32px;
435
1
                height: 24px;
436
1
            }
437
1
        ",
438
1
        );
439
1

            
440
1
        let applied_styles = style_tree(&document, &style);
441
1

            
442
1
        let mut viewport: Dimensions = Default::default();
443
1
        viewport.content.width = 800.0;
444
1
        viewport.content.height = 600.0;
445
1

            
446
1
        let actual = layout_tree(&applied_styles, viewport);
447
1

            
448
1
        assert_eq!(actual.dimensions, viewport);
449

            
450
1
        let b0 = &actual.children[0];
451
1
        let c0 = &b0.children[0].children[0]; // TODO: unnecessary anonymous box
452
1
        let c1 = &b0.children[0].children[1];
453
1

            
454
1
        assert_eq!(actual.dimensions, viewport);
455
1
        assert_eq!(
456
1
            b0.dimensions,
457
1
            Dimensions {
458
1
                content: Rect {
459
1
                    x: 24.0,
460
1
                    y: 24.0,
461
1
                    width: 100.0,
462
1
                    height: 100.0
463
1
                },
464
1
                padding: EdgeSizes {
465
1
                    left: 0.0,
466
1
                    right: 0.0,
467
1
                    top: 0.0,
468
1
                    bottom: 0.0
469
1
                },
470
1
                border: EdgeSizes {
471
1
                    left: 0.0,
472
1
                    right: 0.0,
473
1
                    top: 0.0,
474
1
                    bottom: 0.0
475
1
                },
476
1
                margin: EdgeSizes {
477
1
                    left: 24.0,
478
1
                    right: 676.0,
479
1
                    top: 24.0,
480
1
                    bottom: 24.0
481
1
                },
482
1
            }
483
1
        );
484

            
485
        // TODO: inline positioning not implemented yet
486

            
487
        // assert_eq!(c0.dimensions, Dimensions {
488
        //     content: Rect { x: 24.0, y: 24.0, width: 32.0, height: 32.0 },
489
        //     padding: EdgeSizes { left: 0.0, right: 0.0, top: 0.0, bottom: 0.0 },
490
        //     border: EdgeSizes { left: 0.0, right: 0.0, top: 0.0, bottom: 0.0 },
491
        //     margin: EdgeSizes { left: 24.0, right: 676.0, top: 24.0, bottom: 24.0 },
492

            
493
        // });
494
        // assert_eq!(c1.dimensions.content.width, 32.0);
495

            
496
1
        if let BoxType::InlineNode(_) = c0.box_type {
497
1
        } else {
498
            panic!();
499
        }
500

            
501
1
        if let BoxType::InlineNode(_) = c1.box_type {
502
1
        } else {
503
            panic!();
504
        }
505
1
    }
506
}