| 1 | /* |
| 2 | * Copyright (C) 2018 Apple Inc. All rights reserved. |
| 3 | * |
| 4 | * Redistribution and use in source and binary forms, with or without |
| 5 | * modification, are permitted provided that the following conditions |
| 6 | * are met: |
| 7 | * 1. Redistributions of source code must retain the above copyright |
| 8 | * notice, this list of conditions and the following disclaimer. |
| 9 | * 2. Redistributions in binary form must reproduce the above copyright |
| 10 | * notice, this list of conditions and the following disclaimer in the |
| 11 | * documentation and/or other materials provided with the distribution. |
| 12 | * |
| 13 | * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' |
| 14 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, |
| 15 | * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
| 16 | * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS |
| 17 | * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR |
| 18 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
| 19 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
| 20 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN |
| 21 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
| 22 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF |
| 23 | * THE POSSIBILITY OF SUCH DAMAGE. |
| 24 | */ |
| 25 | |
| 26 | #include "config.h" |
| 27 | #include "FormattingContext.h" |
| 28 | |
| 29 | #if ENABLE(LAYOUT_FORMATTING_CONTEXT) |
| 30 | |
| 31 | #include "FloatingState.h" |
| 32 | #include "FormattingState.h" |
| 33 | #include "InlineFormattingState.h" |
| 34 | |
| 35 | namespace WebCore { |
| 36 | namespace Layout { |
| 37 | |
| 38 | static inline bool isHeightAuto(const Box& layoutBox) |
| 39 | { |
| 40 | // 10.5 Content height: the 'height' property |
| 41 | // |
| 42 | // The percentage is calculated with respect to the height of the generated box's containing block. |
| 43 | // If the height of the containing block is not specified explicitly (i.e., it depends on content height), |
| 44 | // and this element is not absolutely positioned, the used height is calculated as if 'auto' was specified. |
| 45 | |
| 46 | auto height = layoutBox.style().logicalHeight(); |
| 47 | if (height.isAuto()) |
| 48 | return true; |
| 49 | |
| 50 | if (height.isPercent()) { |
| 51 | if (layoutBox.isOutOfFlowPositioned()) |
| 52 | return false; |
| 53 | |
| 54 | return !layoutBox.containingBlock()->style().logicalHeight().isFixed(); |
| 55 | } |
| 56 | |
| 57 | return false; |
| 58 | } |
| 59 | |
| 60 | Optional<LayoutUnit> FormattingContext::Geometry::computedHeightValue(const LayoutState& layoutState, const Box& layoutBox, HeightType heightType) |
| 61 | { |
| 62 | auto& style = layoutBox.style(); |
| 63 | auto height = heightType == HeightType::Normal ? style.logicalHeight() : heightType == HeightType::Min ? style.logicalMinHeight() : style.logicalMaxHeight(); |
| 64 | if (height.isUndefined() || height.isAuto()) |
| 65 | return { }; |
| 66 | |
| 67 | if (height.isFixed()) |
| 68 | return { height.value() }; |
| 69 | |
| 70 | 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 | |
| 90 | static 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 | |
| 148 | Optional<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 | |
| 159 | Optional<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'). |
| 170 | Optional<LayoutUnit> FormattingContext::Geometry::computedMaxHeight(const LayoutState& layoutState, const Box& layoutBox) |
| 171 | { |
| 172 | return computedHeightValue(layoutState, layoutBox, HeightType::Max); |
| 173 | } |
| 174 | |
| 175 | Optional<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 | |
| 183 | static 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 | |
| 216 | static 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 | |
| 237 | LayoutUnit 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 | |
| 260 | VerticalGeometry 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 | |
| 385 | HorizontalGeometry 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 | |
| 539 | VerticalGeometry 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 | |
| 623 | HorizontalGeometry 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 | |
| 730 | HeightAndMargin 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 | |
| 761 | WidthAndMargin 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 | |
| 783 | HeightAndMargin 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 | |
| 793 | WidthAndMargin 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 | |
| 808 | VerticalGeometry 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 | |
| 817 | HorizontalGeometry 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 | |
| 826 | HeightAndMargin 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 | |
| 835 | WidthAndMargin 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 | |
| 844 | HeightAndMargin 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 | |
| 890 | WidthAndMargin 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 | |
| 961 | LayoutSize 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 | |
| 1037 | Edges 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 | |
| 1047 | Optional<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 | |
| 1061 | ComputedHorizontalMargin 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 | |
| 1068 | ComputedVerticalMargin 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 | |
| 1075 | FormattingContext::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 | |