[LFC] FormattingContext::intrinsicWidthConstraints should compute and save the intrin...
[WebKit-https.git] / Source / WebCore / layout / inlineformatting / InlineFormattingContext.cpp
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->isFloatingPositioned())
108             ASSERT_NOT_IMPLEMENTED_YET();
109         else if (layoutBox->isInlineBlockBox()) {
110             computeIntrinsicWidthForFormattingContextRoot(*layoutBox);
111             formattingContextRootList.append(layoutBox);
112         } else if (layoutBox->isReplaced() || is<Container>(*layoutBox)) {
113             computeBorderAndPadding(*layoutBox, usedValues);
114             // inline-block and replaced.
115             auto needsWidthComputation = layoutBox->isReplaced() || layoutBox->establishesFormattingContext();
116             if (needsWidthComputation)
117                 computeWidthAndMargin(*layoutBox, usedValues);
118             else {
119                 // Simple inline container with no intrinsic width <span>.
120                 computeMargin(downcast<InlineContainer>(*layoutBox), usedValues);
121             }
122         }
123         layoutBox = nextInPreOrder(*layoutBox, root);
124     }
125
126     InlineRunProvider inlineRunProvider;
127     collectInlineContent(inlineRunProvider);
128
129     auto maximumLineWidth = [&](auto availableWidth) {
130         LayoutUnit maxContentLogicalRight;
131         auto lineBreaker = InlineLineBreaker { layoutState, formattingState().inlineContent(), inlineRunProvider.runs() };
132         LayoutUnit lineLogicalRight;
133
134         // Switch to the min/max formatting root width values before formatting the lines.
135         for (auto* formattingRoot : formattingContextRootList) {
136             auto intrinsicWidths = layoutState.formattingStateForBox(*formattingRoot).intrinsicWidthConstraints(*formattingRoot);
137             layoutState.displayBoxForLayoutBox(*formattingRoot).setContentBoxWidth(availableWidth ? intrinsicWidths->maximum : intrinsicWidths->minimum);
138         }
139
140         while (auto run = lineBreaker.nextRun(lineLogicalRight, availableWidth, !lineLogicalRight)) {
141             if (run->position == InlineLineBreaker::Run::Position::LineBegin)
142                 lineLogicalRight = 0;
143             lineLogicalRight += run->width;
144
145             maxContentLogicalRight = std::max(maxContentLogicalRight, lineLogicalRight);
146         }
147         return maxContentLogicalRight;
148     };
149
150     auto intrinsicWidthConstraints = FormattingContext::IntrinsicWidthConstraints { maximumLineWidth(0), maximumLineWidth(LayoutUnit::max()) };
151     layoutState.formattingStateForBox(root).setIntrinsicWidthConstraints(root, intrinsicWidthConstraints);
152 }
153
154 void InlineFormattingContext::computeIntrinsicWidthForFormattingContextRoot(const Box& layoutBox) const
155 {
156     ASSERT(layoutBox.establishesFormattingContext());
157
158     auto usedValues = UsedHorizontalValues { };
159     computeBorderAndPadding(layoutBox, usedValues);
160     computeMargin(downcast<InlineContainer>(layoutBox), usedValues);
161     layoutState().createFormattingContext(layoutBox)->computeIntrinsicWidthConstraints();
162 }
163
164 void InlineFormattingContext::computeMargin(const InlineContainer& inlineContainer, UsedHorizontalValues usedValues) const
165 {
166     // Non-replaced and formatting root containers (<span></span>) don't have width property -> non width computation.
167     ASSERT(!inlineContainer.replaced());
168
169     auto& displayBox = layoutState().displayBoxForLayoutBox(inlineContainer);
170     auto computedHorizontalMargin = Geometry::computedHorizontalMargin(inlineContainer, usedValues);
171     displayBox.setHorizontalComputedMargin(computedHorizontalMargin);
172     displayBox.setHorizontalMargin({ computedHorizontalMargin.start.valueOr(0), computedHorizontalMargin.end.valueOr(0) });
173 }
174
175 void InlineFormattingContext::computeBorderAndPadding(const Box& layoutBox, UsedHorizontalValues usedValues) const
176 {
177     auto& displayBox = layoutState().displayBoxForLayoutBox(layoutBox);
178     displayBox.setBorder(Geometry::computedBorder(layoutBox));
179     displayBox.setPadding(Geometry::computedPadding(layoutBox, usedValues));
180 }
181
182 void InlineFormattingContext::computeWidthAndMargin(const Box& layoutBox, UsedHorizontalValues usedValues) const
183 {
184     auto& layoutState = this->layoutState();
185     WidthAndMargin widthAndMargin;
186     if (layoutBox.isFloatingPositioned())
187         widthAndMargin = Geometry::floatingWidthAndMargin(layoutState, layoutBox, usedValues);
188     else if (layoutBox.isInlineBlockBox())
189         widthAndMargin = Geometry::inlineBlockWidthAndMargin(layoutState, layoutBox, usedValues);
190     else if (layoutBox.replaced())
191         widthAndMargin = Geometry::inlineReplacedWidthAndMargin(layoutState, layoutBox, usedValues);
192     else
193         ASSERT_NOT_REACHED();
194
195     auto& displayBox = layoutState.displayBoxForLayoutBox(layoutBox);
196     displayBox.setContentBoxWidth(widthAndMargin.width);
197     displayBox.setHorizontalMargin(widthAndMargin.usedMargin);
198     displayBox.setHorizontalComputedMargin(widthAndMargin.computedMargin);
199 }
200
201 void InlineFormattingContext::computeHeightAndMargin(const Box& layoutBox) const
202 {
203     auto& layoutState = this->layoutState();
204
205     HeightAndMargin heightAndMargin;
206     if (layoutBox.isFloatingPositioned())
207         heightAndMargin = Geometry::floatingHeightAndMargin(layoutState, layoutBox, { });
208     else if (layoutBox.isInlineBlockBox())
209         heightAndMargin = Geometry::inlineBlockHeightAndMargin(layoutState, layoutBox);
210     else if (layoutBox.replaced())
211         heightAndMargin = Geometry::inlineReplacedHeightAndMargin(layoutState, layoutBox, { });
212     else
213         ASSERT_NOT_REACHED();
214
215     auto& displayBox = layoutState.displayBoxForLayoutBox(layoutBox);
216     displayBox.setContentBoxHeight(heightAndMargin.height);
217     displayBox.setVerticalMargin({ heightAndMargin.nonCollapsedMargin, { } });
218 }
219
220 void InlineFormattingContext::layoutFormattingContextRoot(const Box& root, UsedHorizontalValues usedValues) const
221 {
222     ASSERT(root.isFloatingPositioned() || root.isInlineBlockBox());
223     ASSERT(usedValues.containingBlockWidth);
224
225     computeBorderAndPadding(root, usedValues);
226     computeWidthAndMargin(root, usedValues);
227     // Swich over to the new formatting context (the one that the root creates).
228     auto formattingContext = layoutState().createFormattingContext(root);
229     formattingContext->layout();
230     // Come back and finalize the root's height and margin.
231     computeHeightAndMargin(root);
232     // Now that we computed the root's height, we can go back and layout the out-of-flow descedants (if any).
233     formattingContext->layoutOutOfFlowDescendants(root);
234 }
235
236 void InlineFormattingContext::computeWidthAndHeightForReplacedInlineBox(const Box& layoutBox, UsedHorizontalValues usedValues) const
237 {
238     ASSERT(!layoutBox.isContainer());
239     ASSERT(!layoutBox.establishesFormattingContext());
240     ASSERT(layoutBox.replaced());
241     ASSERT(usedValues.containingBlockWidth);
242
243     computeBorderAndPadding(layoutBox, usedValues);
244     computeWidthAndMargin(layoutBox, usedValues);
245     computeHeightAndMargin(layoutBox);
246 }
247
248 static void addDetachingRules(InlineItem& inlineItem, Optional<LayoutUnit> nonBreakableStartWidth, Optional<LayoutUnit> nonBreakableEndWidth)
249 {
250     OptionSet<InlineItem::DetachingRule> detachingRules;
251     if (nonBreakableStartWidth) {
252         detachingRules.add(InlineItem::DetachingRule::BreakAtStart);
253         inlineItem.addNonBreakableStart(*nonBreakableStartWidth);
254     }
255     if (nonBreakableEndWidth) {
256         detachingRules.add(InlineItem::DetachingRule::BreakAtEnd);
257         inlineItem.addNonBreakableEnd(*nonBreakableEndWidth);
258     }
259     inlineItem.addDetachingRule(detachingRules);
260 }
261
262 static InlineItem& createAndAppendInlineItem(InlineRunProvider& inlineRunProvider, InlineContent& inlineContent, const Box& layoutBox)
263 {
264     ASSERT(layoutBox.isInlineLevelBox() || layoutBox.isFloatingPositioned());
265     auto inlineItem = std::make_unique<InlineItem>(layoutBox);
266     auto* inlineItemPtr = inlineItem.get();
267     inlineContent.add(WTFMove(inlineItem));
268     inlineRunProvider.append(*inlineItemPtr);
269     return *inlineItemPtr;
270 }
271
272 void InlineFormattingContext::collectInlineContent(InlineRunProvider& inlineRunProvider) const
273 {
274     if (!is<Container>(root()))
275         return;
276     auto& root = downcast<Container>(this->root());
277     if (!root.hasInFlowOrFloatingChild())
278         return;
279     // The logic here is very similar to BFC layout.
280     // 1. Travers down the layout tree and collect "start" unbreakable widths (margin-left, border-left, padding-left)
281     // 2. Create InlineItem per leaf inline box (text nodes, inline-blocks, floats) and set "start" unbreakable width on them. 
282     // 3. Climb back and collect "end" unbreakable width and set it on the last InlineItem.
283     auto& layoutState = this->layoutState();
284     auto& inlineContent = formattingState().inlineContent();
285
286     enum class NonBreakableWidthType { Start, End };
287     auto nonBreakableWidth = [&](auto& container, auto type) {
288         auto& displayBox = layoutState.displayBoxForLayoutBox(container);
289         if (type == NonBreakableWidthType::Start)
290             return displayBox.marginStart() + displayBox.borderLeft() + displayBox.paddingLeft().valueOr(0);
291         return displayBox.marginEnd() + displayBox.borderRight() + displayBox.paddingRight().valueOr(0);
292     };
293
294     LayoutQueue layoutQueue;
295     layoutQueue.append(root.firstInFlowOrFloatingChild());
296
297     Optional<LayoutUnit> nonBreakableStartWidth;
298     Optional<LayoutUnit> nonBreakableEndWidth;
299     InlineItem* lastInlineItem = nullptr;
300     while (!layoutQueue.isEmpty()) {
301         while (true) {
302             auto& layoutBox = *layoutQueue.last();
303             if (!is<Container>(layoutBox))
304                 break;
305             auto& container = downcast<Container>(layoutBox);
306
307             if (container.establishesFormattingContext()) {
308                 // Formatting contexts are treated as leaf nodes.
309                 auto& inlineItem = createAndAppendInlineItem(inlineRunProvider, inlineContent, container);
310                 auto& displayBox = layoutState.displayBoxForLayoutBox(container);
311                 auto currentNonBreakableStartWidth = nonBreakableStartWidth.valueOr(0) + displayBox.marginStart() + nonBreakableEndWidth.valueOr(0);
312                 addDetachingRules(inlineItem, currentNonBreakableStartWidth, displayBox.marginEnd());
313                 nonBreakableStartWidth = { };
314                 nonBreakableEndWidth = { };
315
316                 // Formatting context roots take care of their subtrees. Continue with next sibling if exists.
317                 layoutQueue.removeLast();
318                 if (!container.nextInFlowOrFloatingSibling())
319                     break;
320                 layoutQueue.append(container.nextInFlowOrFloatingSibling());
321                 continue;
322             }
323
324             // Check if this non-formatting context container has any non-breakable start properties (margin-left, border-left, padding-left)
325             // <span style="padding-left: 5px"><span style="padding-left: 5px">foobar</span></span> -> 5px + 5px
326             auto currentNonBreakableStartWidth = nonBreakableWidth(layoutBox, NonBreakableWidthType::Start);
327             if (currentNonBreakableStartWidth || layoutBox.isPositioned())
328                 nonBreakableStartWidth = nonBreakableStartWidth.valueOr(0) + currentNonBreakableStartWidth;
329
330             if (!container.hasInFlowOrFloatingChild())
331                 break;
332             layoutQueue.append(container.firstInFlowOrFloatingChild());
333         }
334
335         while (!layoutQueue.isEmpty()) {
336             auto& layoutBox = *layoutQueue.takeLast();
337             if (is<Container>(layoutBox)) {
338                 // This is the end of an inline container. Compute the non-breakable end width and add it to the last inline box.
339                 // <span style="padding-right: 5px">foobar</span> -> 5px; last inline item -> "foobar"
340                 auto currentNonBreakableEndWidth = nonBreakableWidth(layoutBox, NonBreakableWidthType::End);
341                 if (currentNonBreakableEndWidth || layoutBox.isPositioned())
342                     nonBreakableEndWidth = nonBreakableEndWidth.valueOr(0) + currentNonBreakableEndWidth;
343                 // Add it to the last inline box
344                 if (lastInlineItem) {
345                     addDetachingRules(*lastInlineItem, { }, nonBreakableEndWidth);
346                     nonBreakableEndWidth = { };
347                 }
348             } else {
349                 // Leaf inline box
350                 auto& inlineItem = createAndAppendInlineItem(inlineRunProvider, inlineContent, layoutBox);
351                 // Add start and the (through empty containers) accumulated end width.
352                 // <span style="padding-left: 1px">foobar</span> -> nonBreakableStartWidth: 1px;
353                 // <span style="padding: 5px"></span>foobar -> nonBreakableStartWidth: 5px; nonBreakableEndWidth: 5px
354                 if (nonBreakableStartWidth || nonBreakableEndWidth) {
355                     addDetachingRules(inlineItem, nonBreakableStartWidth.valueOr(0) + nonBreakableEndWidth.valueOr(0), { });
356                     nonBreakableStartWidth = { };
357                     nonBreakableEndWidth = { };
358                 }
359                 lastInlineItem = &inlineItem;
360             }
361
362             if (auto* nextSibling = layoutBox.nextInFlowOrFloatingSibling()) {
363                 layoutQueue.append(nextSibling);
364                 break;
365             }
366         }
367     }
368 }
369
370 }
371 }
372
373 #endif