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 | |