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
44namespace WebCore {
45namespace Layout {
46
47WTF_MAKE_ISO_ALLOCATED_IMPL(InlineFormattingContext);
48
49InlineFormattingContext::InlineFormattingContext(const Box& formattingContextRoot, InlineFormattingState& formattingState)
50 : FormattingContext(formattingContextRoot, formattingState)
51{
52}
53
54static 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
67void 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
95void 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
157void 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
175void 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
185void 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
193void 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
212void 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
231void 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
247void 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
259static 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
273static 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
283void 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