[WTF] Add makeUnique<T>, which ensures T is fast-allocated, makeUnique / makeUniqueWi...
[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 "InlineTextItem.h"
34 #include "LayoutBox.h"
35 #include "LayoutContainer.h"
36 #include "LayoutState.h"
37 #include "Logging.h"
38 #include "Textutil.h"
39 #include <wtf/IsoMallocInlines.h>
40 #include <wtf/text/TextStream.h>
41
42 namespace WebCore {
43 namespace Layout {
44
45 WTF_MAKE_ISO_ALLOCATED_IMPL(InlineFormattingContext);
46
47 InlineFormattingContext::InlineFormattingContext(const Box& formattingContextRoot, InlineFormattingState& formattingState)
48     : FormattingContext(formattingContextRoot, formattingState)
49 {
50 }
51
52 static inline const Box* nextInPreOrder(const Box& layoutBox, const Container& stayWithin)
53 {
54     const Box* nextInPreOrder = nullptr;
55     if (!layoutBox.establishesFormattingContext() && is<Container>(layoutBox) && downcast<Container>(layoutBox).hasInFlowOrFloatingChild())
56         return downcast<Container>(layoutBox).firstInFlowOrFloatingChild();
57
58     for (nextInPreOrder = &layoutBox; nextInPreOrder && nextInPreOrder != &stayWithin; nextInPreOrder = nextInPreOrder->parent()) {
59         if (auto* nextSibling = nextInPreOrder->nextInFlowOrFloatingSibling())
60             return nextSibling;
61     }
62     return nullptr;
63 }
64
65 void InlineFormattingContext::layout() const
66 {
67     if (!is<Container>(root()))
68         return;
69
70     auto& root = downcast<Container>(this->root());
71     if (!root.hasInFlowOrFloatingChild())
72         return;
73
74     LOG_WITH_STREAM(FormattingContextLayout, stream << "[Start] -> inline formatting context -> formatting root(" << &root << ")");
75     auto availableWidth = layoutState().displayBoxForLayoutBox(root).contentBoxWidth();
76     auto usedValues = UsedHorizontalValues { availableWidth };
77     auto* layoutBox = root.firstInFlowOrFloatingChild();
78     // Compute width/height for non-text content and margin/border/padding for inline containers.
79     while (layoutBox) {
80         if (layoutBox->establishesFormattingContext())
81             layoutFormattingContextRoot(*layoutBox, usedValues);
82         else if (is<Container>(*layoutBox))
83             computeMarginBorderAndPaddingForInlineContainer(downcast<Container>(*layoutBox), usedValues);
84         else if (layoutBox->isReplaced())
85             computeWidthAndHeightForReplacedInlineBox(*layoutBox, usedValues);
86         else {
87             ASSERT(layoutBox->isInlineLevelBox());
88             initializeMarginBorderAndPaddingForGenericInlineBox(*layoutBox);
89         }
90         layoutBox = nextInPreOrder(*layoutBox, root);
91     }
92
93     // FIXME: This is such a waste when intrinsic width computation already collected the inline items.
94     formattingState().inlineItems().clear();
95     formattingState().inlineRuns().clear();
96
97     collectInlineContent();
98     InlineLayout(*this).layout(formattingState().inlineItems(), availableWidth);
99     LOG_WITH_STREAM(FormattingContextLayout, stream << "[End] -> inline formatting context -> formatting root(" << &root << ")");
100 }
101
102 FormattingContext::IntrinsicWidthConstraints InlineFormattingContext::computedIntrinsicWidthConstraints() const
103 {
104     auto& layoutState = this->layoutState();
105     ASSERT(!formattingState().intrinsicWidthConstraints());
106
107     if (!is<Container>(root()) || !downcast<Container>(root()).hasInFlowOrFloatingChild()) {
108         auto constraints = Geometry::constrainByMinMaxWidth(root(), { });
109         formattingState().setIntrinsicWidthConstraints(constraints);
110         return constraints;
111     }
112
113     auto& root = downcast<Container>(this->root());
114     Vector<const Box*> formattingContextRootList;
115     auto usedValues = UsedHorizontalValues { };
116     auto* layoutBox = root.firstInFlowOrFloatingChild();
117     while (layoutBox) {
118         if (layoutBox->establishesFormattingContext()) {
119             formattingContextRootList.append(layoutBox);
120             computeIntrinsicWidthForFormattingRoot(*layoutBox);
121         } else if (layoutBox->isReplaced() || is<Container>(*layoutBox)) {
122             computeBorderAndPadding(*layoutBox, usedValues);
123             // inline-block and replaced.
124             auto needsWidthComputation = layoutBox->isReplaced();
125             if (needsWidthComputation)
126                 computeWidthAndMargin(*layoutBox, usedValues);
127             else {
128                 // Simple inline container with no intrinsic width <span>.
129                 computeHorizontalMargin(*layoutBox, usedValues);
130             }
131         }
132         layoutBox = nextInPreOrder(*layoutBox, root);
133     }
134
135     collectInlineContent();
136
137     auto maximumLineWidth = [&](auto availableWidth) {
138         // Switch to the min/max formatting root width values before formatting the lines.
139         for (auto* formattingRoot : formattingContextRootList) {
140             auto intrinsicWidths = layoutState.formattingStateForBox(*formattingRoot).intrinsicWidthConstraintsForBox(*formattingRoot);
141             auto& displayBox = layoutState.displayBoxForLayoutBox(*formattingRoot);
142             auto contentWidth = (availableWidth ? intrinsicWidths->maximum : intrinsicWidths->minimum) - displayBox.horizontalMarginBorderAndPadding();
143             displayBox.setContentBoxWidth(contentWidth);
144         }
145         return InlineLayout(*this).computedIntrinsicWidth(formattingState().inlineItems(), availableWidth);
146     };
147
148     auto constraints = Geometry::constrainByMinMaxWidth(root, { maximumLineWidth(0), maximumLineWidth(LayoutUnit::max()) });
149     formattingState().setIntrinsicWidthConstraints(constraints);
150     return constraints;
151 }
152
153 void InlineFormattingContext::initializeMarginBorderAndPaddingForGenericInlineBox(const Box& layoutBox) const
154 {
155     ASSERT(layoutBox.isAnonymous() || layoutBox.isLineBreakBox());
156     auto& displayBox = layoutState().displayBoxForLayoutBox(layoutBox);
157
158     displayBox.setVerticalMargin({ { }, { } });
159     displayBox.setHorizontalMargin({ });
160     displayBox.setBorder({ { }, { } });
161     displayBox.setPadding({ });
162 }
163
164 void InlineFormattingContext::computeMarginBorderAndPaddingForInlineContainer(const Container& container, UsedHorizontalValues usedValues) const
165 {
166     computeHorizontalMargin(container, usedValues);
167     computeBorderAndPadding(container, usedValues);
168     // Inline containers (<span>) have 0 vertical margins.
169     layoutState().displayBoxForLayoutBox(container).setVerticalMargin({ { }, { } });
170 }
171
172 void InlineFormattingContext::computeIntrinsicWidthForFormattingRoot(const Box& formattingRoot) const
173 {
174     ASSERT(formattingRoot.establishesFormattingContext());
175     auto& layoutState = this->layoutState();
176
177     auto usedValues = UsedHorizontalValues { };
178     computeBorderAndPadding(formattingRoot, usedValues);
179     computeHorizontalMargin(formattingRoot, usedValues);
180
181     IntrinsicWidthConstraints constraints;
182     if (auto fixedWidth = Geometry::fixedValue(formattingRoot.style().logicalWidth()))
183         constraints = { *fixedWidth, *fixedWidth };
184     else
185         constraints = layoutState.createFormattingContext(formattingRoot)->computedIntrinsicWidthConstraints();
186     constraints = Geometry::constrainByMinMaxWidth(formattingRoot, constraints);
187     constraints.expand(layoutState.displayBoxForLayoutBox(formattingRoot).horizontalMarginBorderAndPadding());
188     formattingState().setIntrinsicWidthConstraintsForBox(formattingRoot, constraints);
189 }
190
191 void InlineFormattingContext::computeHorizontalMargin(const Box& layoutBox, UsedHorizontalValues usedValues) const
192 {
193     auto computedHorizontalMargin = Geometry::computedHorizontalMargin(layoutBox, usedValues);
194     auto& displayBox = layoutState().displayBoxForLayoutBox(layoutBox);
195     displayBox.setHorizontalComputedMargin(computedHorizontalMargin);
196     displayBox.setHorizontalMargin({ computedHorizontalMargin.start.valueOr(0), computedHorizontalMargin.end.valueOr(0) });
197 }
198
199 void InlineFormattingContext::computeWidthAndMargin(const Box& layoutBox, UsedHorizontalValues usedValues) const
200 {
201     auto& layoutState = this->layoutState();
202     WidthAndMargin widthAndMargin;
203     if (layoutBox.isFloatingPositioned())
204         widthAndMargin = Geometry::floatingWidthAndMargin(layoutState, layoutBox, usedValues);
205     else if (layoutBox.isInlineBlockBox())
206         widthAndMargin = Geometry::inlineBlockWidthAndMargin(layoutState, layoutBox, usedValues);
207     else if (layoutBox.replaced())
208         widthAndMargin = Geometry::inlineReplacedWidthAndMargin(layoutState, layoutBox, usedValues);
209     else
210         ASSERT_NOT_REACHED();
211
212     auto& displayBox = layoutState.displayBoxForLayoutBox(layoutBox);
213     displayBox.setContentBoxWidth(widthAndMargin.width);
214     displayBox.setHorizontalMargin(widthAndMargin.usedMargin);
215     displayBox.setHorizontalComputedMargin(widthAndMargin.computedMargin);
216 }
217
218 void InlineFormattingContext::computeHeightAndMargin(const Box& layoutBox) const
219 {
220     auto& layoutState = this->layoutState();
221
222     HeightAndMargin heightAndMargin;
223     if (layoutBox.isFloatingPositioned())
224         heightAndMargin = Geometry::floatingHeightAndMargin(layoutState, layoutBox, { }, UsedHorizontalValues { layoutState.displayBoxForLayoutBox(*layoutBox.containingBlock()).contentBoxWidth() });
225     else if (layoutBox.isInlineBlockBox())
226         heightAndMargin = Geometry::inlineBlockHeightAndMargin(layoutState, layoutBox);
227     else if (layoutBox.replaced())
228         heightAndMargin = Geometry::inlineReplacedHeightAndMargin(layoutState, layoutBox, { });
229     else
230         ASSERT_NOT_REACHED();
231
232     auto& displayBox = layoutState.displayBoxForLayoutBox(layoutBox);
233     displayBox.setContentBoxHeight(heightAndMargin.height);
234     displayBox.setVerticalMargin({ heightAndMargin.nonCollapsedMargin, { } });
235 }
236
237 void InlineFormattingContext::layoutFormattingContextRoot(const Box& root, UsedHorizontalValues usedValues) const
238 {
239     ASSERT(root.isFloatingPositioned() || root.isInlineBlockBox());
240     ASSERT(usedValues.containingBlockWidth);
241
242     computeBorderAndPadding(root, usedValues);
243     computeWidthAndMargin(root, usedValues);
244     // This is similar to static positioning in block formatting context. We just need to initialize the top left position.
245     layoutState().displayBoxForLayoutBox(root).setTopLeft({ 0, 0 });
246     // Swich over to the new formatting context (the one that the root creates).
247     auto formattingContext = layoutState().createFormattingContext(root);
248     formattingContext->layout();
249     // Come back and finalize the root's height and margin.
250     computeHeightAndMargin(root);
251     // Now that we computed the root's height, we can go back and layout the out-of-flow content.
252     formattingContext->layoutOutOfFlowContent();
253 }
254
255 void InlineFormattingContext::computeWidthAndHeightForReplacedInlineBox(const Box& layoutBox, UsedHorizontalValues usedValues) const
256 {
257     ASSERT(!layoutBox.isContainer());
258     ASSERT(!layoutBox.establishesFormattingContext());
259     ASSERT(layoutBox.replaced());
260     ASSERT(usedValues.containingBlockWidth);
261
262     computeBorderAndPadding(layoutBox, usedValues);
263     computeWidthAndMargin(layoutBox, usedValues);
264     computeHeightAndMargin(layoutBox);
265 }
266
267 void InlineFormattingContext::collectInlineContent() const
268 {
269     auto& root = downcast<Container>(this->root());
270     // Traverse the tree and create inline items out of containers and leaf nodes. This essentially turns the tree inline structure into a flat one.
271     // <span>text<span></span><img></span> -> [ContainerStart][InlineBox][ContainerStart][ContainerEnd][InlineBox][ContainerEnd]
272     auto& formattingState = this->formattingState();
273     LayoutQueue layoutQueue;
274     layoutQueue.append(root.firstInFlowOrFloatingChild());
275     while (!layoutQueue.isEmpty()) {
276         auto treatAsInlineContainer = [](auto& layoutBox) {
277             return is<Container>(layoutBox) && !layoutBox.establishesFormattingContext();
278         };
279         while (true) {
280             auto& layoutBox = *layoutQueue.last();
281             if (!treatAsInlineContainer(layoutBox))
282                 break;
283             // This is the start of an inline container (e.g. <span>).
284             formattingState.addInlineItem(makeUnique<InlineItem>(layoutBox, InlineItem::Type::ContainerStart));
285             auto& container = downcast<Container>(layoutBox);
286             if (!container.hasInFlowOrFloatingChild())
287                 break;
288             layoutQueue.append(container.firstInFlowOrFloatingChild());
289         }
290
291         while (!layoutQueue.isEmpty()) {
292             auto& layoutBox = *layoutQueue.takeLast();
293             // This is the end of an inline container (e.g. </span>).
294             if (treatAsInlineContainer(layoutBox))
295                 formattingState.addInlineItem(makeUnique<InlineItem>(layoutBox, InlineItem::Type::ContainerEnd));
296             else if (layoutBox.isLineBreakBox())
297                 formattingState.addInlineItem(makeUnique<InlineItem>(layoutBox, InlineItem::Type::HardLineBreak));
298             else if (layoutBox.isFloatingPositioned())
299                 formattingState.addInlineItem(makeUnique<InlineItem>(layoutBox, InlineItem::Type::Float));
300             else {
301                 ASSERT(layoutBox.isInlineLevelBox());
302                 if (layoutBox.hasTextContent())
303                     InlineTextItem::createAndAppendTextItems(formattingState.inlineItems(), layoutBox);
304                 else
305                     formattingState.addInlineItem(makeUnique<InlineItem>(layoutBox, InlineItem::Type::Box));
306             }
307
308             if (auto* nextSibling = layoutBox.nextInFlowOrFloatingSibling()) {
309                 layoutQueue.append(nextSibling);
310                 break;
311             }
312         }
313     }
314 }
315
316 }
317 }
318
319 #endif