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 "InlineFormattingContext.h" |
28 | |
29 | #if ENABLE(LAYOUT_FORMATTING_CONTEXT) |
30 | |
31 | #include "InlineFormattingState.h" |
32 | #include "InlineLineBreaker.h" |
33 | #include "InlineRunProvider.h" |
34 | #include "LayoutBox.h" |
35 | #include "LayoutContainer.h" |
36 | #include "LayoutInlineBox.h" |
37 | #include "LayoutInlineContainer.h" |
38 | #include "LayoutState.h" |
39 | #include "Logging.h" |
40 | #include "Textutil.h" |
41 | #include <wtf/IsoMallocInlines.h> |
42 | #include <wtf/text/TextStream.h> |
43 | |
44 | namespace WebCore { |
45 | namespace Layout { |
46 | |
47 | WTF_MAKE_ISO_ALLOCATED_IMPL(InlineFormattingContext); |
48 | |
49 | InlineFormattingContext::InlineFormattingContext(const Box& formattingContextRoot, InlineFormattingState& formattingState) |
50 | : FormattingContext(formattingContextRoot, formattingState) |
51 | { |
52 | } |
53 | |
54 | static inline const Box* nextInPreOrder(const Box& layoutBox, const Container& root) |
55 | { |
56 | const Box* nextInPreOrder = nullptr; |
57 | if (!layoutBox.establishesFormattingContext() && is<Container>(layoutBox) && downcast<Container>(layoutBox).hasInFlowOrFloatingChild()) |
58 | return downcast<Container>(layoutBox).firstInFlowOrFloatingChild(); |
59 | |
60 | for (nextInPreOrder = &layoutBox; nextInPreOrder && nextInPreOrder != &root; nextInPreOrder = nextInPreOrder->parent()) { |
61 | if (auto* nextSibling = nextInPreOrder->nextInFlowOrFloatingSibling()) |
62 | return nextSibling; |
63 | } |
64 | return nullptr; |
65 | } |
66 | |
67 | void InlineFormattingContext::layout() const |
68 | { |
69 | if (!is<Container>(root())) |
70 | return; |
71 | |
72 | LOG_WITH_STREAM(FormattingContextLayout, stream << "[Start] -> inline formatting context -> formatting root(" << &root() << ")" ); |
73 | auto& root = downcast<Container>(this->root()); |
74 | auto usedValues = UsedHorizontalValues { layoutState().displayBoxForLayoutBox(root).contentBoxWidth() }; |
75 | auto* layoutBox = root.firstInFlowOrFloatingChild(); |
76 | // Compute width/height for non-text content and margin/border/padding for inline containers. |
77 | while (layoutBox) { |
78 | if (layoutBox->establishesFormattingContext()) |
79 | layoutFormattingContextRoot(*layoutBox, usedValues); |
80 | else if (is<Container>(*layoutBox)) { |
81 | auto& inlineContainer = downcast<InlineContainer>(*layoutBox); |
82 | computeMargin(inlineContainer, usedValues); |
83 | computeBorderAndPadding(inlineContainer, usedValues); |
84 | } else if (layoutBox->isReplaced()) |
85 | computeWidthAndHeightForReplacedInlineBox(*layoutBox, usedValues); |
86 | layoutBox = nextInPreOrder(*layoutBox, root); |
87 | } |
88 | |
89 | InlineRunProvider inlineRunProvider; |
90 | collectInlineContent(inlineRunProvider); |
91 | LineLayout(*this).layout(inlineRunProvider); |
92 | LOG_WITH_STREAM(FormattingContextLayout, stream << "[End] -> inline formatting context -> formatting root(" << &root << ")" ); |
93 | } |
94 | |
95 | void InlineFormattingContext::computeIntrinsicWidthConstraints() const |
96 | { |
97 | ASSERT(is<Container>(root())); |
98 | |
99 | auto& layoutState = this->layoutState(); |
100 | auto& root = downcast<Container>(this->root()); |
101 | ASSERT(!layoutState.formattingStateForBox(root).intrinsicWidthConstraints(root)); |
102 | |
103 | Vector<const Box*> formattingContextRootList; |
104 | auto usedValues = UsedHorizontalValues { }; |
105 | auto* layoutBox = root.firstInFlowOrFloatingChild(); |
106 | while (layoutBox) { |
107 | if (layoutBox->establishesFormattingContext()) { |
108 | formattingContextRootList.append(layoutBox); |
109 | if (layoutBox->isFloatingPositioned()) |
110 | computeIntrinsicWidthForFloatBox(*layoutBox); |
111 | else if (layoutBox->isInlineBlockBox()) |
112 | computeIntrinsicWidthForInlineBlock(*layoutBox); |
113 | else |
114 | ASSERT_NOT_REACHED(); |
115 | } else if (layoutBox->isReplaced() || is<Container>(*layoutBox)) { |
116 | computeBorderAndPadding(*layoutBox, usedValues); |
117 | // inline-block and replaced. |
118 | auto needsWidthComputation = layoutBox->isReplaced() || layoutBox->establishesFormattingContext(); |
119 | if (needsWidthComputation) |
120 | computeWidthAndMargin(*layoutBox, usedValues); |
121 | else { |
122 | // Simple inline container with no intrinsic width <span>. |
123 | computeMargin(*layoutBox, usedValues); |
124 | } |
125 | } |
126 | layoutBox = nextInPreOrder(*layoutBox, root); |
127 | } |
128 | |
129 | InlineRunProvider inlineRunProvider; |
130 | collectInlineContent(inlineRunProvider); |
131 | |
132 | auto maximumLineWidth = [&](auto availableWidth) { |
133 | LayoutUnit maxContentLogicalRight; |
134 | auto lineBreaker = InlineLineBreaker { layoutState, formattingState().inlineContent(), inlineRunProvider.runs() }; |
135 | LayoutUnit lineLogicalRight; |
136 | |
137 | // Switch to the min/max formatting root width values before formatting the lines. |
138 | for (auto* formattingRoot : formattingContextRootList) { |
139 | auto intrinsicWidths = layoutState.formattingStateForBox(*formattingRoot).intrinsicWidthConstraints(*formattingRoot); |
140 | layoutState.displayBoxForLayoutBox(*formattingRoot).setContentBoxWidth(availableWidth ? intrinsicWidths->maximum : intrinsicWidths->minimum); |
141 | } |
142 | |
143 | while (auto run = lineBreaker.nextRun(lineLogicalRight, availableWidth, !lineLogicalRight)) { |
144 | if (run->position == InlineLineBreaker::Run::Position::LineBegin) |
145 | lineLogicalRight = 0; |
146 | lineLogicalRight += run->width; |
147 | |
148 | maxContentLogicalRight = std::max(maxContentLogicalRight, lineLogicalRight); |
149 | } |
150 | return maxContentLogicalRight; |
151 | }; |
152 | |
153 | auto intrinsicWidthConstraints = Geometry::constrainByMinMaxWidth(root, { maximumLineWidth(0), maximumLineWidth(LayoutUnit::max()) }); |
154 | layoutState.formattingStateForBox(root).setIntrinsicWidthConstraints(root, intrinsicWidthConstraints); |
155 | } |
156 | |
157 | void InlineFormattingContext::computeIntrinsicWidthForFloatBox(const Box& layoutBox) const |
158 | { |
159 | ASSERT(layoutBox.isFloatingPositioned()); |
160 | auto& layoutState = this->layoutState(); |
161 | |
162 | auto usedHorizontalValues = UsedHorizontalValues { }; |
163 | computeBorderAndPadding(layoutBox, usedHorizontalValues); |
164 | computeMargin(layoutBox, usedHorizontalValues); |
165 | layoutState.createFormattingContext(layoutBox)->computeIntrinsicWidthConstraints(); |
166 | |
167 | auto usedVerticalValues = UsedVerticalValues { }; |
168 | auto heightAndMargin = Geometry::floatingHeightAndMargin(layoutState, layoutBox, usedVerticalValues, usedHorizontalValues); |
169 | |
170 | auto& displayBox = layoutState.displayBoxForLayoutBox(layoutBox); |
171 | displayBox.setContentBoxHeight(heightAndMargin.height); |
172 | displayBox.setVerticalMargin({ heightAndMargin.nonCollapsedMargin, { } }); |
173 | } |
174 | |
175 | void InlineFormattingContext::computeIntrinsicWidthForInlineBlock(const Box& layoutBox) const |
176 | { |
177 | ASSERT(layoutBox.isInlineBlockBox()); |
178 | |
179 | auto usedValues = UsedHorizontalValues { }; |
180 | computeBorderAndPadding(layoutBox, usedValues); |
181 | computeMargin(layoutBox, usedValues); |
182 | layoutState().createFormattingContext(layoutBox)->computeIntrinsicWidthConstraints(); |
183 | } |
184 | |
185 | void InlineFormattingContext::computeMargin(const Box& layoutBox, UsedHorizontalValues usedValues) const |
186 | { |
187 | auto computedHorizontalMargin = Geometry::computedHorizontalMargin(layoutBox, usedValues); |
188 | auto& displayBox = layoutState().displayBoxForLayoutBox(layoutBox); |
189 | displayBox.setHorizontalComputedMargin(computedHorizontalMargin); |
190 | displayBox.setHorizontalMargin({ computedHorizontalMargin.start.valueOr(0), computedHorizontalMargin.end.valueOr(0) }); |
191 | } |
192 | |
193 | void InlineFormattingContext::computeWidthAndMargin(const Box& layoutBox, UsedHorizontalValues usedValues) const |
194 | { |
195 | auto& layoutState = this->layoutState(); |
196 | WidthAndMargin widthAndMargin; |
197 | if (layoutBox.isFloatingPositioned()) |
198 | widthAndMargin = Geometry::floatingWidthAndMargin(layoutState, layoutBox, usedValues); |
199 | else if (layoutBox.isInlineBlockBox()) |
200 | widthAndMargin = Geometry::inlineBlockWidthAndMargin(layoutState, layoutBox, usedValues); |
201 | else if (layoutBox.replaced()) |
202 | widthAndMargin = Geometry::inlineReplacedWidthAndMargin(layoutState, layoutBox, usedValues); |
203 | else |
204 | ASSERT_NOT_REACHED(); |
205 | |
206 | auto& displayBox = layoutState.displayBoxForLayoutBox(layoutBox); |
207 | displayBox.setContentBoxWidth(widthAndMargin.width); |
208 | displayBox.setHorizontalMargin(widthAndMargin.usedMargin); |
209 | displayBox.setHorizontalComputedMargin(widthAndMargin.computedMargin); |
210 | } |
211 | |
212 | void InlineFormattingContext::computeHeightAndMargin(const Box& layoutBox) const |
213 | { |
214 | auto& layoutState = this->layoutState(); |
215 | |
216 | HeightAndMargin heightAndMargin; |
217 | if (layoutBox.isFloatingPositioned()) |
218 | heightAndMargin = Geometry::floatingHeightAndMargin(layoutState, layoutBox, { }, UsedHorizontalValues { layoutState.displayBoxForLayoutBox(*layoutBox.containingBlock()).contentBoxWidth() }); |
219 | else if (layoutBox.isInlineBlockBox()) |
220 | heightAndMargin = Geometry::inlineBlockHeightAndMargin(layoutState, layoutBox); |
221 | else if (layoutBox.replaced()) |
222 | heightAndMargin = Geometry::inlineReplacedHeightAndMargin(layoutState, layoutBox, { }); |
223 | else |
224 | ASSERT_NOT_REACHED(); |
225 | |
226 | auto& displayBox = layoutState.displayBoxForLayoutBox(layoutBox); |
227 | displayBox.setContentBoxHeight(heightAndMargin.height); |
228 | displayBox.setVerticalMargin({ heightAndMargin.nonCollapsedMargin, { } }); |
229 | } |
230 | |
231 | void InlineFormattingContext::layoutFormattingContextRoot(const Box& root, UsedHorizontalValues usedValues) const |
232 | { |
233 | ASSERT(root.isFloatingPositioned() || root.isInlineBlockBox()); |
234 | ASSERT(usedValues.containingBlockWidth); |
235 | |
236 | computeBorderAndPadding(root, usedValues); |
237 | computeWidthAndMargin(root, usedValues); |
238 | // Swich over to the new formatting context (the one that the root creates). |
239 | auto formattingContext = layoutState().createFormattingContext(root); |
240 | formattingContext->layout(); |
241 | // Come back and finalize the root's height and margin. |
242 | computeHeightAndMargin(root); |
243 | // Now that we computed the root's height, we can go back and layout the out-of-flow descedants (if any). |
244 | formattingContext->layoutOutOfFlowDescendants(root); |
245 | } |
246 | |
247 | void InlineFormattingContext::computeWidthAndHeightForReplacedInlineBox(const Box& layoutBox, UsedHorizontalValues usedValues) const |
248 | { |
249 | ASSERT(!layoutBox.isContainer()); |
250 | ASSERT(!layoutBox.establishesFormattingContext()); |
251 | ASSERT(layoutBox.replaced()); |
252 | ASSERT(usedValues.containingBlockWidth); |
253 | |
254 | computeBorderAndPadding(layoutBox, usedValues); |
255 | computeWidthAndMargin(layoutBox, usedValues); |
256 | computeHeightAndMargin(layoutBox); |
257 | } |
258 | |
259 | static void addDetachingRules(InlineItem& inlineItem, Optional<LayoutUnit> nonBreakableStartWidth, Optional<LayoutUnit> nonBreakableEndWidth) |
260 | { |
261 | OptionSet<InlineItem::DetachingRule> detachingRules; |
262 | if (nonBreakableStartWidth) { |
263 | detachingRules.add(InlineItem::DetachingRule::BreakAtStart); |
264 | inlineItem.addNonBreakableStart(*nonBreakableStartWidth); |
265 | } |
266 | if (nonBreakableEndWidth) { |
267 | detachingRules.add(InlineItem::DetachingRule::BreakAtEnd); |
268 | inlineItem.addNonBreakableEnd(*nonBreakableEndWidth); |
269 | } |
270 | inlineItem.addDetachingRule(detachingRules); |
271 | } |
272 | |
273 | static InlineItem& createAndAppendInlineItem(InlineRunProvider& inlineRunProvider, InlineContent& inlineContent, const Box& layoutBox) |
274 | { |
275 | ASSERT(layoutBox.isInlineLevelBox() || layoutBox.isFloatingPositioned()); |
276 | auto inlineItem = std::make_unique<InlineItem>(layoutBox); |
277 | auto* inlineItemPtr = inlineItem.get(); |
278 | inlineContent.add(WTFMove(inlineItem)); |
279 | inlineRunProvider.append(*inlineItemPtr); |
280 | return *inlineItemPtr; |
281 | } |
282 | |
283 | void InlineFormattingContext::collectInlineContent(InlineRunProvider& inlineRunProvider) const |
284 | { |
285 | if (!is<Container>(root())) |
286 | return; |
287 | auto& root = downcast<Container>(this->root()); |
288 | if (!root.hasInFlowOrFloatingChild()) |
289 | return; |
290 | // The logic here is very similar to BFC layout. |
291 | // 1. Travers down the layout tree and collect "start" unbreakable widths (margin-left, border-left, padding-left) |
292 | // 2. Create InlineItem per leaf inline box (text nodes, inline-blocks, floats) and set "start" unbreakable width on them. |
293 | // 3. Climb back and collect "end" unbreakable width and set it on the last InlineItem. |
294 | auto& layoutState = this->layoutState(); |
295 | auto& inlineContent = formattingState().inlineContent(); |
296 | |
297 | enum class NonBreakableWidthType { Start, End }; |
298 | auto nonBreakableWidth = [&](auto& container, auto type) { |
299 | auto& displayBox = layoutState.displayBoxForLayoutBox(container); |
300 | if (type == NonBreakableWidthType::Start) |
301 | return displayBox.marginStart() + displayBox.borderLeft() + displayBox.paddingLeft().valueOr(0); |
302 | return displayBox.marginEnd() + displayBox.borderRight() + displayBox.paddingRight().valueOr(0); |
303 | }; |
304 | |
305 | LayoutQueue layoutQueue; |
306 | layoutQueue.append(root.firstInFlowOrFloatingChild()); |
307 | |
308 | Optional<LayoutUnit> nonBreakableStartWidth; |
309 | Optional<LayoutUnit> nonBreakableEndWidth; |
310 | InlineItem* lastInlineItem = nullptr; |
311 | while (!layoutQueue.isEmpty()) { |
312 | while (true) { |
313 | auto& layoutBox = *layoutQueue.last(); |
314 | if (!is<Container>(layoutBox)) |
315 | break; |
316 | auto& container = downcast<Container>(layoutBox); |
317 | |
318 | if (container.establishesFormattingContext()) { |
319 | // Formatting contexts are treated as leaf nodes. |
320 | auto& inlineItem = createAndAppendInlineItem(inlineRunProvider, inlineContent, container); |
321 | auto& displayBox = layoutState.displayBoxForLayoutBox(container); |
322 | auto currentNonBreakableStartWidth = nonBreakableStartWidth.valueOr(0) + displayBox.marginStart() + nonBreakableEndWidth.valueOr(0); |
323 | addDetachingRules(inlineItem, currentNonBreakableStartWidth, displayBox.marginEnd()); |
324 | nonBreakableStartWidth = { }; |
325 | nonBreakableEndWidth = { }; |
326 | |
327 | // Formatting context roots take care of their subtrees. Continue with next sibling if exists. |
328 | layoutQueue.removeLast(); |
329 | if (!container.nextInFlowOrFloatingSibling()) |
330 | break; |
331 | layoutQueue.append(container.nextInFlowOrFloatingSibling()); |
332 | continue; |
333 | } |
334 | |
335 | // Check if this non-formatting context container has any non-breakable start properties (margin-left, border-left, padding-left) |
336 | // <span style="padding-left: 5px"><span style="padding-left: 5px">foobar</span></span> -> 5px + 5px |
337 | auto currentNonBreakableStartWidth = nonBreakableWidth(layoutBox, NonBreakableWidthType::Start); |
338 | if (currentNonBreakableStartWidth || layoutBox.isPositioned()) |
339 | nonBreakableStartWidth = nonBreakableStartWidth.valueOr(0) + currentNonBreakableStartWidth; |
340 | |
341 | if (!container.hasInFlowOrFloatingChild()) |
342 | break; |
343 | layoutQueue.append(container.firstInFlowOrFloatingChild()); |
344 | } |
345 | |
346 | while (!layoutQueue.isEmpty()) { |
347 | auto& layoutBox = *layoutQueue.takeLast(); |
348 | if (is<Container>(layoutBox)) { |
349 | // This is the end of an inline container. Compute the non-breakable end width and add it to the last inline box. |
350 | // <span style="padding-right: 5px">foobar</span> -> 5px; last inline item -> "foobar" |
351 | auto currentNonBreakableEndWidth = nonBreakableWidth(layoutBox, NonBreakableWidthType::End); |
352 | if (currentNonBreakableEndWidth || layoutBox.isPositioned()) |
353 | nonBreakableEndWidth = nonBreakableEndWidth.valueOr(0) + currentNonBreakableEndWidth; |
354 | // Add it to the last inline box |
355 | if (lastInlineItem) { |
356 | addDetachingRules(*lastInlineItem, { }, nonBreakableEndWidth); |
357 | nonBreakableEndWidth = { }; |
358 | } |
359 | } else { |
360 | // Leaf inline box |
361 | auto& inlineItem = createAndAppendInlineItem(inlineRunProvider, inlineContent, layoutBox); |
362 | // Add start and the (through empty containers) accumulated end width. |
363 | // <span style="padding-left: 1px">foobar</span> -> nonBreakableStartWidth: 1px; |
364 | // <span style="padding: 5px"></span>foobar -> nonBreakableStartWidth: 5px; nonBreakableEndWidth: 5px |
365 | if (nonBreakableStartWidth || nonBreakableEndWidth) { |
366 | addDetachingRules(inlineItem, nonBreakableStartWidth.valueOr(0) + nonBreakableEndWidth.valueOr(0), { }); |
367 | nonBreakableStartWidth = { }; |
368 | nonBreakableEndWidth = { }; |
369 | } |
370 | lastInlineItem = &inlineItem; |
371 | } |
372 | |
373 | if (auto* nextSibling = layoutBox.nextInFlowOrFloatingSibling()) { |
374 | layoutQueue.append(nextSibling); |
375 | break; |
376 | } |
377 | } |
378 | } |
379 | } |
380 | |
381 | } |
382 | } |
383 | |
384 | #endif |
385 | |