[LFC][IFC] Move some code from InlineFormattingContext::Line to InlineFormattingConte...
[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, FormattingState& formattingState)
50     : FormattingContext(formattingContextRoot, formattingState)
51 {
52 }
53
54 void InlineFormattingContext::layout() const
55 {
56     if (!is<Container>(root()))
57         return;
58
59     LOG_WITH_STREAM(FormattingContextLayout, stream << "[Start] -> inline formatting context -> formatting root(" << &root() << ")");
60
61     auto& inlineFormattingState = downcast<InlineFormattingState>(formattingState());
62     InlineRunProvider inlineRunProvider(inlineFormattingState);
63
64     collectInlineContent(inlineRunProvider);
65     // Compute width/height for non-text content.
66     for (auto& inlineRun : inlineRunProvider.runs()) {
67         if (inlineRun.isText())
68             continue;
69
70         auto& layoutBox = inlineRun.inlineItem().layoutBox();
71         if (layoutBox.establishesFormattingContext()) {
72             layoutFormattingContextRoot(layoutBox);
73             continue;
74         }
75         computeWidthAndHeightForReplacedInlineBox(layoutBox);
76     }
77
78     layoutInlineContent(inlineRunProvider);
79     LOG_WITH_STREAM(FormattingContextLayout, stream << "[End] -> inline formatting context -> formatting root(" << &root() << ")");
80 }
81
82 static bool isTrimmableContent(const InlineLineBreaker::Run& run)
83 {
84     return run.content.isWhitespace() && run.content.style().collapseWhiteSpace();
85 }
86
87 void InlineFormattingContext::initializeNewLine(Line& line) const
88 {
89     auto& formattingRoot = downcast<Container>(root());
90     auto& formattingRootDisplayBox = layoutState().displayBoxForLayoutBox(formattingRoot);
91
92     auto lineLogicalLeft = formattingRootDisplayBox.contentBoxLeft();
93     auto lineLogicalTop = line.isFirstLine() ? formattingRootDisplayBox.contentBoxTop() : line.logicalBottom();
94     auto availableWidth = formattingRootDisplayBox.contentBoxWidth();
95
96     // Check for intruding floats and adjust logical left/available width for this line accordingly.
97     auto& floatingState = formattingState().floatingState();
98     if (!floatingState.isEmpty()) {
99         auto floatConstraints = floatingState.constraints(lineLogicalTop, formattingRoot);
100         // Check if these constraints actually put limitation on the line.
101         if (floatConstraints.left && *floatConstraints.left <= formattingRootDisplayBox.contentBoxLeft())
102             floatConstraints.left = { };
103
104         if (floatConstraints.right && *floatConstraints.right >= formattingRootDisplayBox.contentBoxRight())
105             floatConstraints.right = { };
106
107         if (floatConstraints.left && floatConstraints.right) {
108             ASSERT(*floatConstraints.left < *floatConstraints.right);
109             availableWidth = *floatConstraints.right - *floatConstraints.left;
110             lineLogicalLeft = *floatConstraints.left;
111         } else if (floatConstraints.left) {
112             ASSERT(*floatConstraints.left > lineLogicalLeft);
113             availableWidth -= (*floatConstraints.left - lineLogicalLeft);
114             lineLogicalLeft = *floatConstraints.left;
115         } else if (floatConstraints.right) {
116             ASSERT(*floatConstraints.right > lineLogicalLeft);
117             availableWidth = *floatConstraints.right - lineLogicalLeft;
118         }
119     }
120
121     Display::Box::Rect logicalRect;
122     logicalRect.setTop(lineLogicalTop);
123     logicalRect.setLeft(lineLogicalLeft);
124     logicalRect.setWidth(availableWidth);
125     logicalRect.setHeight(formattingRoot.style().computedLineHeight());
126
127     line.init(logicalRect);
128 }
129
130 void InlineFormattingContext::closeLine(Line& line, IsLastLine isLastLine) const
131 {
132     auto runRange = line.close();
133     ASSERT(!runRange.firstRunIndex || runRange.lastRunIndex);
134
135     if (!runRange.firstRunIndex)
136         return;
137
138     Geometry::alignRuns(downcast<InlineFormattingState>(formattingState()), root().style().textAlign(), line, runRange, isLastLine);
139 }
140
141 void InlineFormattingContext::appendContentToLine(Line& line, const InlineLineBreaker::Run& run) const
142 {
143     auto lastRunType = line.lastRunType();
144     line.appendContent(run);
145
146     if (root().style().textAlign() == TextAlignMode::Justify)
147         Geometry::computeExpansionOpportunities(downcast<InlineFormattingState>(formattingState()), run.content, lastRunType.value_or(InlineRunProvider::Run::Type::NonWhitespace));
148 }
149
150 void InlineFormattingContext::layoutInlineContent(const InlineRunProvider& inlineRunProvider) const
151 {
152     auto& layoutState = this->layoutState();
153     auto& inlineFormattingState = downcast<InlineFormattingState>(formattingState());
154     auto floatingContext = FloatingContext { inlineFormattingState.floatingState() };
155
156     Line line(inlineFormattingState);
157     initializeNewLine(line);
158
159     InlineLineBreaker lineBreaker(layoutState, inlineFormattingState.inlineContent(), inlineRunProvider.runs());
160     while (auto run = lineBreaker.nextRun(line.contentLogicalRight(), line.availableWidth(), !line.hasContent())) {
161         auto isFirstRun = run->position == InlineLineBreaker::Run::Position::LineBegin;
162         auto isLastRun = run->position == InlineLineBreaker::Run::Position::LineEnd;
163         auto generatesInlineRun = true;
164
165         // Position float and adjust the runs on line.
166         if (run->content.isFloat()) {
167             auto& floatBox = run->content.inlineItem().layoutBox();
168             computeFloatPosition(floatingContext, line, floatBox);
169             inlineFormattingState.floatingState().append(floatBox);
170
171             auto floatBoxWidth = layoutState.displayBoxForLayoutBox(floatBox).width();
172             // Shrink availble space for current line and move existing inline runs.
173             floatBox.isLeftFloatingPositioned() ? line.adjustLogicalLeft(floatBoxWidth) : line.adjustLogicalRight(floatBoxWidth);
174
175             generatesInlineRun = false;
176         }
177
178         // 1. Initialize new line if needed.
179         // 2. Append inline run unless it is skipped.
180         // 3. Close current line if needed.
181         if (isFirstRun) {
182             // When the first run does not generate an actual inline run, the next run comes in first-run as well.
183             // No need to spend time on closing/initializing.
184             // Skip leading whitespace.
185             if (!generatesInlineRun || isTrimmableContent(*run))
186                 continue;
187
188             if (line.hasContent()) {
189                 // Previous run ended up being at the line end. Adjust the line accordingly.
190                 if (!line.isClosed())
191                     closeLine(line, IsLastLine::No);
192                 initializeNewLine(line);
193             }
194          }
195
196         if (generatesInlineRun)
197             appendContentToLine(line, *run);
198
199         if (isLastRun)
200             closeLine(line, IsLastLine::No);
201     }
202
203     closeLine(line, IsLastLine::Yes);
204 }
205
206 void InlineFormattingContext::computeWidthAndMargin(const Box& layoutBox) const
207 {
208     auto& layoutState = this->layoutState();
209
210     WidthAndMargin widthAndMargin;
211     if (layoutBox.isFloatingPositioned())
212         widthAndMargin = Geometry::floatingWidthAndMargin(layoutState, layoutBox);
213     else if (layoutBox.isInlineBlockBox())
214         widthAndMargin = Geometry::inlineBlockWidthAndMargin(layoutState, layoutBox);
215     else if (layoutBox.replaced())
216         widthAndMargin = Geometry::inlineReplacedWidthAndMargin(layoutState, layoutBox);
217     else
218         ASSERT_NOT_REACHED();
219
220     auto& displayBox = layoutState.displayBoxForLayoutBox(layoutBox);
221     displayBox.setContentBoxWidth(widthAndMargin.width);
222     displayBox.setHorizontalMargin(widthAndMargin.margin);
223     displayBox.setHorizontalNonComputedMargin(widthAndMargin.nonComputedMargin);
224 }
225
226 void InlineFormattingContext::computeHeightAndMargin(const Box& layoutBox) const
227 {
228     auto& layoutState = this->layoutState();
229
230     HeightAndMargin heightAndMargin;
231     if (layoutBox.isFloatingPositioned())
232         heightAndMargin = Geometry::floatingHeightAndMargin(layoutState, layoutBox);
233     else if (layoutBox.isInlineBlockBox())
234         heightAndMargin = Geometry::inlineBlockHeightAndMargin(layoutState, layoutBox);
235     else if (layoutBox.replaced())
236         heightAndMargin = Geometry::inlineReplacedHeightAndMargin(layoutState, layoutBox);
237     else
238         ASSERT_NOT_REACHED();
239
240     auto& displayBox = layoutState.displayBoxForLayoutBox(layoutBox);
241     displayBox.setContentBoxHeight(heightAndMargin.height);
242     displayBox.setVerticalNonCollapsedMargin(heightAndMargin.margin);
243     displayBox.setVerticalMargin(heightAndMargin.collapsedMargin.value_or(heightAndMargin.margin));
244 }
245
246 void InlineFormattingContext::layoutFormattingContextRoot(const Box& layoutBox) const
247 {
248     ASSERT(layoutBox.isFloatingPositioned() || layoutBox.isInlineBlockBox());
249
250     auto& layoutState = this->layoutState();
251     auto& formattingState = layoutState.createFormattingStateForFormattingRootIfNeeded(layoutBox);
252     computeBorderAndPadding(layoutBox);
253     computeWidthAndMargin(layoutBox);
254     // Swich over to the new formatting context (the one that the root creates).
255     formattingState.formattingContext(layoutBox)->layout();
256     // Come back and finalize the root's height and margin.
257     computeHeightAndMargin(layoutBox);
258 }
259
260 void InlineFormattingContext::computeWidthAndHeightForReplacedInlineBox(const Box& layoutBox) const
261 {
262     ASSERT(!layoutBox.isContainer());
263     ASSERT(!layoutBox.establishesFormattingContext());
264     ASSERT(layoutBox.replaced());
265
266     computeBorderAndPadding(layoutBox);
267     computeWidthAndMargin(layoutBox);
268     computeHeightAndMargin(layoutBox);
269 }
270
271 void InlineFormattingContext::computeFloatPosition(const FloatingContext& floatingContext, Line& line, const Box& floatBox) const
272 {
273     auto& layoutState = this->layoutState();
274     ASSERT(layoutState.hasDisplayBox(floatBox));
275     auto& displayBox = layoutState.displayBoxForLayoutBox(floatBox);
276
277     // Set static position first.
278     displayBox.setTopLeft({ line.contentLogicalRight(), line.logicalTop() });
279     // Float it.
280     displayBox.setTopLeft(floatingContext.positionForFloat(floatBox));
281 }
282
283 void InlineFormattingContext::computeStaticPosition(const Box&) const
284 {
285 }
286
287 void InlineFormattingContext::computeInFlowPositionedPosition(const Box&) const
288 {
289 }
290
291 void InlineFormattingContext::collectInlineContent(InlineRunProvider& inlineRunProvider) const
292 {
293     if (!is<Container>(root()))
294         return;
295
296     auto& formattingRoot = downcast<Container>(root());
297     auto* layoutBox = formattingRoot.firstInFlowOrFloatingChild();
298
299     while (layoutBox) {
300         ASSERT(layoutBox->isDescendantOf(formattingRoot));
301
302         if (layoutBox->establishesFormattingContext()) {
303             inlineRunProvider.append(*layoutBox);
304             layoutBox = layoutBox->nextInFlowOrFloatingSibling();
305             continue;
306         }
307
308         if (is<Container>(layoutBox)) {
309             layoutBox = downcast<Container>(*layoutBox).firstInFlowOrFloatingChild();
310             continue;
311         }
312
313         inlineRunProvider.append(*layoutBox);
314
315         while (true) {
316             if (auto* nextSibling = layoutBox->nextInFlowOrFloatingSibling()) {
317                 layoutBox = nextSibling;
318                 break;
319             }
320
321             layoutBox = layoutBox->parent();
322
323             if (layoutBox == &formattingRoot)
324                 return;
325         }
326     }
327 }
328
329 FormattingContext::InstrinsicWidthConstraints InlineFormattingContext::instrinsicWidthConstraints() const
330 {
331     auto& formattingStateForRoot = layoutState().formattingStateForBox(root());
332     if (auto instrinsicWidthConstraints = formattingStateForRoot.instrinsicWidthConstraints(root()))
333         return *instrinsicWidthConstraints;
334
335     auto& inlineFormattingState = downcast<InlineFormattingState>(formattingState());
336     InlineRunProvider inlineRunProvider(inlineFormattingState);
337     collectInlineContent(inlineRunProvider);
338
339     // Compute width for non-text content.
340     for (auto& inlineRun : inlineRunProvider.runs()) {
341         if (inlineRun.isText())
342             continue;
343
344         computeWidthAndMargin(inlineRun.inlineItem().layoutBox());
345     }
346
347     auto maximumLineWidth = [&](auto availableWidth) {
348         LayoutUnit maxContentLogicalRight;
349         InlineLineBreaker lineBreaker(layoutState(), inlineFormattingState.inlineContent(), inlineRunProvider.runs());
350         LayoutUnit lineLogicalRight;
351         while (auto run = lineBreaker.nextRun(lineLogicalRight, availableWidth, !lineLogicalRight)) {
352             if (run->position == InlineLineBreaker::Run::Position::LineBegin)
353                 lineLogicalRight = 0;
354             lineLogicalRight += run->width;
355
356             maxContentLogicalRight = std::max(maxContentLogicalRight, lineLogicalRight);
357         }
358         return maxContentLogicalRight;
359     };
360
361     auto instrinsicWidthConstraints = FormattingContext::InstrinsicWidthConstraints { maximumLineWidth(0), maximumLineWidth(LayoutUnit::max()) };
362     formattingStateForRoot.setInstrinsicWidthConstraints(root(), instrinsicWidthConstraints);
363     return instrinsicWidthConstraints;
364 }
365
366 }
367 }
368
369 #endif