2 * Copyright (C) 2018 Apple Inc. All rights reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
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.
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.
27 #include "FormattingContext.h"
29 #if ENABLE(LAYOUT_FORMATTING_CONTEXT)
31 #include "FloatingState.h"
32 #include "FormattingState.h"
37 static LayoutUnit contentHeightForFormattingContextRoot(const LayoutContext& layoutContext, const Box& layoutBox)
39 ASSERT(layoutBox.style().logicalHeight().isAuto() && (layoutBox.establishesFormattingContext() || layoutBox.isDocumentBox()));
41 // 10.6.7 'Auto' heights for block formatting context roots
43 // 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.
44 // If it has block-level children, the height is the distance between the top margin-edge of the topmost block-level
45 // child box and the bottom margin-edge of the bottommost block-level child box.
47 // In addition, if the element has any floating descendants whose bottom margin edge is below the element's bottom content edge,
48 // then the height is increased to include those edges. Only floats that participate in this block formatting context are taken
49 // into account, e.g., floats inside absolutely positioned descendants or other floats are not.
50 if (!is<Container>(layoutBox) || !downcast<Container>(layoutBox).hasInFlowOrFloatingChild())
53 auto& formattingRootContainer = downcast<Container>(layoutBox);
54 if (formattingRootContainer.establishesInlineFormattingContext())
57 auto& firstDisplayBox = layoutContext.displayBoxForLayoutBox(*formattingRootContainer.firstInFlowChild());
58 auto& lastDisplayBox = layoutContext.displayBoxForLayoutBox(*formattingRootContainer.lastInFlowChild());
59 auto top = firstDisplayBox.rectWithMargin().top();
60 auto bottom = lastDisplayBox.rectWithMargin().bottom();
62 auto* formattingContextRoot = &layoutBox;
63 // TODO: The document renderer is not a formatting context root by default at all. Need to find out what it is.
64 if (!layoutBox.establishesFormattingContext()) {
65 ASSERT(layoutBox.isDocumentBox());
66 formattingContextRoot = &layoutBox.formattingContextRoot();
69 auto floatsBottom = layoutContext.establishedFormattingState(*formattingContextRoot).floatingState().bottom(*formattingContextRoot);
71 bottom = std::max(*floatsBottom, bottom);
73 auto computedHeight = bottom - top;
74 LOG_WITH_STREAM(FormattingContextLayout, stream << "[Height] -> content height for formatting context root -> height(" << computedHeight << "px) layoutBox("<< &layoutBox << ")");
75 return computedHeight;
78 std::optional<LayoutUnit> FormattingContext::Geometry::computedValueIfNotAuto(const Length& geometryProperty, LayoutUnit containingBlockWidth)
80 if (geometryProperty.isUndefined())
83 if (geometryProperty.isAuto())
86 return valueForLength(geometryProperty, containingBlockWidth);
89 std::optional<LayoutUnit> FormattingContext::Geometry::fixedValue(const Length& geometryProperty)
91 if (!geometryProperty.isFixed())
93 return { geometryProperty.value() };
96 // https://www.w3.org/TR/CSS22/visudet.html#min-max-heights
97 // Specifies a percentage for determining the used value. The percentage is calculated with respect to the height of the generated box's containing block.
98 // 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,
99 // the percentage value is treated as '0' (for 'min-height') or 'none' (for 'max-height').
100 std::optional<LayoutUnit> FormattingContext::Geometry::computedMaxHeight(const LayoutContext& layoutContext, const Box& layoutBox)
102 auto maxHeight = layoutBox.style().logicalMaxHeight();
103 if (maxHeight.isUndefined() || maxHeight.isAuto())
106 if (maxHeight.isFixed())
107 return { maxHeight.value() };
109 std::optional<LayoutUnit> containingBlockHeightValue;
110 auto height = layoutBox.containingBlock()->style().logicalHeight();
111 if (height.isFixed())
112 containingBlockHeightValue = { height.value() };
113 else if (layoutBox.isOutOfFlowPositioned()) {
114 // Containing block's height is already computed.
115 containingBlockHeightValue = layoutContext.displayBoxForLayoutBox(*layoutBox.containingBlock()).height();
118 if (containingBlockHeightValue)
119 return valueForLength(maxHeight, *containingBlockHeightValue);
124 std::optional<LayoutUnit> FormattingContext::Geometry::computedMinHeight(const LayoutContext& layoutContext, const Box& layoutBox)
126 auto minHeight = layoutBox.style().logicalMinHeight();
127 if (minHeight.isUndefined() || minHeight.isAuto())
130 if (minHeight.isFixed())
131 return { minHeight.value() };
133 std::optional<LayoutUnit> containingBlockHeightValue;
134 auto height = layoutBox.containingBlock()->style().logicalHeight();
135 if (height.isFixed())
136 containingBlockHeightValue = { height.value() };
137 else if (layoutBox.isOutOfFlowPositioned()) {
138 // Containing block's height is already computed.
139 containingBlockHeightValue = layoutContext.displayBoxForLayoutBox(*layoutBox.containingBlock()).height();
142 if (containingBlockHeightValue)
143 return valueForLength(minHeight, *containingBlockHeightValue);
148 static LayoutUnit staticVerticalPositionForOutOfFlowPositioned(const LayoutContext& layoutContext, const Box& layoutBox)
150 ASSERT(layoutBox.isOutOfFlowPositioned());
152 // 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
153 // 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
154 // 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
155 // '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
156 // computed value for 'display'.) The value is negative if the hypothetical box is above the containing block.
158 // Start with this box's border box offset from the parent's border box.
160 if (auto* previousInFlowSibling = layoutBox.previousInFlowSibling()) {
161 // Add sibling offset
162 auto& previousInFlowDisplayBox = layoutContext.displayBoxForLayoutBox(*previousInFlowSibling);
163 top += previousInFlowDisplayBox.bottom() + previousInFlowDisplayBox.nonCollapsedMarginBottom();
165 ASSERT(layoutBox.parent());
166 top = layoutContext.displayBoxForLayoutBox(*layoutBox.parent()).contentBoxTop();
169 // Resolve top all the way up to the containing block.
170 auto* containingBlock = layoutBox.containingBlock();
171 for (auto* container = layoutBox.parent(); container != containingBlock; container = container->containingBlock()) {
172 auto& displayBox = layoutContext.displayBoxForLayoutBox(*container);
173 // Display::Box::top is the border box top position in its containing block's coordinate system.
174 top += displayBox.top();
175 ASSERT(!container->isPositioned());
177 // FIXME: floatings need to be taken into account.
181 static LayoutUnit staticHorizontalPositionForOutOfFlowPositioned(const LayoutContext& layoutContext, const Box& layoutBox)
183 ASSERT(layoutBox.isOutOfFlowPositioned());
184 // See staticVerticalPositionForOutOfFlowPositioned for the definition of the static position.
186 // Start with this box's border box offset from the parent's border box.
187 ASSERT(layoutBox.parent());
188 auto left = layoutContext.displayBoxForLayoutBox(*layoutBox.parent()).contentBoxLeft();
190 // Resolve left all the way up to the containing block.
191 auto* containingBlock = layoutBox.containingBlock();
192 for (auto* container = layoutBox.parent(); container != containingBlock; container = container->containingBlock()) {
193 auto& displayBox = layoutContext.displayBoxForLayoutBox(*container);
194 // Display::Box::left is the border box left position in its containing block's coordinate system.
195 left += displayBox.left();
196 ASSERT(!container->isPositioned());
198 // FIXME: floatings need to be taken into account.
202 LayoutUnit FormattingContext::Geometry::shrinkToFitWidth(LayoutContext& layoutContext, const FormattingContext& formattingContext, const Box& layoutBox)
204 LOG_WITH_STREAM(FormattingContextLayout, stream << "[Width] -> shrink to fit -> unsupported -> width(" << LayoutUnit { } << "px) layoutBox: " << &layoutBox << ")");
205 // Calculation of the shrink-to-fit width is similar to calculating the width of a table cell using the automatic table layout algorithm.
206 // Roughly: calculate the preferred width by formatting the content without breaking lines other than where explicit line breaks occur,
207 // and also calculate the preferred minimum width, e.g., by trying all possible line breaks. CSS 2.2 does not define the exact algorithm.
208 // 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',
209 // 'padding-left', 'padding-right', 'border-right-width', 'margin-right', and the widths of any relevant scroll bars.
211 // Then the shrink-to-fit width is: min(max(preferred minimum width, available width), preferred width).
212 auto availableWidth = layoutContext.displayBoxForLayoutBox(*layoutBox.containingBlock()).width();
213 auto instrinsicWidthConstraints = formattingContext.instrinsicWidthConstraints(layoutContext, layoutBox);
214 return std::min(std::max(instrinsicWidthConstraints.minimum, availableWidth), instrinsicWidthConstraints.maximum);
217 VerticalGeometry FormattingContext::Geometry::outOfFlowNonReplacedVerticalGeometry(const LayoutContext& layoutContext, const Box& layoutBox, std::optional<LayoutUnit> precomputedHeight)
219 ASSERT(layoutBox.isOutOfFlowPositioned() && !layoutBox.replaced());
221 // 10.6.4 Absolutely positioned, non-replaced elements
223 // For absolutely positioned elements, the used values of the vertical dimensions must satisfy this constraint:
224 // 'top' + 'margin-top' + 'border-top-width' + 'padding-top' + 'height' + 'padding-bottom' + 'border-bottom-width' + 'margin-bottom' + 'bottom'
225 // = height of containing block
227 // If all three of 'top', 'height', and 'bottom' are auto, set 'top' to the static position and apply rule number three below.
229 // If none of the three are 'auto': If both 'margin-top' and 'margin-bottom' are 'auto', solve the equation under the extra
230 // constraint that the two margins get equal values. If one of 'margin-top' or 'margin-bottom' is 'auto', solve the equation for that value.
231 // If the values are over-constrained, ignore the value for 'bottom' and solve for that value.
233 // Otherwise, pick the one of the following six rules that applies.
235 // 1. 'top' and 'height' are 'auto' and 'bottom' is not 'auto', then the height is based on the content per 10.6.7,
236 // set 'auto' values for 'margin-top' and 'margin-bottom' to 0, and solve for 'top'
237 // 2. 'top' and 'bottom' are 'auto' and 'height' is not 'auto', then set 'top' to the static position, set 'auto' values for
238 // 'margin-top' and 'margin-bottom' to 0, and solve for 'bottom'
239 // 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'
240 // values for 'margin-top' and 'margin-bottom' to 0, and solve for 'bottom'
241 // 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'
242 // 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'
243 // 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'
245 auto& style = layoutBox.style();
246 auto& displayBox = layoutContext.displayBoxForLayoutBox(layoutBox);
247 auto& containingBlockDisplayBox = layoutContext.displayBoxForLayoutBox(*layoutBox.containingBlock());
248 auto containingBlockHeight = containingBlockDisplayBox.height();
249 auto containingBlockWidth = containingBlockDisplayBox.width();
251 auto top = computedValueIfNotAuto(style.logicalTop(), containingBlockWidth);
252 auto bottom = computedValueIfNotAuto(style.logicalBottom(), containingBlockWidth);
253 auto height = computedValueIfNotAuto(precomputedHeight ? Length { precomputedHeight.value(), Fixed } : style.logicalHeight(), containingBlockHeight);
254 auto marginTop = computedValueIfNotAuto(style.marginTop(), containingBlockWidth);
255 auto marginBottom = computedValueIfNotAuto(style.marginBottom(), containingBlockWidth);
256 auto paddingTop = displayBox.paddingTop().value_or(0);
257 auto paddingBottom = displayBox.paddingBottom().value_or(0);
258 auto borderTop = displayBox.borderTop();
259 auto borderBottom = displayBox.borderBottom();
261 if (!top && !height && !bottom)
262 top = staticVerticalPositionForOutOfFlowPositioned(layoutContext, layoutBox);
264 if (top && height && bottom) {
265 if (!marginTop && !marginBottom) {
266 auto marginTopAndBottom = containingBlockHeight - (*top + borderTop + paddingTop + *height + paddingBottom + borderBottom + *bottom);
267 marginTop = marginBottom = marginTopAndBottom / 2;
268 } else if (!marginTop)
269 marginTop = containingBlockHeight - (*top + borderTop + paddingTop + *height + paddingBottom + borderBottom + *marginBottom + *bottom);
271 marginBottom = containingBlockHeight - (*top + *marginTop + borderTop + paddingTop + *height + paddingBottom + borderBottom + *bottom);
273 auto boxHeight = *top + *marginTop + borderTop + paddingTop + *height + paddingBottom + borderBottom + *marginBottom + *bottom;
274 if (boxHeight > containingBlockHeight)
275 bottom = containingBlockHeight - (*top + *marginTop + borderTop + paddingTop + *height + paddingBottom + borderBottom + *marginBottom);
278 if (!top && !height && bottom) {
280 height = contentHeightForFormattingContextRoot(layoutContext, layoutBox);
281 marginTop = marginTop.value_or(0);
282 marginBottom = marginBottom.value_or(0);
283 top = containingBlockHeight - (*marginTop + borderTop + paddingTop + *height + paddingBottom + borderBottom + *marginBottom + *bottom);
286 if (!top && !bottom && height) {
288 top = staticVerticalPositionForOutOfFlowPositioned(layoutContext, layoutBox);
289 marginTop = marginTop.value_or(0);
290 marginBottom = marginBottom.value_or(0);
291 bottom = containingBlockHeight - (*top + *marginTop + borderTop + paddingTop + *height + paddingBottom + borderBottom + *marginBottom);
294 if (!height && !bottom && top) {
296 height = contentHeightForFormattingContextRoot(layoutContext, layoutBox);
297 marginTop = marginTop.value_or(0);
298 marginBottom = marginBottom.value_or(0);
299 bottom = containingBlockHeight - (*top + *marginTop + borderTop + paddingTop + *height + paddingBottom + borderBottom + *marginBottom);
302 if (!top && height && bottom) {
304 marginTop = marginTop.value_or(0);
305 marginBottom = marginBottom.value_or(0);
306 top = containingBlockHeight - (*marginTop + borderTop + paddingTop + *height + paddingBottom + borderBottom + *marginBottom + *bottom);
309 if (!height && top && bottom) {
311 marginTop = marginTop.value_or(0);
312 marginBottom = marginBottom.value_or(0);
313 height = containingBlockHeight - (*top + *marginTop + borderTop + paddingTop + paddingBottom + borderBottom + *marginBottom + *bottom);
316 if (!bottom && top && height) {
318 marginTop = marginTop.value_or(0);
319 marginBottom = marginBottom.value_or(0);
320 bottom = containingBlockHeight - (*top + *marginTop + borderTop + paddingTop + *height + paddingBottom + borderBottom + *marginBottom);
327 ASSERT(marginBottom);
329 LOG_WITH_STREAM(FormattingContextLayout, stream << "[Position][Height][Margin] -> out-of-flow non-replaced -> top(" << *top << "px) bottom(" << *bottom << "px) height(" << *height << "px) margin(" << *marginTop << "px, " << *marginBottom << "px) layoutBox(" << &layoutBox << ")");
330 return { *top, *bottom, { *height, { *marginTop, *marginBottom }, { } } };
333 HorizontalGeometry FormattingContext::Geometry::outOfFlowNonReplacedHorizontalGeometry(LayoutContext& layoutContext, const FormattingContext& formattingContext, const Box& layoutBox, std::optional<LayoutUnit> precomputedWidth)
335 ASSERT(layoutBox.isOutOfFlowPositioned() && !layoutBox.replaced());
337 // 10.3.7 Absolutely positioned, non-replaced elements
339 // 'left' + 'margin-left' + 'border-left-width' + 'padding-left' + 'width' + 'padding-right' + 'border-right-width' + 'margin-right' + 'right'
340 // = width of containing block
342 // If all three of 'left', 'width', and 'right' are 'auto': First set any 'auto' values for 'margin-left' and 'margin-right' to 0.
343 // Then, if the 'direction' property of the element establishing the static-position containing block is 'ltr' set 'left' to the static
344 // position and apply rule number three below; otherwise, set 'right' to the static position and apply rule number one below.
346 // 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,
347 // 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
348 // solve for 'margin-right' ('margin-left'). If one of 'margin-left' or 'margin-right' is 'auto', solve the equation for that value.
349 // If the values are over-constrained, ignore the value for 'left' (in case the 'direction' property of the containing block is 'rtl') or 'right'
350 // (in case 'direction' is 'ltr') and solve for that value.
352 // Otherwise, set 'auto' values for 'margin-left' and 'margin-right' to 0, and pick the one of the following six rules that applies.
354 // 1. 'left' and 'width' are 'auto' and 'right' is not 'auto', then the width is shrink-to-fit. Then solve for 'left'
355 // 2. 'left' and 'right' are 'auto' and 'width' is not 'auto', then if the 'direction' property of the element establishing the static-position
356 // containing block is 'ltr' set 'left' to the static position, otherwise set 'right' to the static position.
357 // Then solve for 'left' (if 'direction is 'rtl') or 'right' (if 'direction' is 'ltr').
358 // 3. 'width' and 'right' are 'auto' and 'left' is not 'auto', then the width is shrink-to-fit . Then solve for 'right'
359 // 4. 'left' is 'auto', 'width' and 'right' are not 'auto', then solve for 'left'
360 // 5. 'width' is 'auto', 'left' and 'right' are not 'auto', then solve for 'width'
361 // 6. 'right' is 'auto', 'left' and 'width' are not 'auto', then solve for 'right'
363 auto& style = layoutBox.style();
364 auto& displayBox = layoutContext.displayBoxForLayoutBox(layoutBox);
365 auto& containingBlock = *layoutBox.containingBlock();
366 auto containingBlockWidth = layoutContext.displayBoxForLayoutBox(containingBlock).contentBoxWidth();
367 auto isLeftToRightDirection = containingBlock.style().isLeftToRightDirection();
369 auto left = computedValueIfNotAuto(style.logicalLeft(), containingBlockWidth);
370 auto right = computedValueIfNotAuto(style.logicalRight(), containingBlockWidth);
371 auto width = computedValueIfNotAuto(precomputedWidth ? Length { precomputedWidth.value(), Fixed } : style.logicalWidth(), containingBlockWidth);
372 auto marginLeft = computedValueIfNotAuto(style.marginLeft(), containingBlockWidth);
373 auto marginRight = computedValueIfNotAuto(style.marginRight(), containingBlockWidth);
374 auto nonComputedMarginLeft = marginLeft.value_or(0);
375 auto nonComputedMarginRight = marginRight.value_or(0);
376 auto paddingLeft = displayBox.paddingLeft().value_or(0);
377 auto paddingRight = displayBox.paddingRight().value_or(0);
378 auto borderLeft = displayBox.borderLeft();
379 auto borderRight = displayBox.borderRight();
381 if (!left && !width && !right) {
382 // If all three of 'left', 'width', and 'right' are 'auto': First set any 'auto' values for 'margin-left' and 'margin-right' to 0.
383 // Then, if the 'direction' property of the element establishing the static-position containing block is 'ltr' set 'left' to the static
384 // position and apply rule number three below; otherwise, set 'right' to the static position and apply rule number one below.
385 marginLeft = marginLeft.value_or(0);
386 marginRight = marginRight.value_or(0);
388 auto staticHorizontalPosition = staticHorizontalPositionForOutOfFlowPositioned(layoutContext, layoutBox);
389 if (isLeftToRightDirection)
390 left = staticHorizontalPosition;
392 right = staticHorizontalPosition;
393 } else if (left && width && right) {
394 // 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,
395 // 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
396 // solve for 'margin-right' ('margin-left'). If one of 'margin-left' or 'margin-right' is 'auto', solve the equation for that value.
397 // If the values are over-constrained, ignore the value for 'left' (in case the 'direction' property of the containing block is 'rtl') or 'right'
398 // (in case 'direction' is 'ltr') and solve for that value.
399 if (!marginLeft && !marginRight) {
400 auto marginLeftAndRight = containingBlockWidth - (*left + borderLeft + paddingLeft + *width + paddingRight + borderRight + *right);
401 if (marginLeftAndRight >= 0)
402 marginLeft = marginRight = marginLeftAndRight / 2;
404 if (isLeftToRightDirection) {
405 marginLeft = LayoutUnit { 0 };
406 marginRight = containingBlockWidth - (*left + *marginLeft + borderLeft + paddingLeft + *width + paddingRight + borderRight + *right);
408 marginRight = LayoutUnit { 0 };
409 marginLeft = containingBlockWidth - (*left + borderLeft + paddingLeft + *width + paddingRight + borderRight + *marginRight + *right);
412 } else if (!marginLeft) {
413 marginLeft = containingBlockWidth - (*left + borderLeft + paddingLeft + *width + paddingRight + borderRight + *marginRight + *right);
414 // Overconstrained? Ignore right (left).
415 if (*marginLeft < 0) {
416 if (isLeftToRightDirection)
417 marginLeft = containingBlockWidth - (*left + borderLeft + paddingLeft + *width + paddingRight + borderRight + *marginRight);
419 marginLeft = containingBlockWidth - (borderLeft + paddingLeft + *width + paddingRight + borderRight + *marginRight + *right);
421 } else if (!marginRight) {
422 marginRight = containingBlockWidth - (*left + *marginLeft + borderLeft + paddingLeft + *width + paddingRight + borderRight + *right);
423 // Overconstrained? Ignore right (left).
424 if (*marginRight < 0) {
425 if (isLeftToRightDirection)
426 marginRight = containingBlockWidth - (*left + *marginLeft + borderLeft + paddingLeft + *width + paddingRight + borderRight);
428 marginRight = containingBlockWidth - (*marginLeft + borderLeft + paddingLeft + *width + paddingRight + borderRight + *right);
432 // Otherwise, set 'auto' values for 'margin-left' and 'margin-right' to 0, and pick the one of the following six rules that applies.
433 marginLeft = marginLeft.value_or(0);
434 marginRight = marginRight.value_or(0);
440 if (!left && !width && right) {
442 width = shrinkToFitWidth(layoutContext, formattingContext, layoutBox);
443 left = containingBlockWidth - (*marginLeft + borderLeft + paddingLeft + *width + paddingRight + borderRight + *marginRight + *right);
444 } else if (!left && !right && width) {
446 auto staticHorizontalPosition = staticHorizontalPositionForOutOfFlowPositioned(layoutContext, layoutBox);
447 if (isLeftToRightDirection) {
448 left = staticHorizontalPosition;
449 right = containingBlockWidth - (*left + *marginLeft + borderLeft + paddingLeft + *width + paddingRight + borderRight + *marginRight);
451 right = staticHorizontalPosition;
452 left = containingBlockWidth - (*marginLeft + borderLeft + paddingLeft + *width + paddingRight + borderRight + *marginRight + *right);
454 } else if (!width && !right && left) {
456 width = shrinkToFitWidth(layoutContext, formattingContext, layoutBox);
457 right = containingBlockWidth - (*left + *marginLeft + borderLeft + paddingLeft + *width + paddingRight + borderRight + *marginRight);
458 } else if (!left && width && right) {
460 left = containingBlockWidth - (*marginLeft + borderLeft + paddingLeft + *width + paddingRight + borderRight + *marginRight + *right);
461 } else if (!width && left && right) {
463 width = containingBlockWidth - (*left + *marginLeft + borderLeft + paddingLeft + paddingRight + borderRight + *marginRight + *right);
464 } else if (!right && left && width) {
466 right = containingBlockWidth - (*left + *marginLeft + borderLeft + paddingLeft + *width + paddingRight + borderRight + *marginRight);
475 LOG_WITH_STREAM(FormattingContextLayout, stream << "[Position][Width][Margin] -> out-of-flow non-replaced -> left(" << *left << "px) right(" << *right << "px) width(" << *width << "px) margin(" << *marginLeft << "px, " << *marginRight << "px) layoutBox(" << &layoutBox << ")");
476 return { *left, *right, { *width, { *marginLeft, *marginRight }, { nonComputedMarginLeft, nonComputedMarginRight } } };
479 VerticalGeometry FormattingContext::Geometry::outOfFlowReplacedVerticalGeometry(const LayoutContext& layoutContext, const Box& layoutBox, std::optional<LayoutUnit> precomputedHeight)
481 ASSERT(layoutBox.isOutOfFlowPositioned() && layoutBox.replaced());
483 // 10.6.5 Absolutely positioned, replaced elements
485 // The used value of 'height' is determined as for inline replaced elements.
486 // If 'margin-top' or 'margin-bottom' is specified as 'auto' its used value is determined by the rules below.
487 // 1. If both 'top' and 'bottom' have the value 'auto', replace 'top' with the element's static position.
488 // 2. If 'bottom' is 'auto', replace any 'auto' on 'margin-top' or 'margin-bottom' with '0'.
489 // 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.
490 // 4. If at this point there is only one 'auto' left, solve the equation for that value.
491 // 5. If at this point the values are over-constrained, ignore the value for 'bottom' and solve for that value.
493 auto& style = layoutBox.style();
494 auto& displayBox = layoutContext.displayBoxForLayoutBox(layoutBox);
495 auto& containingBlockDisplayBox = layoutContext.displayBoxForLayoutBox(*layoutBox.containingBlock());
496 auto containingBlockHeight = containingBlockDisplayBox.height();
497 auto containingBlockWidth = containingBlockDisplayBox.width();
499 auto top = computedValueIfNotAuto(style.logicalTop(), containingBlockWidth);
500 auto bottom = computedValueIfNotAuto(style.logicalBottom(), containingBlockWidth);
501 auto height = inlineReplacedHeightAndMargin(layoutContext, layoutBox, precomputedHeight).height;
502 auto marginTop = computedValueIfNotAuto(style.marginTop(), containingBlockWidth);
503 auto marginBottom = computedValueIfNotAuto(style.marginBottom(), containingBlockWidth);
504 auto paddingTop = displayBox.paddingTop().value_or(0);
505 auto paddingBottom = displayBox.paddingBottom().value_or(0);
506 auto borderTop = displayBox.borderTop();
507 auto borderBottom = displayBox.borderBottom();
509 if (!top && !bottom) {
511 top = staticVerticalPositionForOutOfFlowPositioned(layoutContext, layoutBox);
516 marginTop = marginTop.value_or(0);
517 marginBottom = marginBottom.value_or(0);
520 if (!marginTop && !marginBottom) {
522 auto marginTopAndBottom = containingBlockHeight - (*top + borderTop + paddingTop + height + paddingBottom + borderBottom + *bottom);
523 marginTop = marginBottom = marginTopAndBottom / 2;
528 top = containingBlockHeight - (*marginTop + borderTop + paddingTop + height + paddingBottom + borderBottom + *marginBottom + *bottom);
531 bottom = containingBlockHeight - (*top + *marginTop + borderTop + paddingTop + height + paddingBottom + borderBottom + *marginBottom);
534 marginTop = containingBlockHeight - (*top + borderTop + paddingTop + height + paddingBottom + borderBottom + *marginBottom + *bottom);
537 marginBottom = containingBlockHeight - (*top + *marginTop + borderTop + paddingTop + height + paddingBottom + borderBottom + *bottom);
540 auto boxHeight = *top + *marginTop + borderTop + paddingTop + height + paddingBottom + borderBottom + *marginBottom + *bottom;
541 if (boxHeight > containingBlockHeight)
542 bottom = containingBlockHeight - (*top + *marginTop + borderTop + paddingTop + height + paddingBottom + borderBottom + *marginBottom);
544 LOG_WITH_STREAM(FormattingContextLayout, stream << "[Position][Height][Margin] -> out-of-flow replaced -> top(" << *top << "px) bottom(" << *bottom << "px) height(" << height << "px) margin(" << *marginTop << "px, " << *marginBottom << "px) layoutBox(" << &layoutBox << ")");
545 return { *top, *bottom, { height, { *marginTop, *marginBottom }, { } } };
548 HorizontalGeometry FormattingContext::Geometry::outOfFlowReplacedHorizontalGeometry(const LayoutContext& layoutContext, const Box& layoutBox, std::optional<LayoutUnit> precomputedWidth)
550 ASSERT(layoutBox.isOutOfFlowPositioned() && layoutBox.replaced());
552 // 10.3.8 Absolutely positioned, replaced elements
553 // 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:
555 // 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.
556 // 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',
557 // set 'left' to the static position; else if 'direction' is 'rtl', set 'right' to the static position.
558 // 2. If 'left' or 'right' are 'auto', replace any 'auto' on 'margin-left' or 'margin-right' with '0'.
559 // 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,
560 // 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
561 // solve for 'margin-right' ('margin-left').
562 // 4. If at this point there is an 'auto' left, solve the equation for that value.
563 // 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
564 // 'right' (in case 'direction' is 'ltr') and solve for that value.
566 auto& style = layoutBox.style();
567 auto& displayBox = layoutContext.displayBoxForLayoutBox(layoutBox);
568 auto& containingBlock = *layoutBox.containingBlock();
569 auto containingBlockWidth = layoutContext.displayBoxForLayoutBox(containingBlock).contentBoxWidth();
570 auto isLeftToRightDirection = containingBlock.style().isLeftToRightDirection();
572 auto left = computedValueIfNotAuto(style.logicalLeft(), containingBlockWidth);
573 auto right = computedValueIfNotAuto(style.logicalRight(), containingBlockWidth);
574 auto marginLeft = computedValueIfNotAuto(style.marginLeft(), containingBlockWidth);
575 auto marginRight = computedValueIfNotAuto(style.marginRight(), containingBlockWidth);
576 auto nonComputedMarginLeft = marginLeft.value_or(0);
577 auto nonComputedMarginRight = marginRight.value_or(0);
578 auto width = inlineReplacedWidthAndMargin(layoutContext, layoutBox, precomputedWidth).width;
579 auto paddingLeft = displayBox.paddingLeft().value_or(0);
580 auto paddingRight = displayBox.paddingRight().value_or(0);
581 auto borderLeft = displayBox.borderLeft();
582 auto borderRight = displayBox.borderRight();
584 if (!left && !right) {
586 auto staticHorizontalPosition = staticHorizontalPositionForOutOfFlowPositioned(layoutContext, layoutBox);
587 if (isLeftToRightDirection)
588 left = staticHorizontalPosition;
590 right = staticHorizontalPosition;
593 if (!left || !right) {
595 marginLeft = marginLeft.value_or(0);
596 marginRight = marginRight.value_or(0);
599 if (!marginLeft && !marginRight) {
601 auto marginLeftAndRight = containingBlockWidth - (*left + borderLeft + paddingLeft + width + paddingRight + borderRight + *right);
602 if (marginLeftAndRight >= 0)
603 marginLeft = marginRight = marginLeftAndRight / 2;
605 if (isLeftToRightDirection) {
606 marginLeft = LayoutUnit { 0 };
607 marginRight = containingBlockWidth - (*left + *marginLeft + borderLeft + paddingLeft + width + paddingRight + borderRight + *right);
609 marginRight = LayoutUnit { 0 };
610 marginLeft = containingBlockWidth - (*left + borderLeft + paddingLeft + width + paddingRight + borderRight + *marginRight + *right);
617 left = containingBlockWidth - (*marginLeft + borderLeft + paddingLeft + width + paddingRight + borderRight + *marginRight + *right);
620 right = containingBlockWidth - (*left + *marginLeft + borderLeft + paddingLeft + width + paddingRight + borderRight + *marginRight);
623 marginLeft = containingBlockWidth - (*left + borderLeft + paddingLeft + width + paddingRight + borderRight + *marginRight + *right);
626 marginRight = containingBlockWidth - (*left + *marginLeft + borderLeft + paddingLeft + width + paddingRight + borderRight + *right);
628 auto boxWidth = (*left + *marginLeft + borderLeft + paddingLeft + width + paddingRight + borderRight + *marginRight + *right);
629 if (boxWidth > containingBlockWidth) {
630 // #5 Over-constrained?
631 if (isLeftToRightDirection)
632 right = containingBlockWidth - (*left + *marginLeft + borderLeft + paddingLeft + width + paddingRight + borderRight + *marginRight);
634 left = containingBlockWidth - (*marginLeft + borderLeft + paddingLeft + width + paddingRight + borderRight + *marginRight + *right);
642 LOG_WITH_STREAM(FormattingContextLayout, stream << "[Position][Width][Margin] -> out-of-flow replaced -> left(" << *left << "px) right(" << *right << "px) width(" << width << "px) margin(" << *marginLeft << "px, " << *marginRight << "px) layoutBox(" << &layoutBox << ")");
643 return { *left, *right, { width, { *marginLeft, *marginRight }, { nonComputedMarginLeft, nonComputedMarginRight } } };
646 HeightAndMargin FormattingContext::Geometry::complicatedCases(const LayoutContext& layoutContext, const Box& layoutBox, std::optional<LayoutUnit> precomputedHeight)
648 ASSERT(!layoutBox.replaced());
649 // TODO: Use complicated-case for document renderer for now (see BlockFormattingContext::Geometry::inFlowHeightAndMargin).
650 ASSERT((layoutBox.isBlockLevelBox() && layoutBox.isInFlow() && !layoutBox.isOverflowVisible()) || layoutBox.isInlineBlockBox() || layoutBox.isFloatingPositioned() || layoutBox.isDocumentBox());
652 // 10.6.6 Complicated cases
654 // 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).
655 // 'Inline-block', non-replaced elements.
656 // Floating, non-replaced elements.
658 // 1. If 'margin-top', or 'margin-bottom' are 'auto', their used value is 0.
659 // 2. If 'height' is 'auto', the height depends on the element's descendants per 10.6.7.
661 auto& style = layoutBox.style();
662 auto& containingBlock = *layoutBox.containingBlock();
663 auto& containingBlockDisplayBox = layoutContext.displayBoxForLayoutBox(containingBlock);
664 auto containingBlockWidth = containingBlockDisplayBox.contentBoxWidth();
666 auto height = fixedValue(precomputedHeight ? Length { precomputedHeight.value(), Fixed } : style.logicalHeight());
667 auto marginTop = computedValueIfNotAuto(style.marginTop(), containingBlockWidth);
668 auto marginBottom = computedValueIfNotAuto(style.marginBottom(), containingBlockWidth);
671 marginTop = marginTop.value_or(0);
672 marginBottom = marginBottom.value_or(0);
675 if (style.logicalHeight().isAuto())
676 height = contentHeightForFormattingContextRoot(layoutContext, layoutBox);
678 ASSERT_NOT_IMPLEMENTED_YET();
683 ASSERT(marginBottom);
685 LOG_WITH_STREAM(FormattingContextLayout, stream << "[Height][Margin] -> floating non-replaced -> height(" << *height << "px) margin(" << *marginTop << "px, " << *marginBottom << "px) -> layoutBox(" << &layoutBox << ")");
686 return HeightAndMargin { *height, { *marginTop, *marginBottom }, { } };
689 WidthAndMargin FormattingContext::Geometry::floatingNonReplacedWidthAndMargin(LayoutContext& layoutContext, const FormattingContext& formattingContext, const Box& layoutBox, std::optional<LayoutUnit> precomputedWidth)
691 ASSERT(layoutBox.isFloatingPositioned() && !layoutBox.replaced());
693 // 10.3.5 Floating, non-replaced elements
695 // 1. If 'margin-left', or 'margin-right' are computed as 'auto', their used value is '0'.
696 // 2. If 'width' is computed as 'auto', the used value is the "shrink-to-fit" width.
698 auto& containingBlock = *layoutBox.containingBlock();
699 auto containingBlockWidth = layoutContext.displayBoxForLayoutBox(containingBlock).contentBoxWidth();
702 auto margin = computedNonCollapsedHorizontalMarginValue(layoutContext, layoutBox);
704 auto width = computedValueIfNotAuto(precomputedWidth ? Length { precomputedWidth.value(), Fixed } : layoutBox.style().logicalWidth(), containingBlockWidth);
706 width = shrinkToFitWidth(layoutContext, formattingContext, layoutBox);
708 LOG_WITH_STREAM(FormattingContextLayout, stream << "[Width][Margin] -> floating non-replaced -> width(" << *width << "px) margin(" << margin.left << "px, " << margin.right << "px) -> layoutBox(" << &layoutBox << ")");
709 return WidthAndMargin { *width, margin, margin };
712 HeightAndMargin FormattingContext::Geometry::floatingReplacedHeightAndMargin(const LayoutContext& layoutContext, const Box& layoutBox, std::optional<LayoutUnit> precomputedHeight)
714 ASSERT(layoutBox.isFloatingPositioned() && layoutBox.replaced());
716 // 10.6.2 Inline replaced elements, block-level replaced elements in normal flow, 'inline-block'
717 // replaced elements in normal flow and floating replaced elements
718 LOG_WITH_STREAM(FormattingContextLayout, stream << "[Height][Margin] -> floating replaced -> redirected to inline replaced");
719 return inlineReplacedHeightAndMargin(layoutContext, layoutBox, precomputedHeight);
722 WidthAndMargin FormattingContext::Geometry::floatingReplacedWidthAndMargin(const LayoutContext& layoutContext, const Box& layoutBox, std::optional<LayoutUnit> precomputedWidth)
724 ASSERT(layoutBox.isFloatingPositioned() && layoutBox.replaced());
726 // 10.3.6 Floating, replaced elements
728 // 1. If 'margin-left' or 'margin-right' are computed as 'auto', their used value is '0'.
729 // 2. The used value of 'width' is determined as for inline replaced elements.
730 auto margin = computedNonCollapsedHorizontalMarginValue(layoutContext, layoutBox);
732 LOG_WITH_STREAM(FormattingContextLayout, stream << "[Height][Margin] -> floating replaced -> redirected to inline replaced");
733 return inlineReplacedWidthAndMargin(layoutContext, layoutBox, precomputedWidth, margin.left, margin.right);
736 VerticalGeometry FormattingContext::Geometry::outOfFlowVerticalGeometry(const LayoutContext& layoutContext, const Box& layoutBox, std::optional<LayoutUnit> precomputedHeight)
738 ASSERT(layoutBox.isOutOfFlowPositioned());
740 if (!layoutBox.replaced())
741 return outOfFlowNonReplacedVerticalGeometry(layoutContext, layoutBox, precomputedHeight);
742 return outOfFlowReplacedVerticalGeometry(layoutContext, layoutBox, precomputedHeight);
745 HorizontalGeometry FormattingContext::Geometry::outOfFlowHorizontalGeometry(LayoutContext& layoutContext, const FormattingContext& formattingContext, const Box& layoutBox, std::optional<LayoutUnit> precomputedWidth)
747 ASSERT(layoutBox.isOutOfFlowPositioned());
749 if (!layoutBox.replaced())
750 return outOfFlowNonReplacedHorizontalGeometry(layoutContext, formattingContext, layoutBox, precomputedWidth);
751 return outOfFlowReplacedHorizontalGeometry(layoutContext, layoutBox, precomputedWidth);
754 HeightAndMargin FormattingContext::Geometry::floatingHeightAndMargin(const LayoutContext& layoutContext, const Box& layoutBox, std::optional<LayoutUnit> precomputedHeight)
756 ASSERT(layoutBox.isFloatingPositioned());
758 if (!layoutBox.replaced())
759 return complicatedCases(layoutContext, layoutBox, precomputedHeight);
760 return floatingReplacedHeightAndMargin(layoutContext, layoutBox, precomputedHeight);
763 WidthAndMargin FormattingContext::Geometry::floatingWidthAndMargin(LayoutContext& layoutContext, const FormattingContext& formattingContext, const Box& layoutBox, std::optional<LayoutUnit> precomputedWidth)
765 ASSERT(layoutBox.isFloatingPositioned());
767 if (!layoutBox.replaced())
768 return floatingNonReplacedWidthAndMargin(layoutContext, formattingContext, layoutBox, precomputedWidth);
769 return floatingReplacedWidthAndMargin(layoutContext, layoutBox, precomputedWidth);
772 HeightAndMargin FormattingContext::Geometry::inlineReplacedHeightAndMargin(const LayoutContext& layoutContext, const Box& layoutBox, std::optional<LayoutUnit> precomputedHeight)
774 ASSERT((layoutBox.isOutOfFlowPositioned() || layoutBox.isFloatingPositioned() || layoutBox.isInFlow()) && layoutBox.replaced());
776 // 10.6.2 Inline replaced elements, block-level replaced elements in normal flow, 'inline-block' replaced elements in normal flow and floating replaced elements
778 // 1. If 'margin-top', or 'margin-bottom' are 'auto', their used value is 0.
779 // 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'.
780 // 3. Otherwise, if 'height' has a computed value of 'auto', and the element has an intrinsic ratio then the used value of 'height' is:
781 // (used width) / (intrinsic ratio)
782 // 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'.
783 // 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
784 // 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.
787 auto margin = computedNonCollapsedVerticalMarginValue(layoutContext, layoutBox);
789 auto& style = layoutBox.style();
790 auto replaced = layoutBox.replaced();
791 auto& containingBlockDisplayBox = layoutContext.displayBoxForLayoutBox(*layoutBox.containingBlock());
792 auto containingBlockWidth = containingBlockDisplayBox.width();
794 auto height = fixedValue(precomputedHeight ? Length { precomputedHeight.value(), Fixed } : style.logicalHeight());
795 auto heightIsAuto = style.logicalHeight().isAuto();
796 auto width = computedValueIfNotAuto(style.logicalWidth(), containingBlockWidth);
798 if (!height && !heightIsAuto)
799 ASSERT_NOT_IMPLEMENTED_YET();
801 if (heightIsAuto && !width && replaced->hasIntrinsicHeight()) {
803 height = replaced->intrinsicHeight();
804 } else if (heightIsAuto && replaced->hasIntrinsicRatio()) {
806 height = *width / replaced->intrinsicRatio();
807 } else if (heightIsAuto && replaced->hasIntrinsicHeight()) {
809 height = replaced->intrinsicHeight();
810 } else if (heightIsAuto) {
817 LOG_WITH_STREAM(FormattingContextLayout, stream << "[Height][Margin] -> inflow replaced -> height(" << *height << "px) margin(" << margin.top << "px, " << margin.bottom << "px) -> layoutBox(" << &layoutBox << ")");
818 return { *height, margin, { } };
821 WidthAndMargin FormattingContext::Geometry::inlineReplacedWidthAndMargin(const LayoutContext& layoutContext, const Box& layoutBox,
822 std::optional<LayoutUnit> precomputedWidth, std::optional<LayoutUnit> precomputedMarginLeft, std::optional<LayoutUnit> precomputedMarginRight)
824 ASSERT((layoutBox.isOutOfFlowPositioned() || layoutBox.isFloatingPositioned() || layoutBox.isInFlow()) && layoutBox.replaced());
826 // 10.3.2 Inline, replaced elements
828 // A computed value of 'auto' for 'margin-left' or 'margin-right' becomes a used value of '0'.
830 // 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'.
832 // 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;
833 // or if 'width' has a computed value of 'auto', 'height' has some other computed value, and the element does have an intrinsic ratio;
834 // then the used value of 'width' is: (used height) * (intrinsic ratio)
836 // 3. If 'height' and 'width' both have computed values of 'auto' and the element has an intrinsic ratio but no intrinsic height or width,
837 // 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
838 // 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.
840 // 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'.
842 // 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.
843 // 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.
845 auto& style = layoutBox.style();
846 auto& containingBlockDisplayBox = layoutContext.displayBoxForLayoutBox(*layoutBox.containingBlock());
847 auto containingBlockWidth = containingBlockDisplayBox.width();
849 auto computeMarginRight = [&]() {
850 if (precomputedMarginRight)
851 return precomputedMarginRight.value();
852 auto marginRight = computedValueIfNotAuto(style.marginRight(), containingBlockWidth);
853 return marginRight.value_or(LayoutUnit { 0 });
856 auto computeMarginLeft = [&]() {
857 if (precomputedMarginLeft)
858 return precomputedMarginLeft.value();
859 auto marginLeft = computedValueIfNotAuto(style.marginLeft(), containingBlockWidth);
860 return marginLeft.value_or(LayoutUnit { 0 });
863 auto replaced = layoutBox.replaced();
866 auto marginLeft = computeMarginLeft();
867 auto marginRight = computeMarginRight();
868 auto nonComputedMarginLeft = computedValueIfNotAuto(style.marginLeft(), containingBlockWidth).value_or(0);
869 auto nonComputedMarginRight = computedValueIfNotAuto(style.marginRight(), containingBlockWidth).value_or(0);
870 auto width = computedValueIfNotAuto(precomputedWidth ? Length { precomputedWidth.value(), Fixed } : style.logicalWidth(), containingBlockWidth);
872 auto heightIsAuto = style.logicalHeight().isAuto();
873 auto height = fixedValue(style.logicalHeight());
874 if (!height && !heightIsAuto)
875 ASSERT_NOT_IMPLEMENTED_YET();
877 if (!width && heightIsAuto && replaced->hasIntrinsicWidth()) {
879 width = replaced->intrinsicWidth();
880 } else if ((!width && heightIsAuto && !replaced->hasIntrinsicWidth() && replaced->hasIntrinsicHeight() && replaced->hasIntrinsicRatio())
881 || (!width && height && replaced->hasIntrinsicRatio())) {
883 width = height.value_or(replaced->hasIntrinsicHeight()) * replaced->intrinsicRatio();
884 } else if (!width && heightIsAuto && replaced->hasIntrinsicRatio() && !replaced->hasIntrinsicWidth() && !replaced->hasIntrinsicHeight()) {
886 // FIXME: undefined but surely doable.
887 ASSERT_NOT_IMPLEMENTED_YET();
888 } else if (!width && replaced->hasIntrinsicWidth()) {
890 width = replaced->intrinsicWidth();
898 LOG_WITH_STREAM(FormattingContextLayout, stream << "[Width][Margin] -> inflow replaced -> width(" << *width << "px) margin(" << marginLeft << "px, " << marginRight << "px) -> layoutBox(" << &layoutBox << ")");
899 return { *width, { marginLeft, marginRight }, { nonComputedMarginLeft, nonComputedMarginRight } };
902 Edges FormattingContext::Geometry::computedBorder(const LayoutContext&, const Box& layoutBox)
904 auto& style = layoutBox.style();
905 LOG_WITH_STREAM(FormattingContextLayout, stream << "[Border] -> layoutBox: " << &layoutBox);
907 { style.borderLeft().boxModelWidth(), style.borderRight().boxModelWidth() },
908 { style.borderTop().boxModelWidth(), style.borderBottom().boxModelWidth() }
912 std::optional<Edges> FormattingContext::Geometry::computedPadding(const LayoutContext& layoutContext, const Box& layoutBox)
914 if (!layoutBox.isPaddingApplicable())
917 auto& style = layoutBox.style();
918 auto containingBlockWidth = layoutContext.displayBoxForLayoutBox(*layoutBox.containingBlock()).contentBoxWidth();
919 LOG_WITH_STREAM(FormattingContextLayout, stream << "[Padding] -> layoutBox: " << &layoutBox);
921 { valueForLength(style.paddingLeft(), containingBlockWidth), valueForLength(style.paddingRight(), containingBlockWidth) },
922 { valueForLength(style.paddingTop(), containingBlockWidth), valueForLength(style.paddingBottom(), containingBlockWidth) }
926 HorizontalEdges FormattingContext::Geometry::computedNonCollapsedHorizontalMarginValue(const LayoutContext& layoutContext, const Box& layoutBox)
928 auto& style = layoutBox.style();
929 auto containingBlockWidth = layoutContext.displayBoxForLayoutBox(*layoutBox.containingBlock()).contentBoxWidth();
931 auto marginLeft = computedValueIfNotAuto(style.marginLeft(), containingBlockWidth).value_or(LayoutUnit { 0 });
932 auto marginRight = computedValueIfNotAuto(style.marginRight(), containingBlockWidth).value_or(LayoutUnit { 0 });
934 LOG_WITH_STREAM(FormattingContextLayout, stream << "[Margin] -> non collapsed horizontal -> margin(" << marginLeft << "px, " << marginRight << "px) -> layoutBox: " << &layoutBox);
935 return { marginLeft, marginRight };
938 VerticalEdges FormattingContext::Geometry::computedNonCollapsedVerticalMarginValue(const LayoutContext& layoutContext, const Box& layoutBox)
940 auto& style = layoutBox.style();
941 auto containingBlockWidth = layoutContext.displayBoxForLayoutBox(*layoutBox.containingBlock()).contentBoxWidth();
943 auto marginTop = computedValueIfNotAuto(style.marginTop(), containingBlockWidth).value_or(LayoutUnit { 0 });
944 auto marginBottom = computedValueIfNotAuto(style.marginBottom(), containingBlockWidth).value_or(LayoutUnit { 0 });
946 LOG_WITH_STREAM(FormattingContextLayout, stream << "[Margin] -> non collapsed vertical -> margin(" << marginTop << "px, " << marginBottom << "px) -> layoutBox: " << &layoutBox);
947 return { marginTop, marginBottom };