[LFC] Move geometry data structures to a dedicated file
[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 "LayoutChildIterator.h"
33 #include "Logging.h"
34 #include <wtf/text/TextStream.h>
35
36 namespace WebCore {
37 namespace Layout {
38
39 static bool isStretchedToViewport(const LayoutContext& layoutContext, const Box& layoutBox)
40 {
41     ASSERT(layoutBox.isInFlow());
42     // In quirks mode, body and html stretch to the viewport.
43     if (!layoutContext.inQuirksMode())
44         return false;
45
46     if (!layoutBox.isDocumentBox() && !layoutBox.isBodyBox())
47         return false;
48
49     return layoutBox.style().logicalHeight().isAuto();
50 }
51
52 static const Container& initialContainingBlock(const Box& layoutBox)
53 {
54     auto* containingBlock = layoutBox.containingBlock();
55     while (containingBlock->containingBlock())
56         containingBlock = containingBlock->containingBlock();
57     return *containingBlock;
58 }
59
60 HeightAndMargin BlockFormattingContext::Geometry::inFlowNonReplacedHeightAndMargin(LayoutContext& layoutContext, const Box& layoutBox)
61 {
62     ASSERT(layoutBox.isInFlow() && !layoutBox.replaced());
63
64     auto compute = [&]() -> HeightAndMargin {
65
66         // 10.6.3 Block-level non-replaced elements in normal flow when 'overflow' computes to 'visible'
67         //
68         // If 'margin-top', or 'margin-bottom' are 'auto', their used value is 0.
69         // If 'height' is 'auto', the height depends on whether the element has any block-level children and whether it has padding or borders:
70         // The element's height is the distance from its top content edge to the first applicable of the following:
71         // 1. the bottom edge of the last line box, if the box establishes a inline formatting context with one or more lines
72         // 2. the bottom edge of the bottom (possibly collapsed) margin of its last in-flow child, if the child's bottom margin
73         //    does not collapse with the element's bottom margin
74         // 3. the bottom border edge of the last in-flow child whose top margin doesn't collapse with the element's bottom margin
75         // 4. zero, otherwise
76         // Only children in the normal flow are taken into account (i.e., floating boxes and absolutely positioned boxes are ignored,
77         // and relatively positioned boxes are considered without their offset). Note that the child box may be an anonymous block box.
78
79         auto& style = layoutBox.style();
80         auto containingBlockWidth = layoutContext.displayBoxForLayoutBox(*layoutBox.containingBlock())->contentBoxWidth();
81         auto& displayBox = *layoutContext.displayBoxForLayoutBox(layoutBox);
82
83         VerticalEdges nonCollapsedMargin = { FormattingContext::Geometry::computedValueIfNotAuto(style.marginTop(), containingBlockWidth).value_or(0),
84             FormattingContext::Geometry::computedValueIfNotAuto(style.marginBottom(), containingBlockWidth).value_or(0) }; 
85         VerticalEdges collapsedMargin = { MarginCollapse::marginTop(layoutContext, layoutBox), MarginCollapse::marginBottom(layoutContext, layoutBox) };
86         auto borderAndPaddingTop = displayBox.borderTop() + displayBox.paddingTop();
87         
88         if (!style.logicalHeight().isAuto())
89             return { style.logicalHeight().value(), nonCollapsedMargin, collapsedMargin };
90
91         if (!is<Container>(layoutBox) || !downcast<Container>(layoutBox).hasInFlowChild())
92             return { 0, nonCollapsedMargin, collapsedMargin };
93
94         // 1. the bottom edge of the last line box, if the box establishes a inline formatting context with one or more lines
95         if (layoutBox.establishesInlineFormattingContext()) {
96             // height = lastLineBox().bottom();
97             return { 0, nonCollapsedMargin, collapsedMargin };
98         }
99
100         // 2. the bottom edge of the bottom (possibly collapsed) margin of its last in-flow child, if the child's bottom margin...
101         auto* lastInFlowChild = downcast<Container>(layoutBox).lastInFlowChild();
102         ASSERT(lastInFlowChild);
103         if (!MarginCollapse::isMarginBottomCollapsedWithParent(layoutContext, *lastInFlowChild)) {
104             auto* lastInFlowDisplayBox = layoutContext.displayBoxForLayoutBox(*lastInFlowChild);
105             ASSERT(lastInFlowDisplayBox);
106             return { lastInFlowDisplayBox->bottom() + lastInFlowDisplayBox->marginBottom() - borderAndPaddingTop, nonCollapsedMargin, collapsedMargin };
107         }
108
109         // 3. the bottom border edge of the last in-flow child whose top margin doesn't collapse with the element's bottom margin
110         auto* inFlowChild = lastInFlowChild;
111         while (inFlowChild && MarginCollapse::isMarginTopCollapsedWithParentMarginBottom(*inFlowChild))
112             inFlowChild = inFlowChild->previousInFlowSibling();
113         if (inFlowChild) {
114             auto* inFlowDisplayBox = layoutContext.displayBoxForLayoutBox(*inFlowChild);
115             ASSERT(inFlowDisplayBox);
116             return { inFlowDisplayBox->top() + inFlowDisplayBox->borderBox().height() - borderAndPaddingTop, nonCollapsedMargin, collapsedMargin };
117         }
118
119         // 4. zero, otherwise
120         return { 0, nonCollapsedMargin, collapsedMargin };
121     };
122
123     auto heightAndMargin = compute();
124
125     if (!isStretchedToViewport(layoutContext, layoutBox)) {
126         LOG_WITH_STREAM(FormattingContextLayout, stream << "[Height][Margin] -> inflow non-replaced -> height(" << heightAndMargin.height << "px) margin(" << heightAndMargin.margin.top << "px, " << heightAndMargin.margin.bottom << "px) -> layoutBox(" << &layoutBox << ")");
127         return heightAndMargin;
128     }
129
130     auto initialContainingBlockHeight = layoutContext.displayBoxForLayoutBox(initialContainingBlock(layoutBox))->contentBoxHeight();
131     // Stretch but never overstretch with the margins.
132     if (heightAndMargin.height + heightAndMargin.margin.top + heightAndMargin.margin.bottom < initialContainingBlockHeight)
133         heightAndMargin.height = initialContainingBlockHeight - heightAndMargin.margin.top - heightAndMargin.margin.bottom;
134
135     LOG_WITH_STREAM(FormattingContextLayout, stream << "[Height][Margin] -> inflow non-replaced -> streched to viewport -> height(" << heightAndMargin.height << "px) margin(" << heightAndMargin.margin.top << "px, " << heightAndMargin.margin.bottom << "px) -> layoutBox(" << &layoutBox << ")");
136     return heightAndMargin;
137 }
138
139 WidthAndMargin BlockFormattingContext::Geometry::inFlowNonReplacedWidthAndMargin(LayoutContext& layoutContext, const Box& layoutBox,
140     std::optional<LayoutUnit> precomputedWidth)
141 {
142     ASSERT(layoutBox.isInFlow() && !layoutBox.replaced());
143
144     auto compute = [&]() {
145
146         // 10.3.3 Block-level, non-replaced elements in normal flow
147         //
148         // The following constraints must hold among the used values of the other properties:
149         // 'margin-left' + 'border-left-width' + 'padding-left' + 'width' + 'padding-right' + 'border-right-width' + 'margin-right' = width of containing block
150         //
151         // 1. If 'width' is not 'auto' and 'border-left-width' + 'padding-left' + 'width' + 'padding-right' + 'border-right-width' 
152         //    (plus any of 'margin-left' or 'margin-right' that are not 'auto') is larger than the width of the containing block, then
153         //    any 'auto' values for 'margin-left' or 'margin-right' are, for the following rules, treated as zero.
154         //
155         // 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
156         //    have to be different from its computed value. If the 'direction' property of the containing block has the value 'ltr', the specified value
157         //    of 'margin-right' is ignored and the value is calculated so as to make the equality true. If the value of 'direction' is 'rtl',
158         //    this happens to 'margin-left' instead.
159         //
160         // 3. If there is exactly one value specified as 'auto', its used value follows from the equality.
161         //
162         // 4. If 'width' is set to 'auto', any other 'auto' values become '0' and 'width' follows from the resulting equality.
163         //
164         // 5. If both 'margin-left' and 'margin-right' are 'auto', their used values are equal. This horizontally centers the element with respect to the
165         //    edges of the containing block.
166
167         auto& style = layoutBox.style();
168         auto* containingBlock = layoutBox.containingBlock();
169         auto containingBlockWidth = layoutContext.displayBoxForLayoutBox(*containingBlock)->contentBoxWidth();
170         auto& displayBox = *layoutContext.displayBoxForLayoutBox(layoutBox);
171
172         auto width = FormattingContext::Geometry::computedValueIfNotAuto(precomputedWidth ? Length { precomputedWidth.value(), Fixed } : style.logicalWidth(), containingBlockWidth);
173         auto marginLeft = FormattingContext::Geometry::computedValueIfNotAuto(style.marginLeft(), containingBlockWidth);
174         auto marginRight = FormattingContext::Geometry::computedValueIfNotAuto(style.marginRight(), containingBlockWidth);
175         auto borderLeft = displayBox.borderLeft();
176         auto borderRight = displayBox.borderRight();
177         auto paddingLeft = displayBox.paddingLeft();
178         auto paddingRight = displayBox.paddingRight();
179
180         // #1
181         if (width) {
182             auto horizontalSpaceForMargin = containingBlockWidth - (marginLeft.value_or(0) + borderLeft + paddingLeft + *width + paddingRight + borderRight + marginRight.value_or(0));
183             if (horizontalSpaceForMargin < 0) {
184                 marginLeft = marginLeft.value_or(0);
185                 marginRight = marginRight.value_or(0);
186             }
187         }
188
189         // #2
190         if (width && marginLeft && marginRight) {
191             if (containingBlock->style().isLeftToRightDirection())
192                 marginRight = containingBlockWidth - (*marginLeft + borderLeft + paddingLeft  + *width + paddingRight + borderRight);
193             else
194                 marginLeft = containingBlockWidth - (borderLeft + paddingLeft + *width + paddingRight + borderRight + *marginRight);
195         }
196
197         // #3
198         if (!marginLeft && width && marginRight)
199             marginLeft = containingBlockWidth - (borderLeft + paddingLeft  + *width + paddingRight + borderRight + *marginRight);
200         else if (marginLeft && !width && marginRight)
201             width = containingBlockWidth - (*marginLeft + borderLeft + paddingLeft + paddingRight + borderRight + *marginRight);
202         else if (marginLeft && width && !marginRight)
203             marginRight = containingBlockWidth - (*marginLeft + borderLeft + paddingLeft + *width + paddingRight + borderRight);
204
205         // #4
206         if (!width) {
207             marginLeft = marginLeft.value_or(0);
208             marginRight = marginRight.value_or(0);
209             width = containingBlockWidth - (*marginLeft + borderLeft + paddingLeft + paddingRight + borderRight + *marginRight);
210         }
211
212         // #5
213         if (!marginLeft && !marginRight) {
214             auto horizontalSpaceForMargin = containingBlockWidth - (borderLeft + paddingLeft  + *width + paddingRight + borderRight);
215             marginLeft = marginRight = horizontalSpaceForMargin / 2;
216         }
217
218         ASSERT(width);
219         ASSERT(marginLeft);
220         ASSERT(marginRight);
221
222         return WidthAndMargin { *width, { *marginLeft, *marginRight } };
223     };
224
225     auto widthAndMargin = compute();
226     if (!isStretchedToViewport(layoutContext, layoutBox)) {
227         LOG_WITH_STREAM(FormattingContextLayout, stream << "[Width][Margin] -> inflow non-replaced -> width(" << widthAndMargin.width << "px) margin(" << widthAndMargin.margin.left << "px, " << widthAndMargin.margin.right << "px) -> layoutBox(" << &layoutBox << ")");
228         return widthAndMargin;
229     }
230
231     auto initialContainingBlockWidth = layoutContext.displayBoxForLayoutBox(initialContainingBlock(layoutBox))->contentBoxWidth();
232     auto horizontalMargins = widthAndMargin.margin.left + widthAndMargin.margin.right;
233     // Stretch but never overstretch with the margins.
234     if (widthAndMargin.width + horizontalMargins < initialContainingBlockWidth)
235         widthAndMargin.width = initialContainingBlockWidth - horizontalMargins;
236
237     LOG_WITH_STREAM(FormattingContextLayout, stream << "[Width][Margin] -> inflow non-replaced -> streched to viewport-> width(" << widthAndMargin.width << "px) margin(" << widthAndMargin.margin.left << "px, " << widthAndMargin.margin.right << "px) -> layoutBox(" << &layoutBox << ")");
238     return widthAndMargin;
239 }
240
241 WidthAndMargin BlockFormattingContext::Geometry::inFlowReplacedWidthAndMargin(LayoutContext& layoutContext, const Box& layoutBox)
242 {
243     ASSERT(layoutBox.isInFlow() && layoutBox.replaced());
244
245     // 10.3.4 Block-level, replaced elements in normal flow
246     //
247     // 1. The used value of 'width' is determined as for inline replaced elements.
248     // 2. Then the rules for non-replaced block-level elements are applied to determine the margins.
249
250     // #1
251     auto width = FormattingContext::Geometry::inlineReplacedWidthAndMargin(layoutContext, layoutBox).width;
252     // #2
253     auto margin = inFlowNonReplacedWidthAndMargin(layoutContext, layoutBox, width).margin;
254
255     LOG_WITH_STREAM(FormattingContextLayout, stream << "[Width][Margin] -> inflow replaced -> width(" << width << "px) margin(" << margin.left << "px, " << margin.left << "px) -> layoutBox(" << &layoutBox << ")");
256     return { width, margin };
257 }
258
259 Position BlockFormattingContext::Geometry::staticPosition(LayoutContext& layoutContext, const Box& layoutBox)
260 {
261     // https://www.w3.org/TR/CSS22/visuren.html#block-formatting
262     // In a block formatting context, boxes are laid out one after the other, vertically, beginning at the top of a containing block.
263     // The vertical distance between two sibling boxes is determined by the 'margin' properties.
264     // Vertical margins between adjacent block-level boxes in a block formatting context collapse.
265     // 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).
266
267     LayoutUnit top;
268     auto& containingBlockDisplayBox = *layoutContext.displayBoxForLayoutBox(*layoutBox.containingBlock());
269     if (auto* previousInFlowSibling = layoutBox.previousInFlowSibling()) {
270         auto& previousInFlowDisplayBox = *layoutContext.displayBoxForLayoutBox(*previousInFlowSibling);
271         top = previousInFlowDisplayBox.bottom() + previousInFlowDisplayBox.marginBottom();
272     } else
273         top = containingBlockDisplayBox.contentBoxTop();
274
275     auto left = containingBlockDisplayBox.contentBoxLeft();
276     LOG_WITH_STREAM(FormattingContextLayout, stream << "[Position] -> static -> top(" << top << "px) left(" << left << "px) layoutBox(" << &layoutBox << ")");
277     return { left, top };
278 }
279
280 Position BlockFormattingContext::Geometry::inFlowPositionedPosition(LayoutContext& layoutContext, const Box& layoutBox)
281 {
282     ASSERT(layoutBox.isInFlowPositioned());
283
284     // 9.4.3 Relative positioning
285     //
286     // The 'top' and 'bottom' properties move relatively positioned element(s) up or down without changing their size.
287     // 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.
288     //
289     // 1. If both are 'auto', their used values are both '0'.
290     // 2. If one of them is 'auto', it becomes the negative of the other.
291     // 3. If neither is 'auto', 'bottom' is ignored (i.e., the used value of 'bottom' will be minus the value of 'top').
292
293     auto& style = layoutBox.style();
294     auto& displayBox = *layoutContext.displayBoxForLayoutBox(layoutBox);
295     auto& containingBlock = *layoutBox.containingBlock();
296     auto containingBlockWidth = layoutContext.displayBoxForLayoutBox(containingBlock)->contentBoxWidth();
297
298     auto top = FormattingContext::Geometry::computedValueIfNotAuto(style.logicalTop(), containingBlockWidth);
299     auto bottom = FormattingContext::Geometry::computedValueIfNotAuto(style.logicalBottom(), containingBlockWidth);
300
301     if (!top && !bottom) {
302         // #1
303         top = bottom = { 0 };
304     } else if (!top) {
305         // #2
306         top = -*bottom;
307     } else if (!bottom) {
308         // #3
309         bottom = -*top;
310     } else {
311         // #4
312         bottom = std::nullopt;
313     }
314
315     // For relatively positioned elements, 'left' and 'right' move the box(es) horizontally, without changing their size.
316     // 'Left' moves the boxes to the right, and 'right' moves them to the left.
317     // Since boxes are not split or stretched as a result of 'left' or 'right', the used values are always: left = -right.
318     //
319     // 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).
320     // 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').
321     // 3. If 'right' is specified as 'auto', its used value is minus the value of 'left'.
322     // 4. If neither 'left' nor 'right' is 'auto', the position is over-constrained, and one of them has to be ignored.
323     //    If the 'direction' property of the containing block is 'ltr', the value of 'left' wins and 'right' becomes -'left'.
324     //    If 'direction' of the containing block is 'rtl', 'right' wins and 'left' is ignored.
325
326     auto left = FormattingContext::Geometry::computedValueIfNotAuto(style.logicalLeft(), containingBlockWidth);
327     auto right = FormattingContext::Geometry::computedValueIfNotAuto(style.logicalRight(), containingBlockWidth);
328
329     if (!left && !right) {
330         // #1
331         left = right = { 0 };
332     } else if (!left) {
333         // #2
334         left = -*right;
335     } else if (!right) {
336         // #3
337         right = -*left;
338     } else {
339         // #4
340         auto isLeftToRightDirection = containingBlock.style().isLeftToRightDirection();
341         if (isLeftToRightDirection)
342             right = -*left;
343         else
344             left = std::nullopt;
345     }
346
347     ASSERT(!bottom || *top == -*bottom);
348     ASSERT(!left || *left == -*right);
349
350     auto newTopPosition = displayBox.top() + *top;
351     auto newLeftPosition = displayBox.left() + left.value_or(-*right);
352
353     LOG_WITH_STREAM(FormattingContextLayout, stream << "[Position] -> positioned inflow -> top(" << newTopPosition << "px) left(" << newLeftPosition << "px) layoutBox(" << &layoutBox << ")");
354     return { newLeftPosition, newTopPosition };
355 }
356
357 HeightAndMargin BlockFormattingContext::Geometry::inFlowHeightAndMargin(LayoutContext& layoutContext, const Box& layoutBox)
358 {
359     ASSERT(layoutBox.isInFlow());
360
361     if (!layoutBox.replaced())
362         return inFlowNonReplacedHeightAndMargin(layoutContext, layoutBox);
363     // 10.6.2 Inline replaced elements, block-level replaced elements in normal flow, 'inline-block'
364     // replaced elements in normal flow and floating replaced elements
365     return FormattingContext::Geometry::inlineReplacedHeightAndMargin(layoutContext, layoutBox);
366 }
367
368 WidthAndMargin BlockFormattingContext::Geometry::inFlowWidthAndMargin(LayoutContext& layoutContext, const Box& layoutBox)
369 {
370     ASSERT(layoutBox.isInFlow());
371
372     if (!layoutBox.replaced())
373         return inFlowNonReplacedWidthAndMargin(layoutContext, layoutBox);
374     return inFlowReplacedWidthAndMargin(layoutContext, layoutBox);
375 }
376
377 bool BlockFormattingContext::Geometry::instrinsicWidthConstraintsNeedChildrenWidth(const Box& layoutBox)
378 {
379     if (!is<Container>(layoutBox) || !downcast<Container>(layoutBox).hasInFlowOrFloatingChild())
380         return false;
381     return layoutBox.style().width().isAuto();
382 }
383
384 FormattingContext::InstrinsicWidthConstraints BlockFormattingContext::Geometry::instrinsicWidthConstraints(LayoutContext& layoutContext, const Box& layoutBox)
385 {
386     auto& style = layoutBox.style();
387     if (auto width = FormattingContext::Geometry::fixedValue(style.logicalWidth()))
388         return { *width, *width };
389
390     // Minimum/maximum width can't be depending on the containing block's width.
391     if (!style.logicalWidth().isAuto())
392         return { };
393
394     if (!is<Container>(layoutBox))
395         return { };
396
397     LayoutUnit minimumIntrinsicWidth;
398     LayoutUnit maximumIntrinsicWidth;
399
400     for (auto& child : childrenOfType<Box>(downcast<Container>(layoutBox))) {
401         if (child.isOutOfFlowPositioned())
402             continue;
403         auto& formattingState = layoutContext.formattingStateForBox(child);
404         ASSERT(formattingState.isBlockFormattingState());
405         auto childInstrinsicWidthConstraints = formattingState.instrinsicWidthConstraints(child);
406         ASSERT(childInstrinsicWidthConstraints);
407         
408         auto& style = child.style();
409         auto horizontalMarginBorderAndPadding = FormattingContext::Geometry::fixedValue(style.marginLeft()).value_or(0)
410             + LayoutUnit { style.borderLeftWidth() }
411             + FormattingContext::Geometry::fixedValue(style.paddingLeft()).value_or(0)
412             + FormattingContext::Geometry::fixedValue(style.paddingRight()).value_or(0)
413             + LayoutUnit { style.borderRightWidth() }
414             + FormattingContext::Geometry::fixedValue(style.marginRight()).value_or(0);
415
416         minimumIntrinsicWidth = std::max(minimumIntrinsicWidth, childInstrinsicWidthConstraints->minimum + horizontalMarginBorderAndPadding); 
417         maximumIntrinsicWidth = std::max(maximumIntrinsicWidth, childInstrinsicWidthConstraints->maximum + horizontalMarginBorderAndPadding); 
418     }
419
420     return { minimumIntrinsicWidth, maximumIntrinsicWidth };
421 }
422
423 }
424 }
425
426 #endif