[LFC] Add missing margins for inflow, non replaced block and replaced inline elements
[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 namespace WebCore {
32 namespace Layout {
33
34 static LayoutUnit contentHeightForFormattingContextRoot(LayoutContext& layoutContext, const Box& layoutBox)
35 {
36     ASSERT(layoutBox.style().logicalHeight().isAuto() && layoutBox.establishesFormattingContext());
37     // 10.6.7 'Auto' heights for block formatting context roots
38
39     // 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.
40     // If it has block-level children, the height is the distance between the top margin-edge of the topmost block-level
41     // child box and the bottom margin-edge of the bottommost block-level child box.
42
43     // In addition, if the element has any floating descendants whose bottom margin edge is below the element's bottom content edge,
44     // then the height is increased to include those edges. Only floats that participate in this block formatting context are taken
45     // into account, e.g., floats inside absolutely positioned descendants or other floats are not.
46     if (!is<Container>(layoutBox) || !downcast<Container>(layoutBox).hasInFlowOrFloatingChild())
47         return 0;
48
49     auto& formattingRootContainer = downcast<Container>(layoutBox);
50     if (formattingRootContainer.establishesInlineFormattingContext())
51         return 0;
52
53     auto* firstDisplayBox = layoutContext.displayBoxForLayoutBox(*formattingRootContainer.firstInFlowChild());
54     auto* lastDisplayBox = layoutContext.displayBoxForLayoutBox(*formattingRootContainer.lastInFlowChild());
55
56     auto top = firstDisplayBox->marginBox().top();
57     auto bottom = lastDisplayBox->marginBox().bottom();
58     // FIXME: add floating support.
59     return bottom - top;
60 }
61
62 static LayoutUnit shrinkToFitWidth(LayoutContext&, const Box&)
63 {
64     return { };
65 }
66
67 static LayoutUnit outOfFlowNonReplacedHeight(LayoutContext& layoutContext, const Box& layoutBox)
68 {
69     ASSERT(layoutBox.isOutOfFlowPositioned() && !layoutBox.replaced());
70
71     // 10.6.4 Absolutely positioned, non-replaced elements
72     //
73     // For absolutely positioned elements, the used values of the vertical dimensions must satisfy this constraint:
74     // 'top' + 'margin-top' + 'border-top-width' + 'padding-top' + 'height' + 'padding-bottom' + 'border-bottom-width' + 'margin-bottom' + 'bottom'
75     // = height of containing block
76
77     // If all three of 'top', 'height', and 'bottom' are auto, set 'top' to the static position and apply rule number three below.
78
79     // If none of the three are 'auto': If both 'margin-top' and 'margin-bottom' are 'auto', solve the equation under the extra
80     // constraint that the two margins get equal values. If one of 'margin-top' or 'margin-bottom' is 'auto', solve the equation for that value.
81     // If the values are over-constrained, ignore the value for 'bottom' and solve for that value.
82
83     // Otherwise, pick the one of the following six rules that applies.
84
85     // 1. 'top' and 'height' are 'auto' and 'bottom' is not 'auto', then the height is based on the content per 10.6.7,
86     //     set 'auto' values for 'margin-top' and 'margin-bottom' to 0, and solve for 'top'
87     // 2. 'top' and 'bottom' are 'auto' and 'height' is not 'auto', then set 'top' to the static position, set 'auto' values for
88     //    'margin-top' and 'margin-bottom' to 0, and solve for 'bottom'
89     // 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'
90     //     values for 'margin-top' and 'margin-bottom' to 0, and solve for 'bottom'
91     // 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'
92     // 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'
93     // 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'
94     auto& style = layoutBox.style();
95     auto top = style.logicalTop();
96     auto bottom = style.logicalBottom();
97     auto height = style.logicalHeight(); 
98
99     auto containingBlockHeight = layoutContext.displayBoxForLayoutBox(*layoutBox.containingBlock())->height();
100     LayoutUnit computedHeightValue;
101
102     if (!height.isAuto())
103         computedHeightValue = valueForLength(height, containingBlockHeight);
104     else if ((top.isAuto() && bottom.isAuto())
105         || (top.isAuto() && !bottom.isAuto())
106         || (!top.isAuto() && bottom.isAuto())) {
107         // All auto (#3), #1 and #3
108         computedHeightValue = contentHeightForFormattingContextRoot(layoutContext, layoutBox);
109     } else if (!top.isAuto() && !bottom.isAuto()) {
110         // #5
111         auto& displayBox = *layoutContext.displayBoxForLayoutBox(layoutBox);
112
113         auto marginTop = displayBox.marginTop();
114         auto marginBottom = displayBox.marginBottom();
115     
116         auto paddingTop = displayBox.paddingTop();
117         auto paddingBottom = displayBox.paddingBottom();
118
119         auto borderTop = displayBox.borderTop();
120         auto borderBottom = displayBox.borderBottom();
121
122         computedHeightValue = containingBlockHeight - (top.value() + marginTop + borderTop + paddingTop + paddingBottom + borderBottom + marginBottom + bottom.value());
123     } else {
124         // #2 #4 #6 have height != auto
125         ASSERT_NOT_REACHED();
126     }
127
128     return computedHeightValue;
129 }
130
131 FormattingContext::Geometry::WidthAndMargin FormattingContext::Geometry::outOfFlowNonReplacedWidthAndMargin(LayoutContext& layoutContext, const Box& layoutBox)
132 {
133     ASSERT(layoutBox.isOutOfFlowPositioned() && !layoutBox.replaced());
134     
135     // 10.3.7 Absolutely positioned, non-replaced elements
136     //
137     // 'left' + 'margin-left' + 'border-left-width' + 'padding-left' + 'width' + 'padding-right' + 'border-right-width' + 'margin-right' + 'right'
138     // = width of containing block
139
140     // If all three of 'left', 'width', and 'right' are 'auto': First set any 'auto' values for 'margin-left' and 'margin-right' to 0.
141     // Then, if the 'direction' property of the element establishing the static-position containing block is 'ltr' set 'left' to the static
142     // position and apply rule number three below; otherwise, set 'right' to the static position and apply rule number one below.
143
144     // 1. 'left' and 'width' are 'auto' and 'right' is not 'auto', then the width is shrink-to-fit. Then solve for 'left'
145     // 2. 'left' and 'right' are 'auto' and 'width' is not 'auto', then if the 'direction' property of the element establishing the static-position 
146     //    containing block is 'ltr' set 'left' to the static position, otherwise set 'right' to the static position.
147     //    Then solve for 'left' (if 'direction is 'rtl') or 'right' (if 'direction' is 'ltr').
148     // 3. 'width' and 'right' are 'auto' and 'left' is not 'auto', then the width is shrink-to-fit . Then solve for 'right'
149     // 4. 'left' is 'auto', 'width' and 'right' are not 'auto', then solve for 'left'
150     // 5. 'width' is 'auto', 'left' and 'right' are not 'auto', then solve for 'width'
151     // 6. 'right' is 'auto', 'left' and 'width' are not 'auto', then solve for 'right'
152     auto& style = layoutBox.style();
153     auto left = style.logicalLeft();
154     auto right = style.logicalRight();
155     auto width = style.logicalWidth();
156
157     auto containingBlockWidth = layoutContext.displayBoxForLayoutBox(*layoutBox.containingBlock())->width();
158     LayoutUnit computedWidthValue;
159
160     if (!width.isAuto())
161         computedWidthValue = valueForLength(width, containingBlockWidth);
162     else if ((left.isAuto() && right.isAuto())
163         || (left.isAuto() && !right.isAuto())
164         || (!left.isAuto() && right.isAuto())) {
165         // All auto (#1), #1 and #3
166         computedWidthValue = shrinkToFitWidth(layoutContext, layoutBox);
167     } else if (!left.isAuto() && !right.isAuto()) {
168         // #5
169         auto& displayBox = *layoutContext.displayBoxForLayoutBox(layoutBox);
170
171         auto marginLeft = displayBox.marginLeft();
172         auto marginRight = displayBox.marginRight();
173     
174         auto paddingLeft = displayBox.paddingLeft();
175         auto paddingRight = displayBox.paddingRight();
176
177         auto borderLeft = displayBox.borderLeft();
178         auto borderRight = displayBox.borderRight();
179
180         computedWidthValue = containingBlockWidth - (left.value() + marginLeft + borderLeft + paddingLeft + paddingRight + borderRight + marginRight + right.value());
181     } else {
182         // #2 #4 #6 have width != auto
183         ASSERT_NOT_REACHED();
184     }
185
186     return WidthAndMargin { computedWidthValue, { } };
187 }
188
189 LayoutUnit FormattingContext::Geometry::outOfFlowReplacedHeight(LayoutContext& layoutContext, const Box& layoutBox)
190 {
191     ASSERT(layoutBox.isOutOfFlowPositioned() && layoutBox.replaced());
192     // 10.6.5 Absolutely positioned, replaced elements
193     //
194     // The used value of 'height' is determined as for inline replaced elements.
195     return inlineReplacedHeight(layoutContext, layoutBox);
196 }
197
198 FormattingContext::Geometry::WidthAndMargin FormattingContext::Geometry::outOfFlowReplacedWidthAndMargin(LayoutContext& layoutContext, const Box& layoutBox)
199 {
200     ASSERT(layoutBox.isOutOfFlowPositioned() && layoutBox.replaced());
201     // 10.3.8 Absolutely positioned, replaced elements
202     //
203     // The used value of 'width' is determined as for inline replaced elements.
204     return inlineReplacedWidthAndMargin(layoutContext, layoutBox);
205 }
206
207 static LayoutUnit floatingNonReplacedHeight(LayoutContext& layoutContext, const Box& layoutBox)
208 {
209     ASSERT(layoutBox.isFloatingPositioned() && !layoutBox.replaced());
210     // 10.6.6 Complicated cases
211     //
212     // Floating, non-replaced elements.
213     //
214     // If 'height' is 'auto', the height depends on the element's descendants per 10.6.7.
215     auto height = layoutBox.style().logicalHeight();
216     return height.isAuto() ? contentHeightForFormattingContextRoot(layoutContext, layoutBox) : LayoutUnit(height.value());
217 }
218
219 FormattingContext::Geometry::WidthAndMargin FormattingContext::Geometry::floatingNonReplacedWidthAndMargin(LayoutContext& layoutContext, const Box& layoutBox)
220 {
221     ASSERT(layoutBox.isFloatingPositioned() && !layoutBox.replaced());
222     // 10.3.5 Floating, non-replaced elements
223
224     // If 'width' is computed as 'auto', the used value is the "shrink-to-fit" width.
225     auto width = layoutBox.style().logicalWidth();
226     auto computedWidthValue = width.isAuto() ? shrinkToFitWidth(layoutContext, layoutBox) : LayoutUnit(width.value());
227     return FormattingContext::Geometry::WidthAndMargin { computedWidthValue, { } };
228 }
229
230 LayoutUnit FormattingContext::Geometry::floatingReplacedHeight(LayoutContext& layoutContext, const Box& layoutBox)
231 {
232     ASSERT(layoutBox.isFloatingPositioned() && layoutBox.replaced());
233     // 10.6.2 Inline replaced elements, block-level replaced elements in normal flow, 'inline-block'
234     // replaced elements in normal flow and floating replaced elements
235     return inlineReplacedHeight(layoutContext, layoutBox);
236 }
237
238 FormattingContext::Geometry::WidthAndMargin FormattingContext::Geometry::floatingReplacedWidthAndMargin(LayoutContext& layoutContext, const Box& layoutBox)
239 {
240     ASSERT(layoutBox.isFloatingPositioned() && layoutBox.replaced());
241     // 10.3.6 Floating, replaced elements
242     //
243     // The used value of 'width' is determined as for inline replaced elements.
244     return inlineReplacedWidthAndMargin(layoutContext, layoutBox);
245 }
246
247 static LayoutPoint outOfFlowNonReplacedPosition(LayoutContext& layoutContext, const Box& layoutBox)
248 {
249     ASSERT(layoutBox.isOutOfFlowPositioned() && !layoutBox.replaced());
250
251     // 10.3.7 Absolutely positioned, non-replaced elements (left/right)
252     // 10.6.4 Absolutely positioned, non-replaced elements (top/bottom)
253
254     // At this point we've the size computed.
255     auto& displayBox = *layoutContext.displayBoxForLayoutBox(layoutBox);
256     auto size = displayBox.size();
257     auto& style = layoutBox.style();
258
259     // 10.6.4 Absolutely positioned, non-replaced elements
260     auto top = style.logicalTop();
261     auto bottom = style.logicalBottom();
262     auto containingBlockHeight = layoutContext.displayBoxForLayoutBox(*layoutBox.containingBlock())->height();
263
264     // 'top' + 'margin-top' + 'border-top-width' + 'padding-top' + 'height' + 'padding-bottom' + 'border-bottom-width' + 'margin-bottom' + 'bottom'
265     // = height of containing block
266     //
267     // 1. 'top' and 'height' are 'auto' and 'bottom' is not 'auto', then the height is based on the content per 10.6.7,
268     //     set 'auto' values for 'margin-top' and 'margin-bottom' to 0, and solve for 'top'
269     // 2. 'top' and 'bottom' are 'auto' and 'height' is not 'auto', then set 'top' to the static position, set 'auto' values for
270     //    'margin-top' and 'margin-bottom' to 0, and solve for 'bottom'
271     // 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'
272     //     values for 'margin-top' and 'margin-bottom' to 0, and solve for 'bottom'
273     // 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'
274     // 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'
275     // 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'
276     LayoutUnit computedTopValue;
277     if (top.isAuto() && !bottom.isAuto()) {
278         // #1 #4
279         auto marginTop = displayBox.marginTop();
280         auto marginBottom = displayBox.marginBottom();
281     
282         auto paddingTop = displayBox.paddingTop();
283         auto paddingBottom = displayBox.paddingBottom();
284
285         auto borderTop = displayBox.borderTop();
286         auto borderBottom = displayBox.borderBottom();
287
288         computedTopValue = containingBlockHeight - (marginTop + borderTop + paddingTop + size.height() + paddingBottom + borderBottom + marginBottom + bottom.value());
289     } else if (top.isAuto() && bottom.isAuto()) {
290         // #2
291         // Already computed as part of the computeStaticPosition();
292         computedTopValue = displayBox.top();
293     } else {
294         // #3 #5 #6 have top != auto
295         computedTopValue = valueForLength(top, containingBlockHeight);
296     }
297
298
299     // 10.3.7 Absolutely positioned, non-replaced elements
300     auto left = style.logicalLeft();
301     auto right = style.logicalRight();
302     auto containingBlockWidth = layoutContext.displayBoxForLayoutBox(*layoutBox.containingBlock())->width();
303
304     // 'left' + 'margin-left' + 'border-left-width' + 'padding-left' + 'width' + 'padding-right' + 'border-right-width' + 'margin-right' + 'right'
305     // = width of containing block
306     //
307     // If all three of 'left', 'width', and 'right' are 'auto': First set any 'auto' values for 'margin-left' and 'margin-right' to 0.
308     // Then, if the 'direction' property of the element establishing the static-position containing block is 'ltr' set 'left' to the static
309     // position and apply rule number three below; otherwise, set 'right' to the static position and apply rule number one below.
310
311     // 1. 'left' and 'width' are 'auto' and 'right' is not 'auto', then the width is shrink-to-fit. Then solve for 'left'
312     // 2. 'left' and 'right' are 'auto' and 'width' is not 'auto', then if the 'direction' property of the element establishing the static-position 
313     //    containing block is 'ltr' set 'left' to the static position, otherwise set 'right' to the static position.
314     //    Then solve for 'left' (if 'direction is 'rtl') or 'right' (if 'direction' is 'ltr').
315     // 3. 'width' and 'right' are 'auto' and 'left' is not 'auto', then the width is shrink-to-fit . Then solve for 'right'
316     // 4. 'left' is 'auto', 'width' and 'right' are not 'auto', then solve for 'left'
317     // 5. 'width' is 'auto', 'left' and 'right' are not 'auto', then solve for 'width'
318     // 6. 'right' is 'auto', 'left' and 'width' are not 'auto', then solve for 'right'
319     LayoutUnit computedLeftValue;
320     if (left.isAuto() && !right.isAuto()) {
321         // #1 #4
322         auto marginLeft = displayBox.marginLeft();
323         auto marginRight = displayBox.marginRight();
324     
325         auto paddingLeft = displayBox.paddingLeft();
326         auto paddingRight = displayBox.paddingRight();
327
328         auto borderLeft = displayBox.borderLeft();
329         auto borderRight = displayBox.borderRight();
330
331         computedLeftValue = containingBlockWidth - (marginLeft + borderLeft + paddingLeft + size.width() + paddingRight + borderRight + marginRight + right.value());
332     } else if (left.isAuto() && right.isAuto()) {
333         // #2
334         // FIXME: rtl
335         computedLeftValue = displayBox.left();
336     } else {
337         // #3 #5 #6 have left != auto
338         computedLeftValue = valueForLength(left, containingBlockWidth);
339     }
340
341     return { computedLeftValue, computedTopValue };
342 }
343
344 static LayoutPoint outOfFlowReplacedPosition(LayoutContext& layoutContext, const Box& layoutBox)
345 {
346     ASSERT(layoutBox.isOutOfFlowPositioned() && layoutBox.replaced());
347
348     // 10.6.5 Absolutely positioned, replaced elements (top/bottom)
349     // 10.3.8 Absolutely positioned, replaced elements (left/right)
350
351     // At this point we've the size computed.
352     auto& displayBox = *layoutContext.displayBoxForLayoutBox(layoutBox);
353     auto size = displayBox.size();
354     auto& style = layoutBox.style();
355
356     // 10.6.5 Absolutely positioned, replaced elements
357     //
358     // This situation is similar to the previous one, except that the element has an intrinsic height. The sequence of substitutions is now:
359     // The used value of 'height' is determined as for inline replaced elements. If 'margin-top' or 'margin-bottom' is specified as 'auto'
360     // its used value is determined by the rules below.
361     //
362     // 1. If both 'top' and 'bottom' have the value 'auto', replace 'top' with the element's static position.
363     // 2. If 'bottom' is 'auto', replace any 'auto' on 'margin-top' or 'margin-bottom' with '0'.
364     // 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.
365     // 4. If at this point there is only one 'auto' left, solve the equation for that value.
366     // 5. If at this point the values are over-constrained, ignore the value for 'bottom' and solve for that value.
367     auto top = style.logicalTop();
368     auto bottom = style.logicalBottom();
369     auto containingBlockHeight = layoutContext.displayBoxForLayoutBox(*layoutBox.containingBlock())->height();
370     LayoutUnit computedTopValue;
371
372     if (!top.isAuto())
373         computedTopValue = valueForLength(top, containingBlockHeight);
374     else if (bottom.isAuto()) {
375         // #1
376         computedTopValue = displayBox.top();
377     } else {
378         // #4
379         auto marginTop = displayBox.marginTop();
380         auto marginBottom = displayBox.marginBottom();
381
382         auto paddingTop = displayBox.paddingTop();
383         auto paddingBottom = displayBox.paddingBottom();
384
385         auto borderTop = displayBox.borderTop();
386         auto borderBottom = displayBox.borderBottom();
387
388         computedTopValue = containingBlockHeight - (marginTop + borderTop + paddingTop + size.height() + paddingBottom + borderBottom + marginBottom + bottom.value());
389     }
390
391
392     // 10.3.8 Absolutely positioned, replaced elements
393     //
394     // 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:
395     //
396     // The used value of 'width' is determined as for inline replaced elements. 
397     //
398     // 1. If 'margin-left' or 'margin-right' is specified as 'auto' its used value is determined by the rules below.
399     // 2. If both 'left' and 'right' have the value 'auto', then if the 'direction' property of the element establishing the
400     //    static-position containing block is 'ltr', set 'left' to the static position; else if 'direction' is 'rtl', set 'right' to the static position.
401     // 3. If 'left' or 'right' are 'auto', replace any 'auto' on 'margin-left' or 'margin-right' with '0'.
402     // 4. If at this point both 'margin-left' and 'margin-right' are still 'auto', solve the equation under the extra constraint
403     //    that the two margins must get equal values, unless this would make them negative, in which case when the direction of
404     //    the containing block is 'ltr' ('rtl'), set 'margin-left' ('margin-right') to zero and solve for 'margin-right' ('margin-left').
405     // 5. If at this point there is an 'auto' left, solve the equation for that value.
406     // 6. If at this point the values are over-constrained, ignore the value for either 'left' (in case the 'direction'
407     //    property of the containing block is 'rtl') or 'right' (in case 'direction' is 'ltr') and solve for that value.
408     auto left = style.logicalLeft();
409     auto right = style.logicalRight();
410     auto containingBlockWidth = layoutContext.displayBoxForLayoutBox(*layoutBox.containingBlock())->width();
411     LayoutUnit computedLeftValue;
412
413     if (!left.isAuto())   
414         computedLeftValue = valueForLength(left, containingBlockWidth);
415     else if (right.isAuto()) {
416         // FIXME: take direction into account
417         computedLeftValue = displayBox.left();
418     } else {
419         // #5
420         auto marginLeft = displayBox.marginLeft();
421         auto marginRight = displayBox.marginRight();
422     
423         auto paddingLeft = displayBox.paddingLeft();
424         auto paddingRight = displayBox.paddingRight();
425
426         auto borderLeft = displayBox.borderLeft();
427         auto borderRight = displayBox.borderRight();
428
429         computedLeftValue = containingBlockWidth - (marginLeft + borderLeft + paddingLeft + size.width() + paddingRight + borderRight + marginRight + right.value());
430     }
431
432     return { computedLeftValue, computedTopValue };
433 }
434
435 LayoutUnit FormattingContext::Geometry::outOfFlowHeight(LayoutContext& layoutContext, const Box& layoutBox)
436 {
437     ASSERT(layoutBox.isOutOfFlowPositioned());
438
439     if (!layoutBox.replaced())
440         return outOfFlowNonReplacedHeight(layoutContext, layoutBox);
441     return outOfFlowReplacedHeight(layoutContext, layoutBox);
442 }
443
444 FormattingContext::Geometry::WidthAndMargin FormattingContext::Geometry::outOfFlowWidthAndMargin(LayoutContext& layoutContext, const Box& layoutBox)
445 {
446     ASSERT(layoutBox.isOutOfFlowPositioned());
447
448     if (!layoutBox.replaced())
449         return outOfFlowNonReplacedWidthAndMargin(layoutContext, layoutBox);
450     return outOfFlowReplacedWidthAndMargin(layoutContext, layoutBox);
451 }
452
453 LayoutUnit FormattingContext::Geometry::floatingHeight(LayoutContext& layoutContext, const Box& layoutBox)
454 {
455     ASSERT(layoutBox.isFloatingPositioned());
456
457     if (!layoutBox.replaced())
458         return floatingNonReplacedHeight(layoutContext, layoutBox);
459     return floatingReplacedHeight(layoutContext, layoutBox);
460 }
461
462 FormattingContext::Geometry::WidthAndMargin FormattingContext::Geometry::floatingWidthAndMargin(LayoutContext& layoutContext, const Box& layoutBox)
463 {
464     ASSERT(layoutBox.isFloatingPositioned());
465
466     if (!layoutBox.replaced())
467         return floatingNonReplacedWidthAndMargin(layoutContext, layoutBox);
468     return floatingReplacedWidthAndMargin(layoutContext, layoutBox);
469 }
470
471 LayoutPoint FormattingContext::Geometry::outOfFlowPosition(LayoutContext& layoutContext, const Box& layoutBox)
472 {
473     ASSERT(layoutBox.isOutOfFlowPositioned());
474
475     if (!layoutBox.replaced())
476         return outOfFlowNonReplacedPosition(layoutContext, layoutBox);
477     return outOfFlowReplacedPosition(layoutContext, layoutBox);
478 }
479
480 LayoutUnit FormattingContext::Geometry::inlineReplacedHeight(LayoutContext&, const Box& layoutBox)
481 {
482     ASSERT((layoutBox.isOutOfFlowPositioned() || layoutBox.isFloatingPositioned() || layoutBox.isInFlow()) && layoutBox.replaced());
483     // 10.6.2 Inline replaced elements, block-level replaced elements in normal flow, 'inline-block' replaced elements in normal flow and floating replaced elements
484     //
485     // 1. 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'.
486     //
487     // 2. Otherwise, if 'height' has a computed value of 'auto', and the element has an intrinsic ratio then the used value of 'height' is:
488     //    (used width) / (intrinsic ratio)
489     //
490     // 3. 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'.
491     //
492     // 4. 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
493     //    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.
494     auto& style = layoutBox.style();
495     auto width = style.logicalWidth();
496     auto height = style.logicalHeight();
497
498     LayoutUnit computedHeightValue;
499     auto replaced = layoutBox.replaced();
500     ASSERT(replaced);
501
502     if (height.isAuto()) {
503         if (width.isAuto() && replaced->hasIntrinsicHeight()) {
504             // #1
505             computedHeightValue = replaced->intrinsicHeight();
506         } else if (replaced->hasIntrinsicRatio()) {
507             // #2
508             computedHeightValue = width.value() / replaced->intrinsicRatio();
509         } else if (replaced->hasIntrinsicHeight()) {
510             // #3
511             computedHeightValue = replaced->intrinsicHeight();
512         } else {
513             // #4
514             computedHeightValue = 150;
515         }
516     } else
517         computedHeightValue = height.value();
518
519     return computedHeightValue;
520 }
521
522 FormattingContext::Geometry::WidthAndMargin FormattingContext::Geometry::inlineReplacedWidthAndMargin(LayoutContext& layoutContext, const Box& layoutBox)
523 {
524     ASSERT((layoutBox.isOutOfFlowPositioned() || layoutBox.isFloatingPositioned() || layoutBox.isInFlow()) && layoutBox.replaced());
525     // 10.3.2 Inline, replaced elements
526     //
527     // A computed value of 'auto' for 'margin-left' or 'margin-right' becomes a used value of '0'.
528     //
529     // 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'.
530     //
531     // 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;
532     //    or if 'width' has a computed value of 'auto', 'height' has some other computed value, and the element does have an intrinsic ratio;
533     //    then the used value of 'width' is: (used height) * (intrinsic ratio)
534     //
535     // 3. If 'height' and 'width' both have computed values of 'auto' and the element has an intrinsic ratio but no intrinsic height or width,
536     //    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
537     //    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.
538     //
539     // 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'.
540     //
541     // 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.
542     //    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.
543     auto& style = layoutBox.style();
544     LayoutUnit computedWidthValue;
545     LayoutUnit computedMarginLeftValue;
546     LayoutUnit computedMarginRightValue;
547
548     {
549         auto marginLeft = style.marginLeft();
550         auto marginRight = style.marginRight();
551         auto containingBlockWidth = layoutContext.displayBoxForLayoutBox(*layoutBox.containingBlock())->width();
552
553         computedMarginLeftValue = marginLeft.isAuto() ? LayoutUnit(0) : valueForLength(marginLeft, containingBlockWidth);
554         computedMarginRightValue = marginRight.isAuto() ? LayoutUnit(0) : valueForLength(marginRight, containingBlockWidth);
555     }
556
557     auto width = style.logicalWidth();
558     auto height = style.logicalHeight();
559     auto replaced = layoutBox.replaced();
560     ASSERT(replaced);
561
562     if (width.isAuto() && height.isAuto() && replaced->hasIntrinsicWidth()) {
563         // #1
564         computedWidthValue = replaced->intrinsicWidth();
565     } else if (width.isAuto() && (height.isCalculated() || replaced->hasIntrinsicHeight()) && replaced->hasIntrinsicRatio()) {
566         // #2
567         auto usedHeight = height.isCalculated() ? LayoutUnit(height.value()) : replaced->intrinsicHeight();
568         computedWidthValue = usedHeight * replaced->intrinsicRatio();
569     } else if (width.isAuto() && height.isAuto() && replaced->hasIntrinsicRatio()) {
570         // #3
571         // FIXME: undefined but surely doable.
572         ASSERT_NOT_IMPLEMENTED_YET();
573     } else if (width.isAuto() && replaced->hasIntrinsicWidth()) {
574         // #4
575         computedWidthValue = replaced->intrinsicWidth();
576     } else {
577         // #5
578         computedWidthValue = 300;
579     }
580
581     return WidthAndMargin { computedWidthValue, { computedMarginLeftValue, computedMarginRightValue } };
582 }
583
584 Display::Box::Edges FormattingContext::Geometry::computedBorder(LayoutContext&, const Box& layoutBox)
585 {
586     auto& style = layoutBox.style();
587     return {
588         { style.borderLeft().boxModelWidth(), style.borderRight().boxModelWidth() },
589         { style.borderTop().boxModelWidth(), style.borderBottom().boxModelWidth() }
590     };
591 }
592
593 std::optional<Display::Box::Edges> FormattingContext::Geometry::computedPadding(LayoutContext& layoutContext, const Box& layoutBox)
594 {
595     if (!layoutBox.isPaddingApplicable())
596         return std::nullopt;
597
598     auto& style = layoutBox.style();
599     auto containingBlockWidth = layoutContext.displayBoxForLayoutBox(*layoutBox.containingBlock())->width();
600     return Display::Box::Edges {
601         { valueForLength(style.paddingLeft(), containingBlockWidth), valueForLength(style.paddingRight(), containingBlockWidth) },
602         { valueForLength(style.paddingTop(), containingBlockWidth), valueForLength(style.paddingBottom(), containingBlockWidth) }
603     };
604 }
605
606 }
607 }
608 #endif