[LFC] Remove PointInContainingBlock and PositionInContainingBlock
[WebKit-https.git] / Source / WebCore / layout / FormattingContextGeometry.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 "FormattingContext.h"
28
29 #if ENABLE(LAYOUT_FORMATTING_CONTEXT)
30
31 #include "FloatingState.h"
32 #include "FormattingState.h"
33 #include "InlineFormattingState.h"
34
35 namespace WebCore {
36 namespace Layout {
37
38 static inline bool isHeightAuto(const Box& layoutBox)
39 {
40     // 10.5 Content height: the 'height' property
41     //
42     // The percentage is calculated with respect to the height of the generated box's containing block.
43     // If the height of the containing block is not specified explicitly (i.e., it depends on content height),
44     // and this element is not absolutely positioned, the used height is calculated as if 'auto' was specified.
45
46     auto height = layoutBox.style().logicalHeight();
47     if (height.isAuto())
48         return true;
49
50     if (height.isPercent()) {
51         if (layoutBox.isOutOfFlowPositioned())
52             return false;
53
54         return !layoutBox.containingBlock()->style().logicalHeight().isFixed();
55     }
56
57     return false;
58 }
59
60 std::optional<LayoutUnit> FormattingContext::Geometry::computedHeightValue(const LayoutState& layoutState, const Box& layoutBox, HeightType heightType)
61 {
62     auto& style = layoutBox.style();
63     auto height = heightType == HeightType::Normal ? style.logicalHeight() : heightType == HeightType::Min ? style.logicalMinHeight() : style.logicalMaxHeight();
64     if (height.isUndefined() || height.isAuto())
65         return { };
66
67     if (height.isFixed())
68         return { height.value() };
69
70     std::optional<LayoutUnit> containingBlockHeightValue;
71     if (layoutBox.isOutOfFlowPositioned()) {
72         // Containing block's height is already computed since we layout the out-of-flow boxes as the last step.
73         containingBlockHeightValue = layoutState.displayBoxForLayoutBox(*layoutBox.containingBlock()).height();
74     } else {
75         auto computedHeightValueForQuirksMode = [&]() -> LayoutUnit {
76             // In quirks mode, we go and travers the containing block chain to find a block level box with fixed height value, even if it means leaving
77             // the current formatting context. FIXME: surely we need to do some tricks here when block direction support is added.
78             auto* containingBlock = layoutBox.containingBlock();
79             LayoutUnit bodyAndDocumentVerticalMarginsPaddingsAndBorders;
80             while (containingBlock) {
81                 auto containingBlockHeight = containingBlock->style().logicalHeight();
82                 if (containingBlockHeight.isFixed())
83                     return containingBlockHeight.value() - bodyAndDocumentVerticalMarginsPaddingsAndBorders;
84
85                 // If the only fixed value box we find is the ICB, then ignore the body and the document (vertical) margin, padding and border. So much quirkiness.
86                 // -and it's totally insane because now we freely travel across formatting context boundaries and computed margins are nonexistent.
87                 if (containingBlock->isBodyBox() || containingBlock->isDocumentBox()) {
88                     auto& displayBox = layoutState.displayBoxForLayoutBox(*containingBlock);
89
90                     auto verticalMargins = computedNonCollapsedVerticalMarginValue(layoutState, *containingBlock);
91                     auto verticalPaddings = displayBox.paddingTop().value_or(0) + displayBox.paddingBottom().value_or(0);
92                     auto verticalBorders = displayBox.borderTop() + displayBox.borderBottom();
93                     bodyAndDocumentVerticalMarginsPaddingsAndBorders += verticalMargins.top + verticalMargins.bottom + verticalPaddings + verticalBorders;
94                 }
95
96                 containingBlock = containingBlock->containingBlock();
97             }
98             // Initial containing block has to have a height.
99             return layoutState.displayBoxForLayoutBox(layoutBox.initialContainingBlock()).contentBox().height() - bodyAndDocumentVerticalMarginsPaddingsAndBorders;
100         };
101
102         if (layoutState.inQuirksMode())
103             containingBlockHeightValue = computedHeightValueForQuirksMode();
104         else {
105             auto containingBlockHeight = layoutBox.containingBlock()->style().logicalHeight();
106             if (containingBlockHeight.isFixed())
107                 containingBlockHeightValue = { containingBlockHeight.value() };
108         }
109     }
110
111     if (!containingBlockHeightValue)
112         return { };
113
114     return valueForLength(height, *containingBlockHeightValue);
115 }
116
117 static LayoutUnit contentHeightForFormattingContextRoot(const LayoutState& layoutState, const Box& layoutBox)
118 {
119     ASSERT(isHeightAuto(layoutBox) && (layoutBox.establishesFormattingContext() || layoutBox.isDocumentBox()));
120
121     // 10.6.7 'Auto' heights for block formatting context roots
122
123     // If it only has inline-level children, the height is the distance between the top of the topmost line box and the bottom of the bottommost line box.
124     // If it has block-level children, the height is the distance between the top margin-edge of the topmost block-level
125     // child box and the bottom margin-edge of the bottommost block-level child box.
126
127     // In addition, if the element has any floating descendants whose bottom margin edge is below the element's bottom content edge,
128     // then the height is increased to include those edges. Only floats that participate in this block formatting context are taken
129     // into account, e.g., floats inside absolutely positioned descendants or other floats are not.
130     if (!is<Container>(layoutBox) || !downcast<Container>(layoutBox).hasInFlowOrFloatingChild())
131         return 0;
132
133     LayoutUnit top;
134     LayoutUnit bottom;
135     auto& formattingRootContainer = downcast<Container>(layoutBox);
136     if (formattingRootContainer.establishesInlineFormattingContext()) {
137         // This is temp and will be replaced by the correct display box once inline runs move over to the display tree.
138         auto& inlineRuns = downcast<InlineFormattingState>(layoutState.establishedFormattingState(layoutBox)).inlineRuns();
139         if (!inlineRuns.isEmpty()) {
140             top = inlineRuns[0].logicalTop();
141             bottom =  inlineRuns.last().logicalBottom();
142         }
143     } else if (formattingRootContainer.establishesBlockFormattingContext() || layoutBox.isDocumentBox()) {
144         auto& firstDisplayBox = layoutState.displayBoxForLayoutBox(*formattingRootContainer.firstInFlowChild());
145         auto& lastDisplayBox = layoutState.displayBoxForLayoutBox(*formattingRootContainer.lastInFlowChild());
146         top = firstDisplayBox.rectWithMargin().top();
147         bottom = lastDisplayBox.rectWithMargin().bottom();
148     }
149
150     auto* formattingContextRoot = &layoutBox;
151     // TODO: The document renderer is not a formatting context root by default at all. Need to find out what it is.
152     if (!layoutBox.establishesFormattingContext()) {
153         ASSERT(layoutBox.isDocumentBox());
154         formattingContextRoot = &layoutBox.formattingContextRoot();
155     }
156
157     auto floatsBottom = layoutState.establishedFormattingState(*formattingContextRoot).floatingState().bottom(*formattingContextRoot);
158     if (floatsBottom)
159         bottom = std::max<LayoutUnit>(*floatsBottom, bottom);
160
161     auto computedHeight = bottom - top;
162     LOG_WITH_STREAM(FormattingContextLayout, stream << "[Height] -> content height for formatting context root -> height(" << computedHeight << "px) layoutBox("<< &layoutBox << ")");
163     return computedHeight;
164 }
165
166 std::optional<LayoutUnit> FormattingContext::Geometry::computedValueIfNotAuto(const Length& geometryProperty, LayoutUnit containingBlockWidth)
167 {
168     if (geometryProperty.isUndefined())
169         return std::nullopt;
170
171     if (geometryProperty.isAuto())
172         return std::nullopt;
173
174     return valueForLength(geometryProperty, containingBlockWidth);
175 }
176
177 std::optional<LayoutUnit> FormattingContext::Geometry::fixedValue(const Length& geometryProperty)
178 {
179     if (!geometryProperty.isFixed())
180         return std::nullopt;
181     return { geometryProperty.value() };
182 }
183
184 // https://www.w3.org/TR/CSS22/visudet.html#min-max-heights
185 // Specifies a percentage for determining the used value. The percentage is calculated with respect to the height of the generated box's containing block.
186 // If the height of the containing block is not specified explicitly (i.e., it depends on content height), and this element is not absolutely positioned,
187 // the percentage value is treated as '0' (for 'min-height') or 'none' (for 'max-height').
188 std::optional<LayoutUnit> FormattingContext::Geometry::computedMaxHeight(const LayoutState& layoutState, const Box& layoutBox)
189 {
190     return computedHeightValue(layoutState, layoutBox, HeightType::Max);
191 }
192
193 std::optional<LayoutUnit> FormattingContext::Geometry::computedMinHeight(const LayoutState& layoutState, const Box& layoutBox)
194 {
195     if (auto minHeightValue = computedHeightValue(layoutState, layoutBox, HeightType::Min))
196         return minHeightValue;
197
198     return { 0 };
199 }
200
201 static LayoutUnit staticVerticalPositionForOutOfFlowPositioned(const LayoutState& layoutState, const Box& layoutBox)
202 {
203     ASSERT(layoutBox.isOutOfFlowPositioned());
204
205     // For the purposes of this section and the next, the term "static position" (of an element) refers, roughly, to the position an element would have
206     // had in the normal flow. More precisely, the static position for 'top' is the distance from the top edge of the containing block to the top margin
207     // edge of a hypothetical box that would have been the first box of the element if its specified 'position' value had been 'static' and its specified
208     // 'float' had been 'none' and its specified 'clear' had been 'none'. (Note that due to the rules in section 9.7 this might require also assuming a different
209     // computed value for 'display'.) The value is negative if the hypothetical box is above the containing block.
210
211     // Start with this box's border box offset from the parent's border box.
212     LayoutUnit top;
213     if (auto* previousInFlowSibling = layoutBox.previousInFlowSibling()) {
214         // Add sibling offset
215         auto& previousInFlowDisplayBox = layoutState.displayBoxForLayoutBox(*previousInFlowSibling);
216         top += previousInFlowDisplayBox.bottom() + previousInFlowDisplayBox.nonCollapsedMarginBottom();
217     } else {
218         ASSERT(layoutBox.parent());
219         top = layoutState.displayBoxForLayoutBox(*layoutBox.parent()).contentBoxTop();
220     }
221
222     // Resolve top all the way up to the containing block.
223     auto* containingBlock = layoutBox.containingBlock();
224     for (auto* container = layoutBox.parent(); container != containingBlock; container = container->containingBlock()) {
225         auto& displayBox = layoutState.displayBoxForLayoutBox(*container);
226         // Display::Box::top is the border box top position in its containing block's coordinate system.
227         top += displayBox.top();
228         ASSERT(!container->isPositioned());
229     }
230     // FIXME: floatings need to be taken into account.
231     return top;
232 }
233
234 static LayoutUnit staticHorizontalPositionForOutOfFlowPositioned(const LayoutState& layoutState, const Box& layoutBox)
235 {
236     ASSERT(layoutBox.isOutOfFlowPositioned());
237     // See staticVerticalPositionForOutOfFlowPositioned for the definition of the static position.
238
239     // Start with this box's border box offset from the parent's border box.
240     ASSERT(layoutBox.parent());
241     auto left = layoutState.displayBoxForLayoutBox(*layoutBox.parent()).contentBoxLeft();
242
243     // Resolve left all the way up to the containing block.
244     auto* containingBlock = layoutBox.containingBlock();
245     for (auto* container = layoutBox.parent(); container != containingBlock; container = container->containingBlock()) {
246         auto& displayBox = layoutState.displayBoxForLayoutBox(*container);
247         // Display::Box::left is the border box left position in its containing block's coordinate system.
248         left += displayBox.left();
249         ASSERT(!container->isPositioned());
250     }
251     // FIXME: floatings need to be taken into account.
252     return left;
253 }
254
255 LayoutUnit FormattingContext::Geometry::shrinkToFitWidth(LayoutState& layoutState, const Box& formattingRoot)
256 {
257     LOG_WITH_STREAM(FormattingContextLayout, stream << "[Width] -> shrink to fit -> unsupported -> width(" << LayoutUnit { } << "px) layoutBox: " << &formattingRoot << ")");
258     ASSERT(formattingRoot.establishesFormattingContext());
259
260     // Calculation of the shrink-to-fit width is similar to calculating the width of a table cell using the automatic table layout algorithm.
261     // Roughly: calculate the preferred width by formatting the content without breaking lines other than where explicit line breaks occur,
262     // and also calculate the preferred minimum width, e.g., by trying all possible line breaks. CSS 2.2 does not define the exact algorithm.
263     // Thirdly, find the available width: in this case, this is the width of the containing block minus the used values of 'margin-left', 'border-left-width',
264     // 'padding-left', 'padding-right', 'border-right-width', 'margin-right', and the widths of any relevant scroll bars.
265
266     // Then the shrink-to-fit width is: min(max(preferred minimum width, available width), preferred width).
267     auto availableWidth = layoutState.displayBoxForLayoutBox(*formattingRoot.containingBlock()).width();
268     auto& formattingState = layoutState.createFormattingStateForFormattingRootIfNeeded(formattingRoot);
269     auto instrinsicWidthConstraints = formattingState.formattingContext(formattingRoot)->instrinsicWidthConstraints();
270     return std::min(std::max(instrinsicWidthConstraints.minimum, availableWidth), instrinsicWidthConstraints.maximum);
271 }
272
273 VerticalGeometry FormattingContext::Geometry::outOfFlowNonReplacedVerticalGeometry(const LayoutState& layoutState, const Box& layoutBox, std::optional<LayoutUnit> usedHeight)
274 {
275     ASSERT(layoutBox.isOutOfFlowPositioned() && !layoutBox.replaced());
276
277     // 10.6.4 Absolutely positioned, non-replaced elements
278     //
279     // For absolutely positioned elements, the used values of the vertical dimensions must satisfy this constraint:
280     // 'top' + 'margin-top' + 'border-top-width' + 'padding-top' + 'height' + 'padding-bottom' + 'border-bottom-width' + 'margin-bottom' + 'bottom'
281     // = height of containing block
282
283     // If all three of 'top', 'height', and 'bottom' are auto, set 'top' to the static position and apply rule number three below.
284
285     // If none of the three are 'auto': If both 'margin-top' and 'margin-bottom' are 'auto', solve the equation under the extra
286     // constraint that the two margins get equal values. If one of 'margin-top' or 'margin-bottom' is 'auto', solve the equation for that value.
287     // If the values are over-constrained, ignore the value for 'bottom' and solve for that value.
288
289     // Otherwise, pick the one of the following six rules that applies.
290
291     // 1. 'top' and 'height' are 'auto' and 'bottom' is not 'auto', then the height is based on the content per 10.6.7,
292     //     set 'auto' values for 'margin-top' and 'margin-bottom' to 0, and solve for 'top'
293     // 2. 'top' and 'bottom' are 'auto' and 'height' is not 'auto', then set 'top' to the static position, set 'auto' values for
294     //    'margin-top' and 'margin-bottom' to 0, and solve for 'bottom'
295     // 3. 'height' and 'bottom' are 'auto' and 'top' is not 'auto', then the height is based on the content per 10.6.7, set 'auto'
296     //     values for 'margin-top' and 'margin-bottom' to 0, and solve for 'bottom'
297     // 4. 'top' is 'auto', 'height' and 'bottom' are not 'auto', then set 'auto' values for 'margin-top' and 'margin-bottom' to 0, and solve for 'top'
298     // 5. 'height' is 'auto', 'top' and 'bottom' are not 'auto', then 'auto' values for 'margin-top' and 'margin-bottom' are set to 0 and solve for 'height'
299     // 6. 'bottom' is 'auto', 'top' and 'height' are not 'auto', then set 'auto' values for 'margin-top' and 'margin-bottom' to 0 and solve for 'bottom'
300
301     auto& style = layoutBox.style();
302     auto& displayBox = layoutState.displayBoxForLayoutBox(layoutBox);
303     auto& containingBlockDisplayBox = layoutState.displayBoxForLayoutBox(*layoutBox.containingBlock());
304     auto containingBlockHeight = containingBlockDisplayBox.height();
305     auto containingBlockWidth = containingBlockDisplayBox.width();
306
307     auto top = computedValueIfNotAuto(style.logicalTop(), containingBlockWidth);
308     auto bottom = computedValueIfNotAuto(style.logicalBottom(), containingBlockWidth);
309     auto height = usedHeight ? usedHeight.value() : computedHeightValue(layoutState, layoutBox, HeightType::Normal);
310     auto marginTop = computedValueIfNotAuto(style.marginTop(), containingBlockWidth);
311     auto marginBottom = computedValueIfNotAuto(style.marginBottom(), containingBlockWidth);
312     auto paddingTop = displayBox.paddingTop().value_or(0);
313     auto paddingBottom = displayBox.paddingBottom().value_or(0);
314     auto borderTop = displayBox.borderTop();
315     auto borderBottom = displayBox.borderBottom();
316
317     if (!top && !height && !bottom)
318         top = staticVerticalPositionForOutOfFlowPositioned(layoutState, layoutBox);
319
320     if (top && height && bottom) {
321         if (!marginTop && !marginBottom) {
322             auto marginTopAndBottom = containingBlockHeight - (*top + borderTop + paddingTop + *height + paddingBottom + borderBottom + *bottom);
323             marginTop = marginBottom = marginTopAndBottom / 2;
324         } else if (!marginTop)
325             marginTop = containingBlockHeight - (*top + borderTop + paddingTop + *height + paddingBottom + borderBottom + *marginBottom + *bottom);
326         else
327             marginBottom = containingBlockHeight - (*top + *marginTop + borderTop + paddingTop + *height + paddingBottom + borderBottom + *bottom);
328         // Over-constrained?
329         auto boxHeight = *top + *marginTop + borderTop + paddingTop + *height + paddingBottom + borderBottom + *marginBottom + *bottom;
330         if (boxHeight > containingBlockHeight)
331             bottom = containingBlockHeight - (*top + *marginTop + borderTop + paddingTop + *height + paddingBottom + borderBottom + *marginBottom); 
332     }
333
334     if (!top && !height && bottom) {
335         // #1
336         height = contentHeightForFormattingContextRoot(layoutState, layoutBox);
337         marginTop = marginTop.value_or(0);
338         marginBottom = marginBottom.value_or(0);
339         top = containingBlockHeight - (*marginTop + borderTop + paddingTop + *height + paddingBottom + borderBottom + *marginBottom + *bottom); 
340     }
341
342     if (!top && !bottom && height) {
343         // #2
344         top = staticVerticalPositionForOutOfFlowPositioned(layoutState, layoutBox);
345         marginTop = marginTop.value_or(0);
346         marginBottom = marginBottom.value_or(0);
347         bottom = containingBlockHeight - (*top + *marginTop + borderTop + paddingTop + *height + paddingBottom + borderBottom + *marginBottom); 
348     }
349
350     if (!height && !bottom && top) {
351         // #3
352         height = contentHeightForFormattingContextRoot(layoutState, layoutBox);
353         marginTop = marginTop.value_or(0);
354         marginBottom = marginBottom.value_or(0);
355         bottom = containingBlockHeight - (*top + *marginTop + borderTop + paddingTop + *height + paddingBottom + borderBottom + *marginBottom); 
356     }
357
358     if (!top && height && bottom) {
359         // #4
360         marginTop = marginTop.value_or(0);
361         marginBottom = marginBottom.value_or(0);
362         top = containingBlockHeight - (*marginTop + borderTop + paddingTop + *height + paddingBottom + borderBottom + *marginBottom + *bottom); 
363     }
364
365     if (!height && top && bottom) {
366         // #5
367         marginTop = marginTop.value_or(0);
368         marginBottom = marginBottom.value_or(0);
369         height = containingBlockHeight - (*top + *marginTop + borderTop + paddingTop + paddingBottom + borderBottom + *marginBottom + *bottom); 
370     }
371
372     if (!bottom && top && height) {
373         // #6
374         marginTop = marginTop.value_or(0);
375         marginBottom = marginBottom.value_or(0);
376         bottom = containingBlockHeight - (*top + *marginTop + borderTop + paddingTop + *height + paddingBottom + borderBottom + *marginBottom); 
377     }
378
379     ASSERT(top);
380     ASSERT(bottom);
381     ASSERT(height);
382     ASSERT(marginTop);
383     ASSERT(marginBottom);
384
385     LOG_WITH_STREAM(FormattingContextLayout, stream << "[Position][Height][Margin] -> out-of-flow non-replaced -> top(" << *top << "px) bottom("  << *bottom << "px) height(" << *height << "px) margin(" << *marginTop << "px, "  << *marginBottom << "px) layoutBox(" << &layoutBox << ")");
386     return { *top, *bottom, { *height, { *marginTop, *marginBottom }, { } } };
387 }
388
389 HorizontalGeometry FormattingContext::Geometry::outOfFlowNonReplacedHorizontalGeometry(LayoutState& layoutState, const Box& layoutBox, std::optional<LayoutUnit> usedWidth)
390 {
391     ASSERT(layoutBox.isOutOfFlowPositioned() && !layoutBox.replaced());
392     
393     // 10.3.7 Absolutely positioned, non-replaced elements
394     //
395     // 'left' + 'margin-left' + 'border-left-width' + 'padding-left' + 'width' + 'padding-right' + 'border-right-width' + 'margin-right' + 'right'
396     // = width of containing block
397
398     // If all three of 'left', 'width', and 'right' are 'auto': First set any 'auto' values for 'margin-left' and 'margin-right' to 0.
399     // Then, if the 'direction' property of the element establishing the static-position containing block is 'ltr' set 'left' to the static
400     // position and apply rule number three below; otherwise, set 'right' to the static position and apply rule number one below.
401     //
402     // If none of the three is 'auto': If both 'margin-left' and 'margin-right' are 'auto', solve the equation under the extra constraint that the two margins get equal values,
403     // unless this would make them negative, in which case when direction of the containing block is 'ltr' ('rtl'), set 'margin-left' ('margin-right') to zero and
404     // solve for 'margin-right' ('margin-left'). If one of 'margin-left' or 'margin-right' is 'auto', solve the equation for that value.
405     // If the values are over-constrained, ignore the value for 'left' (in case the 'direction' property of the containing block is 'rtl') or 'right'
406     // (in case 'direction' is 'ltr') and solve for that value.
407     //
408     // Otherwise, set 'auto' values for 'margin-left' and 'margin-right' to 0, and pick the one of the following six rules that applies.
409     //
410     // 1. 'left' and 'width' are 'auto' and 'right' is not 'auto', then the width is shrink-to-fit. Then solve for 'left'
411     // 2. 'left' and 'right' are 'auto' and 'width' is not 'auto', then if the 'direction' property of the element establishing the static-position 
412     //    containing block is 'ltr' set 'left' to the static position, otherwise set 'right' to the static position.
413     //    Then solve for 'left' (if 'direction is 'rtl') or 'right' (if 'direction' is 'ltr').
414     // 3. 'width' and 'right' are 'auto' and 'left' is not 'auto', then the width is shrink-to-fit . Then solve for 'right'
415     // 4. 'left' is 'auto', 'width' and 'right' are not 'auto', then solve for 'left'
416     // 5. 'width' is 'auto', 'left' and 'right' are not 'auto', then solve for 'width'
417     // 6. 'right' is 'auto', 'left' and 'width' are not 'auto', then solve for 'right'
418
419     auto& style = layoutBox.style();
420     auto& displayBox = layoutState.displayBoxForLayoutBox(layoutBox);
421     auto& containingBlock = *layoutBox.containingBlock();
422     auto containingBlockWidth = layoutState.displayBoxForLayoutBox(containingBlock).contentBoxWidth();
423     auto isLeftToRightDirection = containingBlock.style().isLeftToRightDirection();
424     
425     auto left = computedValueIfNotAuto(style.logicalLeft(), containingBlockWidth);
426     auto right = computedValueIfNotAuto(style.logicalRight(), containingBlockWidth);
427     auto width = computedValueIfNotAuto(usedWidth ? Length { usedWidth.value(), Fixed } : style.logicalWidth(), containingBlockWidth);
428     auto marginLeft = computedValueIfNotAuto(style.marginLeft(), containingBlockWidth);
429     auto marginRight = computedValueIfNotAuto(style.marginRight(), containingBlockWidth);
430     auto nonComputedMarginLeft = marginLeft.value_or(0);
431     auto nonComputedMarginRight = marginRight.value_or(0);
432     auto paddingLeft = displayBox.paddingLeft().value_or(0);
433     auto paddingRight = displayBox.paddingRight().value_or(0);
434     auto borderLeft = displayBox.borderLeft();
435     auto borderRight = displayBox.borderRight();
436
437     if (!left && !width && !right) {
438         // If all three of 'left', 'width', and 'right' are 'auto': First set any 'auto' values for 'margin-left' and 'margin-right' to 0.
439         // Then, if the 'direction' property of the element establishing the static-position containing block is 'ltr' set 'left' to the static
440         // position and apply rule number three below; otherwise, set 'right' to the static position and apply rule number one below.
441         marginLeft = marginLeft.value_or(0);
442         marginRight = marginRight.value_or(0);
443
444         auto staticHorizontalPosition = staticHorizontalPositionForOutOfFlowPositioned(layoutState, layoutBox);
445         if (isLeftToRightDirection)
446             left = staticHorizontalPosition;
447         else
448             right = staticHorizontalPosition;
449     } else if (left && width && right) {
450         // If none of the three is 'auto': If both 'margin-left' and 'margin-right' are 'auto', solve the equation under the extra constraint that the two margins get equal values,
451         // unless this would make them negative, in which case when direction of the containing block is 'ltr' ('rtl'), set 'margin-left' ('margin-right') to zero and
452         // solve for 'margin-right' ('margin-left'). If one of 'margin-left' or 'margin-right' is 'auto', solve the equation for that value.
453         // If the values are over-constrained, ignore the value for 'left' (in case the 'direction' property of the containing block is 'rtl') or 'right'
454         // (in case 'direction' is 'ltr') and solve for that value.
455         if (!marginLeft && !marginRight) {
456             auto marginLeftAndRight = containingBlockWidth - (*left + borderLeft + paddingLeft + *width + paddingRight + borderRight + *right);
457             if (marginLeftAndRight >= 0)
458                 marginLeft = marginRight = marginLeftAndRight / 2;  
459             else {
460                 if (isLeftToRightDirection) {
461                     marginLeft = 0_lu;
462                     marginRight = containingBlockWidth - (*left + *marginLeft + borderLeft + paddingLeft + *width + paddingRight + borderRight + *right);
463                 } else {
464                     marginRight = 0_lu;
465                     marginLeft = containingBlockWidth - (*left + borderLeft + paddingLeft + *width + paddingRight + borderRight + *marginRight + *right);
466                 }
467             }
468         } else if (!marginLeft) {
469             marginLeft = containingBlockWidth - (*left + borderLeft + paddingLeft + *width + paddingRight + borderRight + *marginRight + *right);
470             // Overconstrained? Ignore right (left).
471             if (*marginLeft < 0) {
472                 if (isLeftToRightDirection)
473                     marginLeft = containingBlockWidth - (*left + borderLeft + paddingLeft + *width + paddingRight + borderRight + *marginRight);
474                 else
475                     marginLeft = containingBlockWidth - (borderLeft + paddingLeft + *width + paddingRight + borderRight + *marginRight + *right);
476             }
477         } else if (!marginRight) {
478             marginRight = containingBlockWidth - (*left + *marginLeft + borderLeft + paddingLeft + *width + paddingRight + borderRight + *right);
479             // Overconstrained? Ignore right (left).
480             if (*marginRight < 0) {
481                 if (isLeftToRightDirection)
482                     marginRight = containingBlockWidth - (*left + *marginLeft + borderLeft + paddingLeft + *width + paddingRight + borderRight);
483                 else
484                     marginRight = containingBlockWidth - (*marginLeft + borderLeft + paddingLeft + *width + paddingRight + borderRight + *right);
485             }
486         }
487     } else {
488         // Otherwise, set 'auto' values for 'margin-left' and 'margin-right' to 0, and pick the one of the following six rules that applies.
489         marginLeft = marginLeft.value_or(0);
490         marginRight = marginRight.value_or(0);
491     }
492
493     ASSERT(marginLeft);
494     ASSERT(marginRight);
495
496     if (!left && !width && right) {
497         // #1
498         width = shrinkToFitWidth(layoutState, layoutBox);
499         left = containingBlockWidth - (*marginLeft + borderLeft + paddingLeft + *width + paddingRight  + borderRight + *marginRight + *right);
500     } else if (!left && !right && width) {
501         // #2
502         auto staticHorizontalPosition = staticHorizontalPositionForOutOfFlowPositioned(layoutState, layoutBox);
503         if (isLeftToRightDirection) {
504             left = staticHorizontalPosition;
505             right = containingBlockWidth - (*left + *marginLeft + borderLeft + paddingLeft + *width + paddingRight + borderRight + *marginRight);
506         } else {
507             right = staticHorizontalPosition;
508             left = containingBlockWidth - (*marginLeft + borderLeft + paddingLeft + *width + paddingRight + borderRight + *marginRight + *right);
509         }
510     } else if (!width && !right && left) {
511         // #3
512         width = shrinkToFitWidth(layoutState, layoutBox);
513         right = containingBlockWidth - (*left + *marginLeft + borderLeft + paddingLeft + *width + paddingRight + borderRight + *marginRight);
514     } else if (!left && width && right) {
515         // #4
516         left = containingBlockWidth - (*marginLeft + borderLeft + paddingLeft + *width + paddingRight + borderRight + *marginRight + *right);
517     } else if (!width && left && right) {
518         // #5
519         width = containingBlockWidth - (*left + *marginLeft + borderLeft + paddingLeft + paddingRight  + borderRight + *marginRight + *right);
520     } else if (!right && left && width) {
521         // #6
522         right = containingBlockWidth - (*left + *marginLeft + borderLeft + paddingLeft + *width + paddingRight + borderRight + *marginRight);
523     }
524
525     ASSERT(left);
526     ASSERT(right);
527     ASSERT(width);
528     ASSERT(marginLeft);
529     ASSERT(marginRight);
530
531     LOG_WITH_STREAM(FormattingContextLayout, stream << "[Position][Width][Margin] -> out-of-flow non-replaced -> left(" << *left << "px) right("  << *right << "px) width(" << *width << "px) margin(" << *marginLeft << "px, "  << *marginRight << "px) layoutBox(" << &layoutBox << ")");
532     return { *left, *right, { *width, { *marginLeft, *marginRight }, { nonComputedMarginLeft, nonComputedMarginRight } } };
533 }
534
535 VerticalGeometry FormattingContext::Geometry::outOfFlowReplacedVerticalGeometry(const LayoutState& layoutState, const Box& layoutBox, std::optional<LayoutUnit> usedHeight)
536 {
537     ASSERT(layoutBox.isOutOfFlowPositioned() && layoutBox.replaced());
538
539     // 10.6.5 Absolutely positioned, replaced elements
540     //
541     // The used value of 'height' is determined as for inline replaced elements.
542     // If 'margin-top' or 'margin-bottom' is specified as 'auto' its used value is determined by the rules below.
543     // 1. If both 'top' and 'bottom' have the value 'auto', replace 'top' with the element's static position.
544     // 2. If 'bottom' is 'auto', replace any 'auto' on 'margin-top' or 'margin-bottom' with '0'.
545     // 3. If at this point both 'margin-top' and 'margin-bottom' are still 'auto', solve the equation under the extra constraint that the two margins must get equal values.
546     // 4. If at this point there is only one 'auto' left, solve the equation for that value.
547     // 5. If at this point the values are over-constrained, ignore the value for 'bottom' and solve for that value.
548
549     auto& style = layoutBox.style();
550     auto& displayBox = layoutState.displayBoxForLayoutBox(layoutBox);
551     auto& containingBlockDisplayBox = layoutState.displayBoxForLayoutBox(*layoutBox.containingBlock());
552     auto containingBlockHeight = containingBlockDisplayBox.height();
553     auto containingBlockWidth = containingBlockDisplayBox.width();
554
555     auto top = computedValueIfNotAuto(style.logicalTop(), containingBlockWidth);
556     auto bottom = computedValueIfNotAuto(style.logicalBottom(), containingBlockWidth);
557     auto height = inlineReplacedHeightAndMargin(layoutState, layoutBox, usedHeight).height;
558     auto marginTop = computedValueIfNotAuto(style.marginTop(), containingBlockWidth);
559     auto marginBottom = computedValueIfNotAuto(style.marginBottom(), containingBlockWidth);
560     auto paddingTop = displayBox.paddingTop().value_or(0);
561     auto paddingBottom = displayBox.paddingBottom().value_or(0);
562     auto borderTop = displayBox.borderTop();
563     auto borderBottom = displayBox.borderBottom();
564
565     if (!top && !bottom) {
566         // #1
567         top = staticVerticalPositionForOutOfFlowPositioned(layoutState, layoutBox);
568     }
569
570     if (!bottom) {
571         // #2
572         marginTop = marginTop.value_or(0);
573         marginBottom = marginBottom.value_or(0);
574     }
575
576     if (!marginTop && !marginBottom) {
577         // #3
578         auto marginTopAndBottom = containingBlockHeight - (*top + borderTop + paddingTop + height + paddingBottom + borderBottom + *bottom);
579         marginTop = marginBottom = marginTopAndBottom / 2;
580     }
581
582     // #4
583     if (!top)
584         top = containingBlockHeight - (*marginTop + borderTop + paddingTop + height + paddingBottom + borderBottom + *marginBottom + *bottom);
585
586     if (!bottom)
587         bottom = containingBlockHeight - (*top + *marginTop + borderTop + paddingTop + height + paddingBottom + borderBottom + *marginBottom);
588
589     if (!marginTop)
590         marginTop = containingBlockHeight - (*top + borderTop + paddingTop + height + paddingBottom + borderBottom + *marginBottom + *bottom);
591
592     if (!marginBottom)
593         marginBottom = containingBlockHeight - (*top + *marginTop + borderTop + paddingTop + height + paddingBottom + borderBottom + *bottom);
594
595     // #5
596     auto boxHeight = *top + *marginTop + borderTop + paddingTop + height + paddingBottom + borderBottom + *marginBottom + *bottom;
597     if (boxHeight > containingBlockHeight)
598         bottom = containingBlockHeight - (*top + *marginTop + borderTop + paddingTop + height + paddingBottom + borderBottom + *marginBottom); 
599
600     LOG_WITH_STREAM(FormattingContextLayout, stream << "[Position][Height][Margin] -> out-of-flow replaced -> top(" << *top << "px) bottom("  << *bottom << "px) height(" << height << "px) margin(" << *marginTop << "px, "  << *marginBottom << "px) layoutBox(" << &layoutBox << ")");
601     return { *top, *bottom, { height, { *marginTop, *marginBottom }, { } } };
602 }
603
604 HorizontalGeometry FormattingContext::Geometry::outOfFlowReplacedHorizontalGeometry(const LayoutState& layoutState, const Box& layoutBox, std::optional<LayoutUnit> usedWidth)
605 {
606     ASSERT(layoutBox.isOutOfFlowPositioned() && layoutBox.replaced());
607
608     // 10.3.8 Absolutely positioned, replaced elements
609     // In this case, section 10.3.7 applies up through and including the constraint equation, but the rest of section 10.3.7 is replaced by the following rules:
610     //
611     // The used value of 'width' is determined as for inline replaced elements. If 'margin-left' or 'margin-right' is specified as 'auto' its used value is determined by the rules below.
612     // 1. If both 'left' and 'right' have the value 'auto', then if the 'direction' property of the element establishing the static-position containing block is 'ltr',
613     //   set 'left' to the static position; else if 'direction' is 'rtl', set 'right' to the static position.
614     // 2. If 'left' or 'right' are 'auto', replace any 'auto' on 'margin-left' or 'margin-right' with '0'.
615     // 3. If at this point both 'margin-left' and 'margin-right' are still 'auto', solve the equation under the extra constraint that the two margins must get equal values,
616     //   unless this would make them negative, in which case when the direction of the containing block is 'ltr' ('rtl'), set 'margin-left' ('margin-right') to zero and
617     //   solve for 'margin-right' ('margin-left').
618     // 4. If at this point there is an 'auto' left, solve the equation for that value.
619     // 5. If at this point the values are over-constrained, ignore the value for either 'left' (in case the 'direction' property of the containing block is 'rtl') or
620     //   'right' (in case 'direction' is 'ltr') and solve for that value.
621
622     auto& style = layoutBox.style();
623     auto& displayBox = layoutState.displayBoxForLayoutBox(layoutBox);
624     auto& containingBlock = *layoutBox.containingBlock();
625     auto containingBlockWidth = layoutState.displayBoxForLayoutBox(containingBlock).contentBoxWidth();
626     auto isLeftToRightDirection = containingBlock.style().isLeftToRightDirection();
627
628     auto left = computedValueIfNotAuto(style.logicalLeft(), containingBlockWidth);
629     auto right = computedValueIfNotAuto(style.logicalRight(), containingBlockWidth);
630     auto marginLeft = computedValueIfNotAuto(style.marginLeft(), containingBlockWidth);
631     auto marginRight = computedValueIfNotAuto(style.marginRight(), containingBlockWidth);
632     auto nonComputedMarginLeft = marginLeft.value_or(0);
633     auto nonComputedMarginRight = marginRight.value_or(0);
634     auto width = inlineReplacedWidthAndMargin(layoutState, layoutBox, usedWidth).width;
635     auto paddingLeft = displayBox.paddingLeft().value_or(0);
636     auto paddingRight = displayBox.paddingRight().value_or(0);
637     auto borderLeft = displayBox.borderLeft();
638     auto borderRight = displayBox.borderRight();
639
640     if (!left && !right) {
641         // #1
642         auto staticHorizontalPosition = staticHorizontalPositionForOutOfFlowPositioned(layoutState, layoutBox);
643         if (isLeftToRightDirection)
644             left = staticHorizontalPosition;
645         else
646             right = staticHorizontalPosition;
647     }
648
649     if (!left || !right) {
650         // #2
651         marginLeft = marginLeft.value_or(0); 
652         marginRight = marginRight.value_or(0); 
653     }
654
655     if (!marginLeft && !marginRight) {
656         // #3
657         auto marginLeftAndRight = containingBlockWidth - (*left + borderLeft + paddingLeft + width + paddingRight + borderRight + *right);
658         if (marginLeftAndRight >= 0)
659             marginLeft = marginRight = marginLeftAndRight / 2;
660         else {
661             if (isLeftToRightDirection) {
662                 marginLeft = 0_lu;
663                 marginRight = containingBlockWidth - (*left + *marginLeft + borderLeft + paddingLeft + width + paddingRight + borderRight + *right);
664             } else {
665                 marginRight = 0_lu;
666                 marginLeft = containingBlockWidth - (*left + borderLeft + paddingLeft + width + paddingRight + borderRight + *marginRight + *right);
667             }
668         }
669     }
670
671     // #4
672     if (!left)
673         left = containingBlockWidth - (*marginLeft + borderLeft + paddingLeft + width + paddingRight + borderRight + *marginRight + *right);
674
675     if (!right)
676         right = containingBlockWidth - (*left + *marginLeft + borderLeft + paddingLeft + width + paddingRight + borderRight + *marginRight);
677
678     if (!marginLeft)
679         marginLeft = containingBlockWidth - (*left + borderLeft + paddingLeft + width + paddingRight + borderRight + *marginRight + *right);
680
681     if (!marginRight)
682         marginRight = containingBlockWidth - (*left + *marginLeft + borderLeft + paddingLeft + width + paddingRight + borderRight + *right);
683
684     auto boxWidth = (*left + *marginLeft + borderLeft + paddingLeft + width + paddingRight + borderRight + *marginRight + *right);
685     if (boxWidth > containingBlockWidth) {
686         // #5 Over-constrained?
687         if (isLeftToRightDirection)
688             right = containingBlockWidth - (*left + *marginLeft + borderLeft + paddingLeft + width + paddingRight + borderRight + *marginRight);
689         else
690             left = containingBlockWidth - (*marginLeft + borderLeft + paddingLeft + width + paddingRight + borderRight + *marginRight + *right);
691     }
692
693     ASSERT(left);
694     ASSERT(right);
695     ASSERT(marginLeft);
696     ASSERT(marginRight);
697
698     LOG_WITH_STREAM(FormattingContextLayout, stream << "[Position][Width][Margin] -> out-of-flow replaced -> left(" << *left << "px) right("  << *right << "px) width(" << width << "px) margin(" << *marginLeft << "px, "  << *marginRight << "px) layoutBox(" << &layoutBox << ")");
699     return { *left, *right, { width, { *marginLeft, *marginRight }, { nonComputedMarginLeft, nonComputedMarginRight } } };
700 }
701
702 HeightAndMargin FormattingContext::Geometry::complicatedCases(const LayoutState& layoutState, const Box& layoutBox, std::optional<LayoutUnit> usedHeight)
703 {
704     ASSERT(!layoutBox.replaced());
705     // TODO: Use complicated-case for document renderer for now (see BlockFormattingContext::Geometry::inFlowHeightAndMargin).
706     ASSERT((layoutBox.isBlockLevelBox() && layoutBox.isInFlow() && !layoutBox.isOverflowVisible()) || layoutBox.isInlineBlockBox() || layoutBox.isFloatingPositioned() || layoutBox.isDocumentBox());
707
708     // 10.6.6 Complicated cases
709     //
710     // 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).
711     // 'Inline-block', non-replaced elements.
712     // Floating, non-replaced elements.
713     //
714     // 1. If 'margin-top', or 'margin-bottom' are 'auto', their used value is 0.
715     // 2. If 'height' is 'auto', the height depends on the element's descendants per 10.6.7.
716
717     auto& style = layoutBox.style();
718     auto& containingBlock = *layoutBox.containingBlock();
719     auto& containingBlockDisplayBox = layoutState.displayBoxForLayoutBox(containingBlock);
720     auto containingBlockWidth = containingBlockDisplayBox.contentBoxWidth();
721
722     auto height = usedHeight ? usedHeight.value() : computedHeightValue(layoutState, layoutBox, HeightType::Normal);
723     auto marginTop = computedValueIfNotAuto(style.marginTop(), containingBlockWidth);
724     auto marginBottom = computedValueIfNotAuto(style.marginBottom(), containingBlockWidth);
725
726     // #1
727     marginTop = marginTop.value_or(0);
728     marginBottom = marginBottom.value_or(0);
729     // #2
730     if (!height) {
731         ASSERT(isHeightAuto(layoutBox));
732         height = contentHeightForFormattingContextRoot(layoutState, layoutBox);
733     }
734
735     ASSERT(height);
736     ASSERT(marginTop);
737     ASSERT(marginBottom);
738
739     LOG_WITH_STREAM(FormattingContextLayout, stream << "[Height][Margin] -> floating non-replaced -> height(" << *height << "px) margin(" << *marginTop << "px, " << *marginBottom << "px) -> layoutBox(" << &layoutBox << ")");
740     return HeightAndMargin { *height, { *marginTop, *marginBottom }, { } };
741 }
742
743 WidthAndMargin FormattingContext::Geometry::floatingNonReplacedWidthAndMargin(LayoutState& layoutState, const Box& layoutBox, std::optional<LayoutUnit> usedWidth)
744 {
745     ASSERT(layoutBox.isFloatingPositioned() && !layoutBox.replaced());
746
747     // 10.3.5 Floating, non-replaced elements
748     //
749     // 1. If 'margin-left', or 'margin-right' are computed as 'auto', their used value is '0'.
750     // 2. If 'width' is computed as 'auto', the used value is the "shrink-to-fit" width.
751
752     auto& containingBlock = *layoutBox.containingBlock();
753     auto containingBlockWidth = layoutState.displayBoxForLayoutBox(containingBlock).contentBoxWidth();
754
755     // #1
756     auto margin = computedNonCollapsedHorizontalMarginValue(layoutState, layoutBox);
757     // #2
758     auto width = computedValueIfNotAuto(usedWidth ? Length { usedWidth.value(), Fixed } : layoutBox.style().logicalWidth(), containingBlockWidth);
759     if (!width)
760         width = shrinkToFitWidth(layoutState, layoutBox);
761
762     LOG_WITH_STREAM(FormattingContextLayout, stream << "[Width][Margin] -> floating non-replaced -> width(" << *width << "px) margin(" << margin.left << "px, " << margin.right << "px) -> layoutBox(" << &layoutBox << ")");
763     return WidthAndMargin { *width, margin, margin };
764 }
765
766 HeightAndMargin FormattingContext::Geometry::floatingReplacedHeightAndMargin(const LayoutState& layoutState, const Box& layoutBox, std::optional<LayoutUnit> usedHeight)
767 {
768     ASSERT(layoutBox.isFloatingPositioned() && layoutBox.replaced());
769
770     // 10.6.2 Inline replaced elements, block-level replaced elements in normal flow, 'inline-block'
771     // replaced elements in normal flow and floating replaced elements
772     LOG_WITH_STREAM(FormattingContextLayout, stream << "[Height][Margin] -> floating replaced -> redirected to inline replaced");
773     return inlineReplacedHeightAndMargin(layoutState, layoutBox, usedHeight);
774 }
775
776 WidthAndMargin FormattingContext::Geometry::floatingReplacedWidthAndMargin(const LayoutState& layoutState, const Box& layoutBox, std::optional<LayoutUnit> usedWidth)
777 {
778     ASSERT(layoutBox.isFloatingPositioned() && layoutBox.replaced());
779
780     // 10.3.6 Floating, replaced elements
781     //
782     // 1. If 'margin-left' or 'margin-right' are computed as 'auto', their used value is '0'.
783     // 2. The used value of 'width' is determined as for inline replaced elements.
784     auto margin = computedNonCollapsedHorizontalMarginValue(layoutState, layoutBox);
785
786     LOG_WITH_STREAM(FormattingContextLayout, stream << "[Height][Margin] -> floating replaced -> redirected to inline replaced");
787     return inlineReplacedWidthAndMargin(layoutState, layoutBox, usedWidth, margin.left, margin.right);
788 }
789
790 VerticalGeometry FormattingContext::Geometry::outOfFlowVerticalGeometry(const LayoutState& layoutState, const Box& layoutBox, std::optional<LayoutUnit> usedHeight)
791 {
792     ASSERT(layoutBox.isOutOfFlowPositioned());
793
794     if (!layoutBox.replaced())
795         return outOfFlowNonReplacedVerticalGeometry(layoutState, layoutBox, usedHeight);
796     return outOfFlowReplacedVerticalGeometry(layoutState, layoutBox, usedHeight);
797 }
798
799 HorizontalGeometry FormattingContext::Geometry::outOfFlowHorizontalGeometry(LayoutState& layoutState, const Box& layoutBox, std::optional<LayoutUnit> usedWidth)
800 {
801     ASSERT(layoutBox.isOutOfFlowPositioned());
802
803     if (!layoutBox.replaced())
804         return outOfFlowNonReplacedHorizontalGeometry(layoutState, layoutBox, usedWidth);
805     return outOfFlowReplacedHorizontalGeometry(layoutState, layoutBox, usedWidth);
806 }
807
808 HeightAndMargin FormattingContext::Geometry::floatingHeightAndMargin(const LayoutState& layoutState, const Box& layoutBox, std::optional<LayoutUnit> usedHeight)
809 {
810     ASSERT(layoutBox.isFloatingPositioned());
811
812     if (!layoutBox.replaced())
813         return complicatedCases(layoutState, layoutBox, usedHeight);
814     return floatingReplacedHeightAndMargin(layoutState, layoutBox, usedHeight);
815 }
816
817 WidthAndMargin FormattingContext::Geometry::floatingWidthAndMargin(LayoutState& layoutState, const Box& layoutBox, std::optional<LayoutUnit> usedWidth)
818 {
819     ASSERT(layoutBox.isFloatingPositioned());
820
821     if (!layoutBox.replaced())
822         return floatingNonReplacedWidthAndMargin(layoutState, layoutBox, usedWidth);
823     return floatingReplacedWidthAndMargin(layoutState, layoutBox, usedWidth);
824 }
825
826 HeightAndMargin FormattingContext::Geometry::inlineReplacedHeightAndMargin(const LayoutState& layoutState, const Box& layoutBox, std::optional<LayoutUnit> usedHeight)
827 {
828     ASSERT((layoutBox.isOutOfFlowPositioned() || layoutBox.isFloatingPositioned() || layoutBox.isInFlow()) && layoutBox.replaced());
829
830     // 10.6.2 Inline replaced elements, block-level replaced elements in normal flow, 'inline-block' replaced elements in normal flow and floating replaced elements
831     //
832     // 1. If 'margin-top', or 'margin-bottom' are 'auto', their used value is 0.
833     // 2. If 'height' and 'width' both have computed values of 'auto' and the element also has an intrinsic height, then that intrinsic height is the used value of 'height'.
834     // 3. Otherwise, if 'height' has a computed value of 'auto', and the element has an intrinsic ratio then the used value of 'height' is:
835     //    (used width) / (intrinsic ratio)
836     // 4. Otherwise, if 'height' has a computed value of 'auto', and the element has an intrinsic height, then that intrinsic height is the used value of 'height'.
837     // 5. Otherwise, if 'height' has a computed value of 'auto', but none of the conditions above are met, then the used value of 'height' must be set to
838     //    the height of the largest rectangle that has a 2:1 ratio, has a height not greater than 150px, and has a width not greater than the device width.
839
840     // #1
841     auto margin = computedNonCollapsedVerticalMarginValue(layoutState, layoutBox);
842
843     auto& style = layoutBox.style();
844     auto replaced = layoutBox.replaced();
845
846     auto height = usedHeight ? usedHeight.value() : computedHeightValue(layoutState, layoutBox, HeightType::Normal);
847     auto heightIsAuto = !usedHeight && isHeightAuto(layoutBox);
848     auto widthIsAuto = style.logicalWidth().isAuto();
849
850     if (heightIsAuto && widthIsAuto && replaced->hasIntrinsicHeight()) {
851         // #2
852         height = replaced->intrinsicHeight();
853     } else if (heightIsAuto && replaced->hasIntrinsicRatio()) {
854         // #3
855         auto usedWidth = layoutState.displayBoxForLayoutBox(layoutBox).width();
856         height = usedWidth / replaced->intrinsicRatio();
857     } else if (heightIsAuto && replaced->hasIntrinsicHeight()) {
858         // #4
859         height = replaced->intrinsicHeight();
860     } else if (heightIsAuto) {
861         // #5
862         height = { 150 };
863     }
864
865     ASSERT(height);
866
867     LOG_WITH_STREAM(FormattingContextLayout, stream << "[Height][Margin] -> inflow replaced -> height(" << *height << "px) margin(" << margin.top << "px, " << margin.bottom << "px) -> layoutBox(" << &layoutBox << ")");
868     return { *height, margin, { } };
869 }
870
871 WidthAndMargin FormattingContext::Geometry::inlineReplacedWidthAndMargin(const LayoutState& layoutState, const Box& layoutBox,
872     std::optional<LayoutUnit> usedWidth, std::optional<LayoutUnit> precomputedMarginLeft, std::optional<LayoutUnit> precomputedMarginRight)
873 {
874     ASSERT((layoutBox.isOutOfFlowPositioned() || layoutBox.isFloatingPositioned() || layoutBox.isInFlow()) && layoutBox.replaced());
875
876     // 10.3.2 Inline, replaced elements
877     //
878     // A computed value of 'auto' for 'margin-left' or 'margin-right' becomes a used value of '0'.
879     //
880     // 1. If 'height' and 'width' both have computed values of 'auto' and the element also has an intrinsic width, then that intrinsic width is the used value of 'width'.
881     //
882     // 2. If 'height' and 'width' both have computed values of 'auto' and the element has no intrinsic width, but does have an intrinsic height and intrinsic ratio;
883     //    or if 'width' has a computed value of 'auto', 'height' has some other computed value, and the element does have an intrinsic ratio;
884     //    then the used value of 'width' is: (used height) * (intrinsic ratio)
885     //
886     // 3. If 'height' and 'width' both have computed values of 'auto' and the element has an intrinsic ratio but no intrinsic height or width,
887     //    then the used value of 'width' is undefined in CSS 2.2. However, it is suggested that, if the containing block's width does not itself depend on the replaced
888     //    element's width, then the used value of 'width' is calculated from the constraint equation used for block-level, non-replaced elements in normal flow.
889     //
890     // 4. Otherwise, if 'width' has a computed value of 'auto', and the element has an intrinsic width, then that intrinsic width is the used value of 'width'.
891     //
892     // 5. Otherwise, if 'width' has a computed value of 'auto', but none of the conditions above are met, then the used value of 'width' becomes 300px.
893     //    If 300px is too wide to fit the device, UAs should use the width of the largest rectangle that has a 2:1 ratio and fits the device instead.
894
895     auto& style = layoutBox.style();
896     auto& containingBlockDisplayBox = layoutState.displayBoxForLayoutBox(*layoutBox.containingBlock());
897     auto containingBlockWidth = containingBlockDisplayBox.width();
898
899     auto computeMarginRight = [&]() {
900         if (precomputedMarginRight)
901             return precomputedMarginRight.value();
902         auto marginRight = computedValueIfNotAuto(style.marginRight(), containingBlockWidth);
903         return marginRight.value_or(0_lu);
904     };
905
906     auto computeMarginLeft = [&]() {
907         if (precomputedMarginLeft)
908             return precomputedMarginLeft.value();
909         auto marginLeft = computedValueIfNotAuto(style.marginLeft(), containingBlockWidth);
910         return marginLeft.value_or(0_lu);
911     };
912
913     auto replaced = layoutBox.replaced();
914     ASSERT(replaced);
915
916     auto marginLeft = computeMarginLeft();
917     auto marginRight = computeMarginRight();
918     auto nonComputedMarginLeft = computedValueIfNotAuto(style.marginLeft(), containingBlockWidth).value_or(0);
919     auto nonComputedMarginRight = computedValueIfNotAuto(style.marginRight(), containingBlockWidth).value_or(0);
920     auto width = computedValueIfNotAuto(usedWidth ? Length { usedWidth.value(), Fixed } : style.logicalWidth(), containingBlockWidth);
921
922     auto heightIsAuto = isHeightAuto(layoutBox);
923     auto height = computedHeightValue(layoutState, layoutBox, HeightType::Normal);
924
925     if (!width && heightIsAuto && replaced->hasIntrinsicWidth()) {
926         // #1
927         width = replaced->intrinsicWidth();
928     } else if ((!width && heightIsAuto && !replaced->hasIntrinsicWidth() && replaced->hasIntrinsicHeight() && replaced->hasIntrinsicRatio())
929         || (!width && height && replaced->hasIntrinsicRatio())) {
930         // #2
931         width = height.value_or(replaced->hasIntrinsicHeight()) * replaced->intrinsicRatio();
932     } else if (!width && heightIsAuto && replaced->hasIntrinsicRatio() && !replaced->hasIntrinsicWidth() && !replaced->hasIntrinsicHeight()) {
933         // #3
934         // FIXME: undefined but surely doable.
935         ASSERT_NOT_IMPLEMENTED_YET();
936     } else if (!width && replaced->hasIntrinsicWidth()) {
937         // #4
938         width = replaced->intrinsicWidth();
939     } else if (!width) {
940         // #5
941         width = { 300 };
942     }
943
944     ASSERT(width);
945
946     LOG_WITH_STREAM(FormattingContextLayout, stream << "[Width][Margin] -> inflow replaced -> width(" << *width << "px) margin(" << marginLeft << "px, " << marginRight << "px) -> layoutBox(" << &layoutBox << ")");
947     return { *width, { marginLeft, marginRight }, { nonComputedMarginLeft, nonComputedMarginRight } };
948 }
949
950 LayoutSize FormattingContext::Geometry::inFlowPositionedPositionOffset(const LayoutState& layoutState, const Box& layoutBox)
951 {
952     ASSERT(layoutBox.isInFlowPositioned());
953
954     // 9.4.3 Relative positioning
955     //
956     // The 'top' and 'bottom' properties move relatively positioned element(s) up or down without changing their size.
957     // 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.
958     //
959     // 1. If both are 'auto', their used values are both '0'.
960     // 2. If one of them is 'auto', it becomes the negative of the other.
961     // 3. If neither is 'auto', 'bottom' is ignored (i.e., the used value of 'bottom' will be minus the value of 'top').
962
963     auto& style = layoutBox.style();
964     auto& containingBlock = *layoutBox.containingBlock();
965     auto containingBlockWidth = layoutState.displayBoxForLayoutBox(containingBlock).contentBoxWidth();
966
967     auto top = computedValueIfNotAuto(style.logicalTop(), containingBlockWidth);
968     auto bottom = computedValueIfNotAuto(style.logicalBottom(), containingBlockWidth);
969
970     if (!top && !bottom) {
971         // #1
972         top = bottom = { 0 };
973     } else if (!top) {
974         // #2
975         top = -*bottom;
976     } else if (!bottom) {
977         // #3
978         bottom = -*top;
979     } else {
980         // #4
981         bottom = std::nullopt;
982     }
983
984     // For relatively positioned elements, 'left' and 'right' move the box(es) horizontally, without changing their size.
985     // 'Left' moves the boxes to the right, and 'right' moves them to the left.
986     // Since boxes are not split or stretched as a result of 'left' or 'right', the used values are always: left = -right.
987     //
988     // 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).
989     // 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').
990     // 3. If 'right' is specified as 'auto', its used value is minus the value of 'left'.
991     // 4. If neither 'left' nor 'right' is 'auto', the position is over-constrained, and one of them has to be ignored.
992     //    If the 'direction' property of the containing block is 'ltr', the value of 'left' wins and 'right' becomes -'left'.
993     //    If 'direction' of the containing block is 'rtl', 'right' wins and 'left' is ignored.
994
995     auto left = computedValueIfNotAuto(style.logicalLeft(), containingBlockWidth);
996     auto right = computedValueIfNotAuto(style.logicalRight(), containingBlockWidth);
997
998     if (!left && !right) {
999         // #1
1000         left = right = { 0 };
1001     } else if (!left) {
1002         // #2
1003         left = -*right;
1004     } else if (!right) {
1005         // #3
1006         right = -*left;
1007     } else {
1008         // #4
1009         auto isLeftToRightDirection = containingBlock.style().isLeftToRightDirection();
1010         if (isLeftToRightDirection)
1011             right = -*left;
1012         else
1013             left = std::nullopt;
1014     }
1015
1016     ASSERT(!bottom || *top == -*bottom);
1017     ASSERT(!left || *left == -*right);
1018
1019     auto topPositionOffset = *top;
1020     auto leftPositionOffset = left.value_or(-*right);
1021
1022     LOG_WITH_STREAM(FormattingContextLayout, stream << "[Position] -> positioned inflow -> top offset(" << topPositionOffset << "px) left offset(" << leftPositionOffset << "px) layoutBox(" << &layoutBox << ")");
1023     return { leftPositionOffset, topPositionOffset };
1024 }
1025
1026 Edges FormattingContext::Geometry::computedBorder(const LayoutState&, const Box& layoutBox)
1027 {
1028     auto& style = layoutBox.style();
1029     LOG_WITH_STREAM(FormattingContextLayout, stream << "[Border] -> layoutBox: " << &layoutBox);
1030     return {
1031         { style.borderLeft().boxModelWidth(), style.borderRight().boxModelWidth() },
1032         { style.borderTop().boxModelWidth(), style.borderBottom().boxModelWidth() }
1033     };
1034 }
1035
1036 std::optional<Edges> FormattingContext::Geometry::computedPadding(const LayoutState& layoutState, const Box& layoutBox)
1037 {
1038     if (!layoutBox.isPaddingApplicable())
1039         return std::nullopt;
1040
1041     auto& style = layoutBox.style();
1042     auto containingBlockWidth = layoutState.displayBoxForLayoutBox(*layoutBox.containingBlock()).contentBoxWidth();
1043     LOG_WITH_STREAM(FormattingContextLayout, stream << "[Padding] -> layoutBox: " << &layoutBox);
1044     return Edges {
1045         { valueForLength(style.paddingLeft(), containingBlockWidth), valueForLength(style.paddingRight(), containingBlockWidth) },
1046         { valueForLength(style.paddingTop(), containingBlockWidth), valueForLength(style.paddingBottom(), containingBlockWidth) }
1047     };
1048 }
1049
1050 HorizontalEdges FormattingContext::Geometry::computedNonCollapsedHorizontalMarginValue(const LayoutState& layoutState, const Box& layoutBox)
1051 {
1052     auto& style = layoutBox.style();
1053     auto containingBlockWidth = layoutState.displayBoxForLayoutBox(*layoutBox.containingBlock()).contentBoxWidth();
1054
1055     auto marginLeft = computedValueIfNotAuto(style.marginLeft(), containingBlockWidth).value_or(0_lu);
1056     auto marginRight = computedValueIfNotAuto(style.marginRight(), containingBlockWidth).value_or(0_lu);
1057
1058     LOG_WITH_STREAM(FormattingContextLayout, stream << "[Margin] -> non collapsed horizontal -> margin(" << marginLeft << "px, " << marginRight << "px) -> layoutBox: " << &layoutBox);
1059     return { marginLeft, marginRight };
1060 }
1061
1062 VerticalEdges FormattingContext::Geometry::computedNonCollapsedVerticalMarginValue(const LayoutState& layoutState, const Box& layoutBox)
1063 {
1064     auto& style = layoutBox.style();
1065     auto containingBlockWidth = layoutState.displayBoxForLayoutBox(*layoutBox.containingBlock()).contentBoxWidth();
1066
1067     auto marginTop = computedValueIfNotAuto(style.marginTop(), containingBlockWidth).value_or(0_lu);
1068     auto marginBottom = computedValueIfNotAuto(style.marginBottom(), containingBlockWidth).value_or(0_lu);
1069
1070     LOG_WITH_STREAM(FormattingContextLayout, stream << "[Margin] -> non collapsed vertical -> margin(" << marginTop << "px, " << marginBottom << "px) -> layoutBox: " << &layoutBox);
1071     return { marginTop, marginBottom };
1072 }
1073
1074 }
1075 }
1076 #endif