d5be6ddb9c1a699cf4b7e35a3c0c5d94246265db
[WebKit-https.git] / Source / WebCore / layout / blockformatting / BlockFormattingContextGeometry.cpp
1 /*
2  * Copyright (C) 2018 Apple Inc. All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  * 1. Redistributions of source code must retain the above copyright
8  *    notice, this list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright
10  *    notice, this list of conditions and the following disclaimer in the
11  *    documentation and/or other materials provided with the distribution.
12  *
13  * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
14  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15  * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23  * THE POSSIBILITY OF SUCH DAMAGE.
24  */
25
26 #include "config.h"
27 #include "BlockFormattingContext.h"
28
29 #if ENABLE(LAYOUT_FORMATTING_CONTEXT)
30
31 #include "FormattingContext.h"
32
33 namespace WebCore {
34 namespace Layout {
35
36 LayoutUnit BlockFormattingContext::Geometry::inFlowNonReplacedHeight(LayoutContext& layoutContext, const Box& layoutBox)
37 {
38     ASSERT(layoutBox.isInFlow() && !layoutBox.replaced());
39
40     // https://www.w3.org/TR/CSS22/visudet.html
41     // If 'height' is 'auto', the height depends on whether the element has any block-level children and whether it has padding or borders:
42     // The element's height is the distance from its top content edge to the first applicable of the following:
43     // 1. the bottom edge of the last line box, if the box establishes a inline formatting context with one or more lines
44     // 2. the bottom edge of the bottom (possibly collapsed) margin of its last in-flow child, if the child's bottom margin
45     //    does not collapse with the element's bottom margin
46     // 3. the bottom border edge of the last in-flow child whose top margin doesn't collapse with the element's bottom margin
47     // 4. zero, otherwise
48     // Only children in the normal flow are taken into account (i.e., floating boxes and absolutely positioned boxes are ignored,
49     // and relatively positioned boxes are considered without their offset). Note that the child box may be an anonymous block box.
50     if (!layoutBox.style().logicalHeight().isAuto()) {
51         // FIXME: Only fixed values yet.
52         return layoutBox.style().logicalHeight().value();
53     }
54
55     if (!is<Container>(layoutBox) || !downcast<Container>(layoutBox).hasInFlowChild())
56         return 0;
57
58     // 1. the bottom edge of the last line box, if the box establishes a inline formatting context with one or more lines
59     if (layoutBox.establishesInlineFormattingContext()) {
60         // height = lastLineBox().bottom();
61         return 0;
62     }
63
64     // 2. the bottom edge of the bottom (possibly collapsed) margin of its last in-flow child, if the child's bottom margin...
65     auto* lastInFlowChild = downcast<Container>(layoutBox).lastInFlowChild();
66     ASSERT(lastInFlowChild);
67     if (!BlockFormattingContext::MarginCollapse::isMarginBottomCollapsedWithParent(*lastInFlowChild)) {
68         auto* lastInFlowDisplayBox = layoutContext.displayBoxForLayoutBox(*lastInFlowChild);
69         ASSERT(lastInFlowDisplayBox);
70         return lastInFlowDisplayBox->bottom() + lastInFlowDisplayBox->marginBottom();
71     }
72
73     // 3. the bottom border edge of the last in-flow child whose top margin doesn't collapse with the element's bottom margin
74     auto* inFlowChild = lastInFlowChild;
75     while (inFlowChild && BlockFormattingContext::MarginCollapse::isMarginTopCollapsedWithParentMarginBottom(*inFlowChild))
76         inFlowChild = inFlowChild->previousInFlowSibling();
77     if (inFlowChild) {
78         auto* inFlowDisplayBox = layoutContext.displayBoxForLayoutBox(*inFlowChild);
79         ASSERT(inFlowDisplayBox);
80         return inFlowDisplayBox->top() + inFlowDisplayBox->borderBox().height();
81     }
82
83     // 4. zero, otherwise
84     return 0;
85 }
86
87 LayoutUnit BlockFormattingContext::Geometry::inFlowNonReplacedWidth(LayoutContext& layoutContext, const Box& layoutBox)
88 {
89     ASSERT(layoutBox.isInFlow() && !layoutBox.replaced());
90
91     // 10.3.3 Block-level, non-replaced elements in normal flow
92     // The following constraints must hold among the used values of the other properties:
93     // 'margin-left' + 'border-left-width' + 'padding-left' + 'width' + 'padding-right' + 'border-right-width' + 'margin-right' = width of containing block
94
95     // If 'width' is set to 'auto', any other 'auto' values become '0' and 'width' follows from the resulting equality.
96     auto& style = layoutBox.style();
97     auto containingBlockWidth = layoutContext.displayBoxForLayoutBox(*layoutBox.containingBlock())->width();
98
99     LayoutUnit computedWidthValue;
100     auto width = style.logicalWidth();
101     if (width.isAuto()) {
102         auto& displayBox = *layoutContext.displayBoxForLayoutBox(layoutBox);
103         auto marginLeft = displayBox.marginLeft();
104         auto marginRight = displayBox.marginRight();
105
106         auto paddingLeft = displayBox.paddingLeft();
107         auto paddingRight = displayBox.paddingRight();
108
109         auto borderLeft = displayBox.borderLeft();
110         auto borderRight = displayBox.borderRight();
111
112         computedWidthValue = containingBlockWidth - (marginLeft + borderLeft + paddingLeft + paddingRight + borderRight + marginRight);
113     } else
114         computedWidthValue = valueForLength(width, containingBlockWidth);
115
116     return computedWidthValue;
117 }
118
119 LayoutPoint BlockFormattingContext::Geometry::staticPosition(LayoutContext& layoutContext, const Box& layoutBox)
120 {
121     // https://www.w3.org/TR/CSS22/visuren.html#block-formatting
122     // In a block formatting context, boxes are laid out one after the other, vertically, beginning at the top of a containing block.
123     // The vertical distance between two sibling boxes is determined by the 'margin' properties.
124     // Vertical margins between adjacent block-level boxes in a block formatting context collapse.
125     // In a block formatting context, each box's left outer edge touches the left edge of the containing block (for right-to-left formatting, right edges touch).
126     auto containingBlockContentBox = layoutContext.displayBoxForLayoutBox(*layoutBox.containingBlock())->contentBox();
127     // Start from the top of the container's content box.
128     auto top = containingBlockContentBox.y();
129     auto left = containingBlockContentBox.x();
130     if (auto* previousInFlowSibling = layoutBox.previousInFlowSibling()) {
131         auto& previousInFlowDisplayBox = *layoutContext.displayBoxForLayoutBox(*previousInFlowSibling);
132         top = previousInFlowDisplayBox.bottom() + previousInFlowDisplayBox.marginBottom();
133     }
134     auto& displayBox = *layoutContext.displayBoxForLayoutBox(layoutBox);
135     LayoutPoint topLeft = { top, left };
136     topLeft.moveBy({ displayBox.marginLeft(), displayBox.marginTop() });
137     return topLeft;
138 }
139
140 LayoutPoint BlockFormattingContext::Geometry::inFlowPositionedPosition(LayoutContext& layoutContext, const Box& layoutBox)
141 {
142     ASSERT(layoutBox.isInFlowPositioned());
143     // 9.4.3 Relative positioning
144     //
145     // The 'top' and 'bottom' properties move relatively positioned element(s) up or down without changing their size.
146     // Top' moves the boxes down, and 'bottom' moves them up. Since boxes are not split or stretched as a result of 'top' or 'bottom', the used values are always: top = -bottom.
147     //
148     // 1. If both are 'auto', their used values are both '0'.
149     // 2. If one of them is 'auto', it becomes the negative of the other.
150     // 3. If neither is 'auto', 'bottom' is ignored (i.e., the used value of 'bottom' will be minus the value of 'top').
151     auto& displayBox = *layoutContext.displayBoxForLayoutBox(layoutBox);
152     auto& style = layoutBox.style();
153
154     auto top = style.logicalTop();
155     auto bottom = style.logicalBottom();
156     LayoutUnit topDelta;
157
158     if (top.isAuto() && bottom.isAuto()) {
159         // #1
160         topDelta = 0;
161     } else if (top.isAuto()) {
162         // #2
163         topDelta = -bottom.value();
164     } else if (bottom.isAuto()) {
165         // #3 #4
166         topDelta = top.value();
167     }
168
169     // For relatively positioned elements, 'left' and 'right' move the box(es) horizontally, without changing their size.
170     // 'Left' moves the boxes to the right, and 'right' moves them to the left.
171     // Since boxes are not split or stretched as a result of 'left' or 'right', the used values are always: left = -right.
172     //
173     // 1. If both 'left' and 'right' are 'auto' (their initial values), the used values are '0' (i.e., the boxes stay in their original position).
174     // 2. If 'left' is 'auto', its used value is minus the value of 'right' (i.e., the boxes move to the left by the value of 'right').
175     // 3. If 'right' is specified as 'auto', its used value is minus the value of 'left'.
176     // 4. If neither 'left' nor 'right' is 'auto', the position is over-constrained, and one of them has to be ignored.
177     //    If the 'direction' property of the containing block is 'ltr', the value of 'left' wins and 'right' becomes -'left'.
178     //    If 'direction' of the containing block is 'rtl', 'right' wins and 'left' is ignored.
179     //
180     auto left = style.logicalLeft();
181     auto right = style.logicalRight();
182     LayoutUnit leftDelta;
183
184     if (left.isAuto() && right.isAuto()) {
185         // #1
186         leftDelta = 0;
187     } else if (left.isAuto()) {
188         // #2
189         leftDelta = -right.value();
190     } else if (right.isAuto()) {
191         // #3
192         leftDelta = left.value();
193     } else {
194         // #4
195         // FIXME: take direction into account
196         leftDelta = left.value();
197     }
198
199     return { displayBox.left() + leftDelta, displayBox.top() + topDelta };
200 }
201
202 Display::Box::Edges BlockFormattingContext::Geometry::computedMargin(LayoutContext& layoutContext, const Box& layoutBox)
203 {
204     auto& style = layoutBox.style();
205     auto containingBlockWidth = layoutContext.displayBoxForLayoutBox(*layoutBox.containingBlock())->width();
206
207     return Display::Box::Edges(
208         BlockFormattingContext::MarginCollapse::marginTop(layoutBox),
209         valueForLength(style.marginLeft(), containingBlockWidth),
210         BlockFormattingContext::MarginCollapse::marginBottom(layoutBox),
211         valueForLength(style.marginRight(), containingBlockWidth)
212     );
213 }
214
215 }
216 }
217
218 #endif