[LFC] The *FormattingState class should provide the *FormattingContext.
[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 "FloatingState.h"
32 #include "InlineFormattingState.h"
33 #include "InlineLineBreaker.h"
34 #include "InlineRunProvider.h"
35 #include "LayoutBox.h"
36 #include "LayoutContainer.h"
37 #include "LayoutFormattingState.h"
38 #include "LayoutInlineBox.h"
39 #include "LayoutInlineContainer.h"
40 #include "Logging.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)
50     : FormattingContext(formattingContextRoot)
51 {
52 }
53
54 void InlineFormattingContext::layout(LayoutState& layoutState, FormattingState& formattingState) const
55 {
56     if (!is<Container>(root()))
57         return;
58
59     LOG_WITH_STREAM(FormattingContextLayout, stream << "[Start] -> inline formatting context -> layout context(" << &layoutState << ") formatting root(" << &root() << ")");
60
61     auto& inlineFormattingState = downcast<InlineFormattingState>(formattingState);
62     InlineRunProvider inlineRunProvider(inlineFormattingState);
63     auto& formattingRoot = downcast<Container>(root());
64     auto* layoutBox = formattingRoot.firstInFlowOrFloatingChild();
65     // Casually walk through the block's descendants and place the inline boxes one after the other as much as we can (yeah, I am looking at you floats).
66     while (layoutBox) {
67
68         if (layoutBox->establishesFormattingContext()) {
69             layoutFormattingContextRoot(layoutState, *layoutBox);
70             // Formatting context roots take care of their entire subtree. Continue with next sibling.
71             inlineRunProvider.append(*layoutBox);
72             layoutBox = layoutBox->nextInFlowOrFloatingSibling();
73             continue;
74         }
75
76         if (is<Container>(layoutBox)) {
77             ASSERT(is<InlineContainer>(layoutBox));
78             layoutBox = downcast<Container>(*layoutBox).firstInFlowOrFloatingChild();
79             continue;
80         }
81
82         inlineRunProvider.append(*layoutBox);
83         computeWidthAndHeightForInlineBox(layoutState, *layoutBox);
84
85         for (; layoutBox; layoutBox = layoutBox->parent()) {
86             if (layoutBox == &formattingRoot) {
87                 layoutBox = nullptr;
88                 break;
89             }
90             if (auto* nextSibling = layoutBox->nextInFlowOrFloatingSibling()) {
91                 layoutBox = nextSibling;
92                 break;
93             }
94         }
95         ASSERT(!layoutBox || layoutBox->isDescendantOf(formattingRoot));
96     }
97
98     layoutInlineContent(layoutState, inlineFormattingState, inlineRunProvider);
99
100     LOG_WITH_STREAM(FormattingContextLayout, stream << "[End] -> inline formatting context -> layout context(" << &layoutState << ") formatting root(" << &root() << ")");
101 }
102
103 static bool isTrimmableContent(const InlineLineBreaker::Run& run)
104 {
105     return run.content.isWhitespace() && run.content.style().collapseWhiteSpace();
106 }
107
108 void InlineFormattingContext::initializeNewLine(const LayoutState& layoutState, InlineFormattingState& inlineFormattingState, Line& line) const
109 {
110     auto& formattingRoot = downcast<Container>(root());
111     auto& formattingRootDisplayBox = layoutState.displayBoxForLayoutBox(formattingRoot);
112
113     auto lineLogicalLeft = formattingRootDisplayBox.contentBoxLeft();
114     auto lineLogicalTop = line.isFirstLine() ? formattingRootDisplayBox.contentBoxTop() : line.logicalBottom();
115     auto availableWidth = formattingRootDisplayBox.contentBoxWidth();
116
117     // Check for intruding floats and adjust logical left/available width for this line accordingly.
118     auto& floatingState = inlineFormattingState.floatingState();
119     if (!floatingState.isEmpty()) {
120         auto floatConstraints = floatingState.constraints(lineLogicalTop, formattingRoot);
121         // Check if these constraints actually put limitation on the line.
122         if (floatConstraints.left && *floatConstraints.left <= formattingRootDisplayBox.contentBoxLeft())
123             floatConstraints.left = { };
124
125         if (floatConstraints.right && *floatConstraints.right >= formattingRootDisplayBox.contentBoxRight())
126             floatConstraints.right = { };
127
128         if (floatConstraints.left && floatConstraints.right) {
129             ASSERT(*floatConstraints.left < *floatConstraints.right);
130             availableWidth = *floatConstraints.right - *floatConstraints.left;
131             lineLogicalLeft = *floatConstraints.left;
132         } else if (floatConstraints.left) {
133             ASSERT(*floatConstraints.left > lineLogicalLeft);
134             availableWidth -= (*floatConstraints.left - lineLogicalLeft);
135             lineLogicalLeft = *floatConstraints.left;
136         } else if (floatConstraints.right) {
137             ASSERT(*floatConstraints.right > lineLogicalLeft);
138             availableWidth = *floatConstraints.right - lineLogicalLeft;
139         }
140     }
141
142     Display::Box::Rect logicalRect;
143     logicalRect.setTop(lineLogicalTop);
144     logicalRect.setLeft(lineLogicalLeft);
145     logicalRect.setWidth(availableWidth);
146     logicalRect.setHeight(formattingRoot.style().computedLineHeight());
147
148     line.init(logicalRect);
149 }
150
151 void InlineFormattingContext::layoutInlineContent(const LayoutState& layoutState, InlineFormattingState& inlineFormattingState, const InlineRunProvider& inlineRunProvider) const
152 {
153     auto floatingContext = FloatingContext { inlineFormattingState.floatingState() };
154
155     Line line(inlineFormattingState, root());
156     initializeNewLine(layoutState, inlineFormattingState, line);
157
158     InlineLineBreaker lineBreaker(layoutState, inlineFormattingState.inlineContent(), inlineRunProvider.runs());
159     while (auto run = lineBreaker.nextRun(line.contentLogicalRight(), line.availableWidth(), !line.hasContent())) {
160         auto isFirstRun = run->position == InlineLineBreaker::Run::Position::LineBegin;
161         auto isLastRun = run->position == InlineLineBreaker::Run::Position::LineEnd;
162         auto generatesInlineRun = true;
163
164         // Position float and adjust the runs on line.
165         if (run->content.isFloat()) {
166             auto& floatBox = run->content.inlineItem().layoutBox();
167             computeFloatPosition(layoutState, floatingContext, line, floatBox);
168             inlineFormattingState.floatingState().append(floatBox);
169
170             auto floatBoxWidth = layoutState.displayBoxForLayoutBox(floatBox).width();
171             // Shrink availble space for current line and move existing inline runs.
172             floatBox.isLeftFloatingPositioned() ? line.adjustLogicalLeft(floatBoxWidth) : line.adjustLogicalRight(floatBoxWidth);
173
174             generatesInlineRun = false;
175         }
176
177         // 1. Initialize new line if needed.
178         // 2. Append inline run unless it is skipped.
179         // 3. Close current line if needed.
180         if (isFirstRun) {
181             // When the first run does not generate an actual inline run, the next run comes in first-run as well.
182             // No need to spend time on closing/initializing.
183             // Skip leading whitespace.
184             if (!generatesInlineRun || isTrimmableContent(*run))
185                 continue;
186
187             if (line.hasContent()) {
188                 // Previous run ended up being at the line end. Adjust the line accordingly.
189                 if (!line.isClosed())
190                     line.close(Line::LastLine::No);
191                 initializeNewLine(layoutState, inlineFormattingState, line);
192             }
193          }
194
195         if (generatesInlineRun)
196              line.appendContent(*run);
197
198         if (isLastRun)
199             line.close(Line::LastLine::No);
200     }
201
202     line.close(Line::LastLine::Yes);
203 }
204
205 void InlineFormattingContext::layoutFormattingContextRoot(LayoutState& layoutState, const Box& layoutBox) const
206 {
207     ASSERT(layoutBox.isFloatingPositioned() || layoutBox.isInlineBlockBox());
208     auto& displayBox = layoutState.displayBoxForLayoutBox(layoutBox);
209
210     auto computeWidthAndMargin = [&]() {
211         WidthAndMargin widthAndMargin;
212
213         if (layoutBox.isFloatingPositioned())
214             widthAndMargin = Geometry::floatingWidthAndMargin(layoutState, *this, layoutBox);
215         else if (layoutBox.isInlineBlockBox())
216             widthAndMargin = Geometry::inlineBlockWidthAndMargin(layoutState, layoutBox);
217         else
218             ASSERT_NOT_REACHED();
219
220         displayBox.setContentBoxWidth(widthAndMargin.width);
221         displayBox.setHorizontalMargin(widthAndMargin.margin);
222         displayBox.setHorizontalNonComputedMargin(widthAndMargin.nonComputedMargin);
223     };
224
225     auto computeHeightAndMargin = [&]() {
226         HeightAndMargin heightAndMargin;
227
228         if (layoutBox.isFloatingPositioned())
229             heightAndMargin = Geometry::floatingHeightAndMargin(layoutState, layoutBox);
230         else if (layoutBox.isInlineBlockBox())
231             heightAndMargin = Geometry::inlineBlockHeightAndMargin(layoutState, layoutBox);
232         else
233             ASSERT_NOT_REACHED();
234
235         displayBox.setContentBoxHeight(heightAndMargin.height);
236         displayBox.setVerticalNonCollapsedMargin(heightAndMargin.margin);
237         displayBox.setVerticalMargin(heightAndMargin.collapsedMargin.value_or(heightAndMargin.margin));
238     };
239
240     computeBorderAndPadding(layoutState, layoutBox);
241     computeWidthAndMargin();
242
243     // Swich over to the new formatting context (the one that the root creates).
244     auto& formattingState = layoutState.createFormattingStateForFormattingRootIfNeeded(layoutBox);
245     formattingState.formattingContext(layoutBox)->layout(layoutState, formattingState);
246
247     // Come back and finalize the root's height and margin.
248     computeHeightAndMargin();
249 }
250
251 void InlineFormattingContext::computeWidthAndHeightForInlineBox(LayoutState& layoutState, const Box& layoutBox) const
252 {
253     ASSERT(!layoutBox.isContainer());
254     ASSERT(!layoutBox.establishesFormattingContext());
255
256     if (is<InlineBox>(layoutBox) && downcast<InlineBox>(layoutBox).hasTextContent()) {
257         // Text content width is computed during text run generation. -It does not make any sense to measure unprocessed text here, since it will likely be
258         // split up (or concatenated).
259         return;
260     }
261
262     // This is pretty much only for replaced inline boxes atm.
263     ASSERT(layoutBox.replaced());
264     computeBorderAndPadding(layoutState, layoutBox);
265
266     auto widthAndMargin = Geometry::inlineReplacedWidthAndMargin(layoutState, layoutBox);
267     auto heightAndMargin = Geometry::inlineReplacedHeightAndMargin(layoutState, layoutBox);
268
269     auto& displayBox = layoutState.displayBoxForLayoutBox(layoutBox);
270     displayBox.setContentBoxWidth(widthAndMargin.width);
271     displayBox.setHorizontalMargin(widthAndMargin.margin);
272     displayBox.setHorizontalNonComputedMargin(widthAndMargin.nonComputedMargin);
273
274     displayBox.setContentBoxHeight(heightAndMargin.height);
275     displayBox.setVerticalNonCollapsedMargin(heightAndMargin.margin);
276     displayBox.setVerticalMargin(heightAndMargin.collapsedMargin.value_or(heightAndMargin.margin));
277 }
278
279 void InlineFormattingContext::computeFloatPosition(const LayoutState& layoutState, const FloatingContext& floatingContext, Line& line, const Box& floatBox) const
280 {
281     ASSERT(layoutState.hasDisplayBox(floatBox));
282     auto& displayBox = layoutState.displayBoxForLayoutBox(floatBox);
283
284     // Set static position first.
285     displayBox.setTopLeft({ line.contentLogicalRight(), line.logicalTop() });
286     // Float it.
287     displayBox.setTopLeft(floatingContext.positionForFloat(floatBox));
288 }
289
290 void InlineFormattingContext::computeStaticPosition(const LayoutState&, const Box&) const
291 {
292 }
293
294 void InlineFormattingContext::computeInFlowPositionedPosition(const LayoutState&, const Box&) const
295 {
296 }
297
298 FormattingContext::InstrinsicWidthConstraints InlineFormattingContext::instrinsicWidthConstraints(LayoutState&, const Box&) const
299 {
300     return { };
301 }
302
303 }
304 }
305
306 #endif