Use Optional::valueOr() instead of Optional::value_or()
[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 #include "InlineFormattingState.h"
33 #include "LayoutChildIterator.h"
34 #include "Logging.h"
35 #include <wtf/text/TextStream.h>
36
37 namespace WebCore {
38 namespace Layout {
39
40 HeightAndMargin BlockFormattingContext::Geometry::inFlowNonReplacedHeightAndMargin(const LayoutState& layoutState, const Box& layoutBox, Optional<LayoutUnit> usedHeight)
41 {
42     ASSERT(layoutBox.isInFlow() && !layoutBox.replaced());
43     ASSERT(layoutBox.isOverflowVisible());
44
45     auto compute = [&]() -> HeightAndMargin {
46
47         // 10.6.3 Block-level non-replaced elements in normal flow when 'overflow' computes to 'visible'
48         //
49         // If 'margin-top', or 'margin-bottom' are 'auto', their used value is 0.
50         // If 'height' is 'auto', the height depends on whether the element has any block-level children and whether it has padding or borders:
51         // The element's height is the distance from its top content edge to the first applicable of the following:
52         // 1. the bottom edge of the last line box, if the box establishes a inline formatting context with one or more lines
53         // 2. the bottom edge of the bottom (possibly collapsed) margin of its last in-flow child, if the child's bottom margin
54         //    does not collapse with the element's bottom margin
55         // 3. the bottom border edge of the last in-flow child whose top margin doesn't collapse with the element's bottom margin
56         // 4. zero, otherwise
57         // Only children in the normal flow are taken into account (i.e., floating boxes and absolutely positioned boxes are ignored,
58         // and relatively positioned boxes are considered without their offset). Note that the child box may be an anonymous block box.
59
60         auto& style = layoutBox.style();
61         auto containingBlockWidth = layoutState.displayBoxForLayoutBox(*layoutBox.containingBlock()).contentBoxWidth();
62         auto& displayBox = layoutState.displayBoxForLayoutBox(layoutBox);
63
64         auto nonCollapsedMargin = VerticalMargin::ComputedValues { computedValueIfNotAuto(style.marginBefore(), containingBlockWidth).valueOr(0),
65             computedValueIfNotAuto(style.marginAfter(), containingBlockWidth).valueOr(0) }; 
66         auto collapsedMargin = VerticalMargin::CollapsedValues { MarginCollapse::marginBefore(layoutState, layoutBox), MarginCollapse::marginAfter(layoutState, layoutBox) };
67         auto borderAndPaddingTop = displayBox.borderTop() + displayBox.paddingTop().valueOr(0);
68
69         auto height = usedHeight ? usedHeight.value() : computedHeightValue(layoutState, layoutBox, HeightType::Normal);
70         if (height)
71             return { height.value(), { nonCollapsedMargin, collapsedMargin } };
72
73         if (!is<Container>(layoutBox) || !downcast<Container>(layoutBox).hasInFlowChild())
74             return { 0, { nonCollapsedMargin, collapsedMargin } };
75
76         // 1. the bottom edge of the last line box, if the box establishes a inline formatting context with one or more lines
77         if (layoutBox.establishesInlineFormattingContext()) {
78             // This is temp and will be replaced by the correct display box once inline runs move over to the display tree.
79             auto& inlineRuns = downcast<InlineFormattingState>(layoutState.establishedFormattingState(layoutBox)).inlineRuns();
80             auto bottomEdge = inlineRuns.isEmpty() ? LayoutUnit() : inlineRuns.last().logicalBottom();
81             return { bottomEdge, { nonCollapsedMargin, collapsedMargin } };
82         }
83
84         // 2. the bottom edge of the bottom (possibly collapsed) margin of its last in-flow child, if the child's bottom margin...
85         auto* lastInFlowChild = downcast<Container>(layoutBox).lastInFlowChild();
86         ASSERT(lastInFlowChild);
87         if (!MarginCollapse::marginAfterCollapsesWithParentMarginAfter(layoutState, *lastInFlowChild)) {
88             auto& lastInFlowDisplayBox = layoutState.displayBoxForLayoutBox(*lastInFlowChild);
89             return { lastInFlowDisplayBox.bottom() + lastInFlowDisplayBox.marginAfter() - borderAndPaddingTop, { nonCollapsedMargin, collapsedMargin } };
90         }
91
92         // 3. the bottom border edge of the last in-flow child whose top margin doesn't collapse with the element's bottom margin
93         auto* inFlowChild = lastInFlowChild;
94         while (inFlowChild && MarginCollapse::marginBeforeCollapsesWithParentMarginAfter(layoutState, *inFlowChild))
95             inFlowChild = inFlowChild->previousInFlowSibling();
96         if (inFlowChild) {
97             auto& inFlowDisplayBox = layoutState.displayBoxForLayoutBox(*inFlowChild);
98             return { inFlowDisplayBox.top() + inFlowDisplayBox.borderBox().height() - borderAndPaddingTop, { nonCollapsedMargin, collapsedMargin } };
99         }
100
101         // 4. zero, otherwise
102         return { 0, { nonCollapsedMargin, collapsedMargin } };
103     };
104
105     auto heightAndMargin = compute();
106
107     LOG_WITH_STREAM(FormattingContextLayout, stream << "[Height][Margin] -> inflow non-replaced -> height(" << heightAndMargin.height << "px) margin(" << heightAndMargin.margin.usedValues().before << "px, " << heightAndMargin.margin.usedValues().after << "px) -> layoutBox(" << &layoutBox << ")");
108     return heightAndMargin;
109 }
110
111 WidthAndMargin BlockFormattingContext::Geometry::inFlowNonReplacedWidthAndMargin(const LayoutState& layoutState, const Box& layoutBox, Optional<LayoutUnit> usedWidth)
112 {
113     ASSERT(layoutBox.isInFlow() && !layoutBox.replaced());
114
115     auto compute = [&]() {
116
117         // 10.3.3 Block-level, non-replaced elements in normal flow
118         //
119         // The following constraints must hold among the used values of the other properties:
120         // 'margin-left' + 'border-left-width' + 'padding-left' + 'width' + 'padding-right' + 'border-right-width' + 'margin-right' = width of containing block
121         //
122         // 1. If 'width' is not 'auto' and 'border-left-width' + 'padding-left' + 'width' + 'padding-right' + 'border-right-width' 
123         //    (plus any of 'margin-left' or 'margin-right' that are not 'auto') is larger than the width of the containing block, then
124         //    any 'auto' values for 'margin-left' or 'margin-right' are, for the following rules, treated as zero.
125         //
126         // 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
127         //    have to be different from its computed value. If the 'direction' property of the containing block has the value 'ltr', the specified value
128         //    of 'margin-right' is ignored and the value is calculated so as to make the equality true. If the value of 'direction' is 'rtl',
129         //    this happens to 'margin-left' instead.
130         //
131         // 3. If there is exactly one value specified as 'auto', its used value follows from the equality.
132         //
133         // 4. If 'width' is set to 'auto', any other 'auto' values become '0' and 'width' follows from the resulting equality.
134         //
135         // 5. If both 'margin-left' and 'margin-right' are 'auto', their used values are equal. This horizontally centers the element with respect to the
136         //    edges of the containing block.
137
138         auto& style = layoutBox.style();
139         auto* containingBlock = layoutBox.containingBlock();
140         auto containingBlockWidth = layoutState.displayBoxForLayoutBox(*containingBlock).contentBoxWidth();
141         auto& displayBox = layoutState.displayBoxForLayoutBox(layoutBox);
142
143         auto width = computedValueIfNotAuto(usedWidth ? Length { usedWidth.value(), Fixed } : style.logicalWidth(), containingBlockWidth);
144         auto marginStart = computedValueIfNotAuto(style.marginStart(), containingBlockWidth);
145         auto marginEnd = computedValueIfNotAuto(style.marginEnd(), containingBlockWidth);
146         auto nonComputedMarginStart = marginStart.valueOr(0);
147         auto nonComputedMarginEnd = marginEnd.valueOr(0);
148         auto borderLeft = displayBox.borderLeft();
149         auto borderRight = displayBox.borderRight();
150         auto paddingLeft = displayBox.paddingLeft().valueOr(0);
151         auto paddingRight = displayBox.paddingRight().valueOr(0);
152
153         // #1
154         if (width) {
155             auto horizontalSpaceForMargin = containingBlockWidth - (marginStart.valueOr(0) + borderLeft + paddingLeft + *width + paddingRight + borderRight + marginEnd.valueOr(0));
156             if (horizontalSpaceForMargin < 0) {
157                 marginStart = marginStart.valueOr(0);
158                 marginEnd = marginEnd.valueOr(0);
159             }
160         }
161
162         // #2
163         if (width && marginStart && marginEnd) {
164             if (containingBlock->style().isLeftToRightDirection())
165                 marginEnd = containingBlockWidth - (*marginStart + borderLeft + paddingLeft  + *width + paddingRight + borderRight);
166             else
167                 marginStart = containingBlockWidth - (borderLeft + paddingLeft + *width + paddingRight + borderRight + *marginEnd);
168         }
169
170         // #3
171         if (!marginStart && width && marginEnd)
172             marginStart = containingBlockWidth - (borderLeft + paddingLeft  + *width + paddingRight + borderRight + *marginEnd);
173         else if (marginStart && !width && marginEnd)
174             width = containingBlockWidth - (*marginStart + borderLeft + paddingLeft + paddingRight + borderRight + *marginEnd);
175         else if (marginStart && width && !marginEnd)
176             marginEnd = containingBlockWidth - (*marginStart + borderLeft + paddingLeft + *width + paddingRight + borderRight);
177
178         // #4
179         if (!width) {
180             marginStart = marginStart.valueOr(0);
181             marginEnd = marginEnd.valueOr(0);
182             width = containingBlockWidth - (*marginStart + borderLeft + paddingLeft + paddingRight + borderRight + *marginEnd);
183         }
184
185         // #5
186         if (!marginStart && !marginEnd) {
187             auto horizontalSpaceForMargin = containingBlockWidth - (borderLeft + paddingLeft  + *width + paddingRight + borderRight);
188             marginStart = marginEnd = horizontalSpaceForMargin / 2;
189         }
190
191         ASSERT(width);
192         ASSERT(marginStart);
193         ASSERT(marginEnd);
194
195         return WidthAndMargin { *width, { *marginStart, *marginEnd }, { nonComputedMarginStart, nonComputedMarginEnd } };
196     };
197
198     auto widthAndMargin = compute();
199     LOG_WITH_STREAM(FormattingContextLayout, stream << "[Width][Margin] -> inflow non-replaced -> width(" << widthAndMargin.width << "px) margin(" << widthAndMargin.margin.start << "px, " << widthAndMargin.margin.end << "px) -> layoutBox(" << &layoutBox << ")");
200     return widthAndMargin;
201 }
202
203 WidthAndMargin BlockFormattingContext::Geometry::inFlowReplacedWidthAndMargin(const LayoutState& layoutState, const Box& layoutBox, Optional<LayoutUnit> usedWidth)
204 {
205     ASSERT(layoutBox.isInFlow() && layoutBox.replaced());
206
207     // 10.3.4 Block-level, replaced elements in normal flow
208     //
209     // 1. The used value of 'width' is determined as for inline replaced elements.
210     // 2. Then the rules for non-replaced block-level elements are applied to determine the margins.
211
212     // #1
213     auto width = inlineReplacedWidthAndMargin(layoutState, layoutBox, usedWidth).width;
214     // #2
215     auto nonReplacedWidthAndMargin = inFlowNonReplacedWidthAndMargin(layoutState, layoutBox, width);
216
217     LOG_WITH_STREAM(FormattingContextLayout, stream << "[Width][Margin] -> inflow replaced -> width(" << width << "px) margin(" << nonReplacedWidthAndMargin.margin.start << "px, " << nonReplacedWidthAndMargin.margin.end << "px) -> layoutBox(" << &layoutBox << ")");
218     return { width, nonReplacedWidthAndMargin.margin, nonReplacedWidthAndMargin.nonComputedMargin };
219 }
220
221 Point BlockFormattingContext::Geometry::staticPosition(const LayoutState& layoutState, const Box& layoutBox)
222 {
223     // https://www.w3.org/TR/CSS22/visuren.html#block-formatting
224     // In a block formatting context, boxes are laid out one after the other, vertically, beginning at the top of a containing block.
225     // The vertical distance between two sibling boxes is determined by the 'margin' properties.
226     // Vertical margins between adjacent block-level boxes in a block formatting context collapse.
227     // 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).
228
229     LayoutUnit top;
230     auto& containingBlockDisplayBox = layoutState.displayBoxForLayoutBox(*layoutBox.containingBlock());
231     if (auto* previousInFlowSibling = layoutBox.previousInFlowSibling()) {
232         auto& previousInFlowDisplayBox = layoutState.displayBoxForLayoutBox(*previousInFlowSibling);
233         top = previousInFlowDisplayBox.bottom() + previousInFlowDisplayBox.marginAfter();
234     } else
235         top = containingBlockDisplayBox.contentBoxTop();
236
237     auto left = containingBlockDisplayBox.contentBoxLeft();
238     LOG_WITH_STREAM(FormattingContextLayout, stream << "[Position] -> static -> top(" << top << "px) left(" << left << "px) layoutBox(" << &layoutBox << ")");
239     return { left, top };
240 }
241
242 HeightAndMargin BlockFormattingContext::Geometry::inFlowHeightAndMargin(const LayoutState& layoutState, const Box& layoutBox, Optional<LayoutUnit> usedHeight)
243 {
244     ASSERT(layoutBox.isInFlow());
245
246     // 10.6.2 Inline replaced elements, block-level replaced elements in normal flow, 'inline-block'
247     // replaced elements in normal flow and floating replaced elements
248     if (layoutBox.replaced())
249         return inlineReplacedHeightAndMargin(layoutState, layoutBox, usedHeight);
250
251     HeightAndMargin heightAndMargin;
252     // TODO: Figure out the case for the document element. Let's just complicated-case it for now.
253     if (layoutBox.isOverflowVisible() && !layoutBox.isDocumentBox())
254         heightAndMargin = inFlowNonReplacedHeightAndMargin(layoutState, layoutBox, usedHeight);
255     else {
256         // 10.6.6 Complicated cases
257         // Block-level, non-replaced elements in normal flow when 'overflow' does not compute to 'visible' (except if the 'overflow' property's value has been propagated to the viewport).
258         heightAndMargin = complicatedCases(layoutState, layoutBox, usedHeight);
259     }
260
261     if (!Quirks::needsStretching(layoutState, layoutBox))
262         return heightAndMargin;
263
264     heightAndMargin = Quirks::stretchedHeight(layoutState, layoutBox, heightAndMargin);
265
266     LOG_WITH_STREAM(FormattingContextLayout, stream << "[Height][Margin] -> inflow non-replaced -> streched to viewport -> height(" << heightAndMargin.height << "px) margin(" << heightAndMargin.margin.usedValues().before << "px, " << heightAndMargin.margin.usedValues().after << "px) -> layoutBox(" << &layoutBox << ")");
267     return heightAndMargin;
268 }
269
270 WidthAndMargin BlockFormattingContext::Geometry::inFlowWidthAndMargin(const LayoutState& layoutState, const Box& layoutBox, Optional<LayoutUnit> usedWidth)
271 {
272     ASSERT(layoutBox.isInFlow());
273
274     if (!layoutBox.replaced())
275         return inFlowNonReplacedWidthAndMargin(layoutState, layoutBox, usedWidth);
276     return inFlowReplacedWidthAndMargin(layoutState, layoutBox, usedWidth);
277 }
278
279 bool BlockFormattingContext::Geometry::instrinsicWidthConstraintsNeedChildrenWidth(const Box& layoutBox)
280 {
281     if (!is<Container>(layoutBox) || !downcast<Container>(layoutBox).hasInFlowOrFloatingChild())
282         return false;
283     return layoutBox.style().width().isAuto();
284 }
285
286 FormattingContext::InstrinsicWidthConstraints BlockFormattingContext::Geometry::instrinsicWidthConstraints(const LayoutState& layoutState, const Box& layoutBox)
287 {
288     auto& style = layoutBox.style();
289     if (auto width = fixedValue(style.logicalWidth()))
290         return { *width, *width };
291
292     // Minimum/maximum width can't be depending on the containing block's width.
293     if (!style.logicalWidth().isAuto())
294         return { };
295
296     if (!is<Container>(layoutBox))
297         return { };
298
299     LayoutUnit minimumIntrinsicWidth;
300     LayoutUnit maximumIntrinsicWidth;
301
302     for (auto& child : childrenOfType<Box>(downcast<Container>(layoutBox))) {
303         if (child.isOutOfFlowPositioned())
304             continue;
305         auto& formattingState = layoutState.formattingStateForBox(child);
306         ASSERT(formattingState.isBlockFormattingState());
307         auto childInstrinsicWidthConstraints = formattingState.instrinsicWidthConstraints(child);
308         ASSERT(childInstrinsicWidthConstraints);
309         
310         auto& style = child.style();
311         auto horizontalMarginBorderAndPadding = fixedValue(style.marginStart()).valueOr(0)
312             + LayoutUnit { style.borderLeftWidth() }
313             + fixedValue(style.paddingLeft()).valueOr(0)
314             + fixedValue(style.paddingRight()).valueOr(0)
315             + LayoutUnit { style.borderRightWidth() }
316             + fixedValue(style.marginEnd()).valueOr(0);
317
318         minimumIntrinsicWidth = std::max(minimumIntrinsicWidth, childInstrinsicWidthConstraints->minimum + horizontalMarginBorderAndPadding); 
319         maximumIntrinsicWidth = std::max(maximumIntrinsicWidth, childInstrinsicWidthConstraints->maximum + horizontalMarginBorderAndPadding); 
320     }
321
322     return { minimumIntrinsicWidth, maximumIntrinsicWidth };
323 }
324
325 LayoutUnit BlockFormattingContext::Geometry::estimatedMarginBefore(const LayoutState& layoutState, const Box& layoutBox)
326 {
327     ASSERT(layoutBox.isBlockLevelBox());
328     // Can't estimate vertical margins for out of flow boxes (and we shouldn't need to do it for float boxes).
329     ASSERT(layoutBox.isInFlow());
330     // Can't cross block formatting context boundary.
331     ASSERT(!layoutBox.establishesBlockFormattingContext());
332
333     // Let's just use the normal path for now.
334     return MarginCollapse::marginBefore(layoutState, layoutBox);
335 }
336
337 LayoutUnit BlockFormattingContext::Geometry::estimatedMarginAfter(const LayoutState& layoutState, const Box& layoutBox)
338 {
339     ASSERT(layoutBox.isBlockLevelBox());
340     // Can't estimate vertical margins for out of flow boxes (and we shouldn't need to do it for float boxes).
341     ASSERT(layoutBox.isInFlow());
342     // Can't cross block formatting context boundary.
343     ASSERT(!layoutBox.establishesBlockFormattingContext());
344
345     // Let's just use the normal path for now.
346     return MarginCollapse::marginAfter(layoutState, layoutBox);
347 }
348
349 }
350 }
351
352 #endif