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