[LFC] Merge width and margin computation for block-level, replaced elements in normal...
[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 static bool isStretchedToViewport(const LayoutContext& layoutContext, const Box& layoutBox)
37 {
38     ASSERT(layoutBox.isInFlow());
39     // In quirks mode, body and html stretch to the viewport.
40     if (!layoutContext.inQuirksMode())
41         return false;
42
43     if (!layoutBox.isDocumentBox() || !layoutBox.isBodyBox())
44         return false;
45
46     return layoutBox.style().logicalHeight().isAuto();
47 }
48
49 static const Container& initialContainingBlock(const Box& layoutBox)
50 {
51     auto* containingBlock = layoutBox.containingBlock();
52     while (containingBlock->containingBlock())
53         containingBlock = containingBlock->containingBlock();
54     return *containingBlock;
55 }
56
57 LayoutUnit BlockFormattingContext::Geometry::inFlowNonReplacedHeight(LayoutContext& layoutContext, const Box& layoutBox)
58 {
59     ASSERT(layoutBox.isInFlow() && !layoutBox.replaced());
60
61     auto compute = [&]() -> LayoutUnit {
62         // https://www.w3.org/TR/CSS22/visudet.html
63         // If 'height' is 'auto', the height depends on whether the element has any block-level children and whether it has padding or borders:
64         // The element's height is the distance from its top content edge to the first applicable of the following:
65         // 1. the bottom edge of the last line box, if the box establishes a inline formatting context with one or more lines
66         // 2. the bottom edge of the bottom (possibly collapsed) margin of its last in-flow child, if the child's bottom margin
67         //    does not collapse with the element's bottom margin
68         // 3. the bottom border edge of the last in-flow child whose top margin doesn't collapse with the element's bottom margin
69         // 4. zero, otherwise
70         // Only children in the normal flow are taken into account (i.e., floating boxes and absolutely positioned boxes are ignored,
71         // and relatively positioned boxes are considered without their offset). Note that the child box may be an anonymous block box.
72         if (!layoutBox.style().logicalHeight().isAuto()) {
73             // FIXME: Only fixed values yet.
74             return layoutBox.style().logicalHeight().value();
75         }
76
77         if (!is<Container>(layoutBox) || !downcast<Container>(layoutBox).hasInFlowChild())
78             return 0;
79
80         // 1. the bottom edge of the last line box, if the box establishes a inline formatting context with one or more lines
81         if (layoutBox.establishesInlineFormattingContext()) {
82             // height = lastLineBox().bottom();
83             return 0;
84         }
85
86         // 2. the bottom edge of the bottom (possibly collapsed) margin of its last in-flow child, if the child's bottom margin...
87         auto* lastInFlowChild = downcast<Container>(layoutBox).lastInFlowChild();
88         ASSERT(lastInFlowChild);
89         if (!BlockFormattingContext::MarginCollapse::isMarginBottomCollapsedWithParent(*lastInFlowChild)) {
90             auto* lastInFlowDisplayBox = layoutContext.displayBoxForLayoutBox(*lastInFlowChild);
91             ASSERT(lastInFlowDisplayBox);
92             return lastInFlowDisplayBox->bottom() + lastInFlowDisplayBox->marginBottom();
93         }
94
95         // 3. the bottom border edge of the last in-flow child whose top margin doesn't collapse with the element's bottom margin
96         auto* inFlowChild = lastInFlowChild;
97         while (inFlowChild && BlockFormattingContext::MarginCollapse::isMarginTopCollapsedWithParentMarginBottom(*inFlowChild))
98             inFlowChild = inFlowChild->previousInFlowSibling();
99         if (inFlowChild) {
100             auto* inFlowDisplayBox = layoutContext.displayBoxForLayoutBox(*inFlowChild);
101             ASSERT(inFlowDisplayBox);
102             return inFlowDisplayBox->top() + inFlowDisplayBox->borderBox().height();
103         }
104
105         // 4. zero, otherwise
106         return 0;
107     };
108
109     auto computedHeight = compute();
110     if (!isStretchedToViewport(layoutContext, layoutBox))
111         return computedHeight;
112     auto initialContainingBlockHeight = layoutContext.displayBoxForLayoutBox(initialContainingBlock(layoutBox))->contentBox().height();
113     return std::max(computedHeight, initialContainingBlockHeight);
114 }
115
116 FormattingContext::Geometry::WidthAndMargin BlockFormattingContext::Geometry::inFlowNonReplacedWidthAndMargin(LayoutContext& layoutContext, const Box& layoutBox,
117     std::optional<LayoutUnit> precomputedWidth)
118 {
119     ASSERT(layoutBox.isInFlow() && !layoutBox.replaced());
120
121     auto compute = [&]() {
122         // 10.3.3 Block-level, non-replaced elements in normal flow
123         // The following constraints must hold among the used values of the other properties:
124         // 'margin-left' + 'border-left-width' + 'padding-left' + 'width' + 'padding-right' + 'border-right-width' + 'margin-right' = width of containing block
125         //
126         // 1. If 'width' is not 'auto' and 'border-left-width' + 'padding-left' + 'width' + 'padding-right' + 'border-right-width' 
127         //    (plus any of 'margin-left' or 'margin-right' that are not 'auto') is larger than the width of the containing block, then
128         //    any 'auto' values for 'margin-left' or 'margin-right' are, for the following rules, treated as zero.
129         //
130         // 2. If all of the above have a computed value other than 'auto', the values are said to be "over-constrained" and one of the used values will
131         //    have to be different from its computed value. If the 'direction' property of the containing block has the value 'ltr', the specified value
132         //    of 'margin-right' is ignored and the value is calculated so as to make the equality true. If the value of 'direction' is 'rtl',
133         //    this happens to 'margin-left' instead.
134         //
135         // 3. If there is exactly one value specified as 'auto', its used value follows from the equality.
136         //
137         // 4. If 'width' is set to 'auto', any other 'auto' values become '0' and 'width' follows from the resulting equality.
138         //
139         // 5. If both 'margin-left' and 'margin-right' are 'auto', their used values are equal. This horizontally centers the element with respect to the
140         //    edges of the containing block.
141
142         auto& style = layoutBox.style();
143         auto width = precomputedWidth ? Length { precomputedWidth.value(), Fixed } : style.logicalWidth();
144         auto* containingBlock = layoutBox.containingBlock();
145         auto containingBlockWidth = layoutContext.displayBoxForLayoutBox(*containingBlock)->width();
146         auto& displayBox = *layoutContext.displayBoxForLayoutBox(layoutBox);
147
148         LayoutUnit computedWidthValue;
149         std::optional<LayoutUnit> computedMarginLeftValue;
150         std::optional<LayoutUnit> computedMarginRightValue;
151
152         auto marginLeft = style.marginLeft();
153         if (!marginLeft.isAuto())
154             computedMarginLeftValue = valueForLength(marginLeft, containingBlockWidth);
155
156         auto marginRight = style.marginRight();
157         if (!marginRight.isAuto())
158             computedMarginRightValue = valueForLength(marginRight, containingBlockWidth);
159
160         auto borderLeft = displayBox.borderLeft();
161         auto borderRight = displayBox.borderRight();
162         auto paddingLeft = displayBox.paddingLeft();
163         auto paddingRight = displayBox.paddingRight();
164
165         // #1
166         if (!width.isAuto()) {
167             computedWidthValue = valueForLength(width, containingBlockWidth);
168             auto horizontalSpaceForMargin = containingBlockWidth - (computedMarginLeftValue.value_or(0) + borderLeft + paddingLeft 
169                 + computedWidthValue + paddingRight + borderRight + computedMarginRightValue.value_or(0));
170
171             if (horizontalSpaceForMargin < 0) {
172                 if (!computedMarginLeftValue)
173                     computedMarginLeftValue = LayoutUnit(0);
174                 if (!computedMarginRightValue)
175                     computedMarginRightValue = LayoutUnit(0);
176             }
177         }
178
179         // #2
180         if (!width.isAuto() && !marginLeft.isAuto() && !marginRight.isAuto()) {
181             ASSERT(computedMarginLeftValue);
182             ASSERT(computedMarginRightValue);
183
184             if (containingBlock->style().isLeftToRightDirection())
185                 computedMarginRightValue = containingBlockWidth - (*computedMarginLeftValue + borderLeft + paddingLeft  + computedWidthValue + paddingRight + borderRight);
186             else
187                 computedMarginLeftValue = containingBlockWidth - (borderLeft + paddingLeft  + computedWidthValue + paddingRight + borderRight + *computedMarginRightValue);
188         }
189
190         // #3
191         if (!computedMarginLeftValue && !marginRight.isAuto() && !width.isAuto()) {
192             ASSERT(computedMarginRightValue);
193             computedMarginLeftValue = containingBlockWidth - (borderLeft + paddingLeft  + computedWidthValue + paddingRight + borderRight + *computedMarginRightValue);
194         } else if (!computedMarginRightValue && !marginLeft.isAuto() && !width.isAuto()) {
195             ASSERT(computedMarginLeftValue);
196             computedMarginRightValue = containingBlockWidth - (*computedMarginLeftValue + borderLeft + paddingLeft  + computedWidthValue + paddingRight + borderRight);
197         }
198
199         // #4
200         if (width.isAuto())
201             computedWidthValue = containingBlockWidth - (computedMarginLeftValue.value_or(0) + borderLeft + paddingLeft + paddingRight + borderRight + computedMarginRightValue.value_or(0));
202
203         // #5
204         if (!computedMarginLeftValue && !computedMarginRightValue) {
205             auto horizontalSpaceForMargin = containingBlockWidth - (borderLeft + paddingLeft  + computedWidthValue + paddingRight + borderRight);
206             computedMarginLeftValue = computedMarginRightValue = horizontalSpaceForMargin / 2;
207         }
208
209         ASSERT(computedMarginLeftValue);
210         ASSERT(computedMarginRightValue);
211
212         return FormattingContext::Geometry::WidthAndMargin { computedWidthValue, { *computedMarginLeftValue, *computedMarginRightValue } };
213     };
214
215     auto computedWidthAndMarginValue = compute();
216     if (!isStretchedToViewport(layoutContext, layoutBox))
217         return computedWidthAndMarginValue;
218     auto initialContainingBlockWidth = layoutContext.displayBoxForLayoutBox(initialContainingBlock(layoutBox))->contentBox().width();
219     return FormattingContext::Geometry::WidthAndMargin { std::max(computedWidthAndMarginValue.width, initialContainingBlockWidth), { } };
220 }
221
222 LayoutPoint BlockFormattingContext::Geometry::staticPosition(LayoutContext& layoutContext, const Box& layoutBox)
223 {
224     // https://www.w3.org/TR/CSS22/visuren.html#block-formatting
225     // In a block formatting context, boxes are laid out one after the other, vertically, beginning at the top of a containing block.
226     // The vertical distance between two sibling boxes is determined by the 'margin' properties.
227     // Vertical margins between adjacent block-level boxes in a block formatting context collapse.
228     // 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).
229     auto containingBlockContentBox = layoutContext.displayBoxForLayoutBox(*layoutBox.containingBlock())->contentBox();
230     // Start from the top of the container's content box.
231     auto top = containingBlockContentBox.top();
232     auto left = containingBlockContentBox.left();
233     if (auto* previousInFlowSibling = layoutBox.previousInFlowSibling()) {
234         auto& previousInFlowDisplayBox = *layoutContext.displayBoxForLayoutBox(*previousInFlowSibling);
235         top = previousInFlowDisplayBox.bottom() + previousInFlowDisplayBox.marginBottom();
236     }
237     auto& displayBox = *layoutContext.displayBoxForLayoutBox(layoutBox);
238     LayoutPoint topLeft = { top, left };
239     topLeft.moveBy({ displayBox.marginLeft(), displayBox.marginTop() });
240     return topLeft;
241 }
242
243 LayoutPoint BlockFormattingContext::Geometry::inFlowPositionedPosition(LayoutContext& layoutContext, const Box& layoutBox)
244 {
245     ASSERT(layoutBox.isInFlowPositioned());
246     // 9.4.3 Relative positioning
247     //
248     // The 'top' and 'bottom' properties move relatively positioned element(s) up or down without changing their size.
249     // 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.
250     //
251     // 1. If both are 'auto', their used values are both '0'.
252     // 2. If one of them is 'auto', it becomes the negative of the other.
253     // 3. If neither is 'auto', 'bottom' is ignored (i.e., the used value of 'bottom' will be minus the value of 'top').
254     auto& displayBox = *layoutContext.displayBoxForLayoutBox(layoutBox);
255     auto& style = layoutBox.style();
256
257     auto top = style.logicalTop();
258     auto bottom = style.logicalBottom();
259     LayoutUnit topDelta;
260
261     if (top.isAuto() && bottom.isAuto()) {
262         // #1
263         topDelta = 0;
264     } else if (top.isAuto()) {
265         // #2
266         topDelta = -bottom.value();
267     } else if (bottom.isAuto()) {
268         // #3 #4
269         topDelta = top.value();
270     }
271
272     // For relatively positioned elements, 'left' and 'right' move the box(es) horizontally, without changing their size.
273     // 'Left' moves the boxes to the right, and 'right' moves them to the left.
274     // Since boxes are not split or stretched as a result of 'left' or 'right', the used values are always: left = -right.
275     //
276     // 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).
277     // 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').
278     // 3. If 'right' is specified as 'auto', its used value is minus the value of 'left'.
279     // 4. If neither 'left' nor 'right' is 'auto', the position is over-constrained, and one of them has to be ignored.
280     //    If the 'direction' property of the containing block is 'ltr', the value of 'left' wins and 'right' becomes -'left'.
281     //    If 'direction' of the containing block is 'rtl', 'right' wins and 'left' is ignored.
282     //
283     auto left = style.logicalLeft();
284     auto right = style.logicalRight();
285     LayoutUnit leftDelta;
286
287     if (left.isAuto() && right.isAuto()) {
288         // #1
289         leftDelta = 0;
290     } else if (left.isAuto()) {
291         // #2
292         leftDelta = -right.value();
293     } else if (right.isAuto()) {
294         // #3
295         leftDelta = left.value();
296     } else {
297         // #4
298         // FIXME: take direction into account
299         leftDelta = left.value();
300     }
301
302     return { displayBox.left() + leftDelta, displayBox.top() + topDelta };
303 }
304
305 LayoutUnit BlockFormattingContext::Geometry::inFlowHeight(LayoutContext& layoutContext, const Box& layoutBox)
306 {
307     ASSERT(layoutBox.isInFlow());
308
309     if (!layoutBox.replaced())
310         return inFlowNonReplacedHeight(layoutContext, layoutBox);
311     // 10.6.2 Inline replaced elements, block-level replaced elements in normal flow, 'inline-block'
312     // replaced elements in normal flow and floating replaced elements
313     return FormattingContext::Geometry::inlineReplacedHeight(layoutContext, layoutBox);
314 }
315
316 FormattingContext::Geometry::WidthAndMargin BlockFormattingContext::Geometry::inFlowWidthAndMargin(LayoutContext& layoutContext, const Box& layoutBox)
317 {
318     ASSERT(layoutBox.isInFlow());
319
320     if (!layoutBox.replaced())
321         return inFlowNonReplacedWidthAndMargin(layoutContext, layoutBox);
322     // 10.3.4 Block-level, replaced elements in normal flow
323     //
324     // 1. The used value of 'width' is determined as for inline replaced elements.
325     // 2. Then the rules for non-replaced block-level elements are applied to determine the margins.
326
327     // #1
328     auto inlineReplacedWidthAndMargin = FormattingContext::Geometry::inlineReplacedWidthAndMargin(layoutContext, layoutBox);
329     // #2
330     auto inlineReplacedWidthAndBlockNonReplacedMargin = Geometry::inFlowNonReplacedWidthAndMargin(layoutContext, layoutBox, inlineReplacedWidthAndMargin.width);
331     ASSERT(inlineReplacedWidthAndMargin.width == inlineReplacedWidthAndBlockNonReplacedMargin.width);
332     return inlineReplacedWidthAndBlockNonReplacedMargin;
333 }
334
335 }
336 }
337
338 #endif