c0cd5da060038e760f4f6feaccb1b58ecb5ddb12
[WebKit-https.git] / Source / WebCore / layout / inlineformatting / InlineLine.cpp
1 /*
2  * Copyright (C) 2019 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 "InlineLine.h"
28
29 #if ENABLE(LAYOUT_FORMATTING_CONTEXT)
30
31 #include <wtf/IsoMallocInlines.h>
32
33 namespace WebCore {
34 namespace Layout {
35
36 WTF_MAKE_ISO_ALLOCATED_IMPL(Line);
37
38 Line::Content::Run::Run(const InlineItem& inlineItem, const Display::Rect& logicalRect, TextContext textContext, bool isCollapsed, bool canBeExtended)
39     : inlineItem(inlineItem)
40     , logicalRect(logicalRect)
41     , textContext(textContext)
42     , isCollapsed(isCollapsed)
43     , canBeExtended(canBeExtended)
44 {
45 }
46
47 Line::Line(const LayoutState& layoutState, const InitialConstraints& initialConstraints, SkipVerticalAligment skipVerticalAligment)
48     : m_layoutState(layoutState)
49     , m_content(std::make_unique<Line::Content>())
50     , m_logicalTopLeft(initialConstraints.topLeft)
51     , m_baseline({ initialConstraints.heightAndBaseline.baselineOffset, initialConstraints.heightAndBaseline.height - initialConstraints.heightAndBaseline.baselineOffset })
52     , m_initialStrut(initialConstraints.heightAndBaseline.strut)
53     , m_contentLogicalHeight(initialConstraints.heightAndBaseline.height)
54     , m_lineLogicalWidth(initialConstraints.availableWidth)
55     , m_skipVerticalAligment(skipVerticalAligment == SkipVerticalAligment::Yes)
56 {
57 }
58
59 bool Line::isVisuallyEmpty() const
60 {
61     // FIXME: This should be cached instead -as the inline items are being added.
62     // Return true for empty inline containers like <span></span>.
63     for (auto& run : m_content->runs()) {
64         if (run->inlineItem.isContainerStart()) {
65             auto& displayBox = m_layoutState.displayBoxForLayoutBox(run->inlineItem.layoutBox());
66             if (displayBox.horizontalBorder() || (displayBox.horizontalPadding() && displayBox.horizontalPadding().value()))
67                 return false;
68             continue;
69         }
70         if (run->inlineItem.isContainerEnd())
71             continue;
72         if (run->inlineItem.layoutBox().establishesFormattingContext()) {
73             ASSERT(run->inlineItem.layoutBox().isInlineBlockBox());
74             auto& displayBox = m_layoutState.displayBoxForLayoutBox(run->inlineItem.layoutBox());
75             if (!displayBox.width())
76                 continue;
77             if (m_skipVerticalAligment || displayBox.height())
78                 return false;
79             continue;
80         }
81         if (!run->isCollapsed)
82             return false;
83     }
84     return true;
85 }
86
87 std::unique_ptr<Line::Content> Line::close()
88 {
89     removeTrailingTrimmableContent();
90     if (!m_skipVerticalAligment) {
91         if (isVisuallyEmpty()) {
92             m_baseline = { };
93             m_baselineTop = { };
94             m_contentLogicalHeight = { };
95         }
96
97         // Remove descent when all content is baseline aligned but none of them have descent.
98         if (InlineFormattingContext::Quirks::lineDescentNeedsCollapsing(m_layoutState, *m_content)) {
99             m_contentLogicalHeight -= m_baseline.descent;
100             m_baseline.descent = { };
101         }
102
103         for (auto& run : m_content->runs()) {
104             LayoutUnit logicalTop;
105             auto& inlineItem = run->inlineItem;
106             auto& layoutBox = inlineItem.layoutBox();
107             auto verticalAlign = inlineItem.style().verticalAlign();
108             auto ascent = inlineItem.style().fontMetrics().ascent();
109
110             switch (verticalAlign) {
111             case VerticalAlign::Baseline:
112                 if (inlineItem.isLineBreak() || inlineItem.isText())
113                     logicalTop = baselineOffset() - ascent;
114                 else if (inlineItem.isContainerStart()) {
115                     auto& displayBox = m_layoutState.displayBoxForLayoutBox(layoutBox);
116                     logicalTop = baselineOffset() - ascent - displayBox.borderTop() - displayBox.paddingTop().valueOr(0);
117                 } else if (layoutBox.isInlineBlockBox() && layoutBox.establishesInlineFormattingContext()) {
118                     auto& formattingState = downcast<InlineFormattingState>(m_layoutState.establishedFormattingState(layoutBox));
119                     // Spec makes us generate at least one line -even if it is empty.
120                     ASSERT(!formattingState.lineBoxes().isEmpty());
121                     auto inlineBlockBaseline = formattingState.lineBoxes().last().baseline();
122                     logicalTop = baselineOffset() - inlineBlockBaseline.ascent;
123                 } else
124                     logicalTop = baselineOffset() - run->logicalRect.height();
125                 break;
126             case VerticalAlign::Top:
127                 logicalTop = { };
128                 break;
129             case VerticalAlign::Bottom:
130                 logicalTop = logicalBottom() - run->logicalRect.height();
131                 break;
132             default:
133                 ASSERT_NOT_IMPLEMENTED_YET();
134                 break;
135             }
136             run->logicalRect.setTop(logicalTop);
137         }
138     }
139     m_content->setLogicalRect({ logicalTop(), logicalLeft(), contentLogicalWidth(), logicalHeight() });
140     m_content->setBaseline(m_baseline);
141     m_content->setBaselineOffset(baselineOffset());
142     return WTFMove(m_content);
143 }
144
145 void Line::removeTrailingTrimmableContent()
146 {
147     // Collapse trimmable trailing content
148     LayoutUnit trimmableWidth;
149     for (auto* trimmableRun : m_trimmableContent) {
150         trimmableRun->isCollapsed = true;
151         trimmableWidth += trimmableRun->logicalRect.width();
152     }
153     m_contentLogicalWidth -= trimmableWidth;
154 }
155
156 void Line::moveLogicalLeft(LayoutUnit delta)
157 {
158     if (!delta)
159         return;
160     ASSERT(delta > 0);
161     // Shrink the line and move the items.
162     m_logicalTopLeft.move(delta, 0);
163     m_lineLogicalWidth -= delta;
164     for (auto& run : m_content->runs())
165         run->logicalRect.moveHorizontally(delta);
166 }
167
168 void Line::moveLogicalRight(LayoutUnit delta)
169 {
170     ASSERT(delta > 0);
171     m_lineLogicalWidth -= delta;
172 }
173
174 LayoutUnit Line::trailingTrimmableWidth() const
175 {
176     LayoutUnit trimmableWidth;
177     for (auto* trimmableRun : m_trimmableContent) {
178         ASSERT(!trimmableRun->isCollapsed);
179         trimmableWidth += trimmableRun->logicalRect.width();
180     }
181     return trimmableWidth;
182 }
183
184 void Line::append(const InlineItem& inlineItem, LayoutUnit logicalWidth)
185 {
186     if (inlineItem.isHardLineBreak())
187         return appendHardLineBreak(inlineItem);
188     if (is<InlineTextItem>(inlineItem))
189         return appendTextContent(downcast<InlineTextItem>(inlineItem), logicalWidth);
190     if (inlineItem.isContainerStart())
191         return appendInlineContainerStart(inlineItem, logicalWidth);
192     if (inlineItem.isContainerEnd())
193         return appendInlineContainerEnd(inlineItem, logicalWidth);
194     if (inlineItem.layoutBox().isReplaced())
195         return appendReplacedInlineBox(inlineItem, logicalWidth);
196     appendNonReplacedInlineBox(inlineItem, logicalWidth);
197 }
198
199 void Line::appendNonBreakableSpace(const InlineItem& inlineItem, const Display::Rect& logicalRect)
200 {
201     m_content->runs().append(std::make_unique<Content::Run>(inlineItem, logicalRect, Content::Run::TextContext { }, false, false));
202     m_contentLogicalWidth += logicalRect.width();
203 }
204
205 void Line::appendInlineContainerStart(const InlineItem& inlineItem, LayoutUnit logicalWidth)
206 {
207     auto logicalRect = Display::Rect { };
208     logicalRect.setLeft(contentLogicalRight());
209     logicalRect.setWidth(logicalWidth);
210
211     if (!m_skipVerticalAligment) {
212         auto logicalHeight = inlineItemContentHeight(inlineItem);
213         adjustBaselineAndLineHeight(inlineItem, logicalHeight);
214         logicalRect.setHeight(logicalHeight);
215     }
216     appendNonBreakableSpace(inlineItem, logicalRect);
217 }
218
219 void Line::appendInlineContainerEnd(const InlineItem& inlineItem, LayoutUnit logicalWidth)
220 {
221     // This is really just a placeholder to mark the end of the inline level container.
222     auto logicalRect = Display::Rect { 0, contentLogicalRight(), logicalWidth, 0 };
223     appendNonBreakableSpace(inlineItem, logicalRect);
224 }
225
226 void Line::appendTextContent(const InlineTextItem& inlineItem, LayoutUnit logicalWidth)
227 {
228     auto isTrimmable = TextUtil::isTrimmableContent(inlineItem);
229     if (!isTrimmable)
230         m_trimmableContent.clear();
231
232     auto shouldCollapseCompletely = [&] {
233         if (!isTrimmable)
234             return false;
235         // Leading whitespace.
236         auto& runs = m_content->runs();
237         if (runs.isEmpty())
238             return true;
239         // Check if the last item is trimmable as well.
240         for (int index = runs.size() - 1; index >= 0; --index) {
241             auto& inlineItem = runs[index]->inlineItem;
242             if (inlineItem.isBox())
243                 return false;
244             if (inlineItem.isText())
245                 return TextUtil::isTrimmableContent(inlineItem);
246             ASSERT(inlineItem.isContainerStart() || inlineItem.isContainerEnd());
247         }
248         return true;
249     };
250
251     // Collapsed line items don't contribute to the line width.
252     auto isCompletelyCollapsed = shouldCollapseCompletely();
253     auto canBeExtended = !isCompletelyCollapsed && !inlineItem.isCollapsed();
254     
255     auto logicalRect = Display::Rect { };
256     logicalRect.setLeft(contentLogicalRight());
257     logicalRect.setWidth(logicalWidth);
258     if (!m_skipVerticalAligment) {
259         auto runHeight = inlineItemContentHeight(inlineItem);
260         logicalRect.setHeight(runHeight);
261         adjustBaselineAndLineHeight(inlineItem, runHeight);
262     }
263
264     auto textContext = Content::Run::TextContext { inlineItem.start(), inlineItem.isCollapsed() ? 1 : inlineItem.length() };
265     auto lineItem = std::make_unique<Content::Run>(inlineItem, logicalRect, textContext, isCompletelyCollapsed, canBeExtended);
266     if (isTrimmable && !isCompletelyCollapsed)
267         m_trimmableContent.add(lineItem.get());
268
269     m_content->runs().append(WTFMove(lineItem));
270     m_contentLogicalWidth += isCompletelyCollapsed ? LayoutUnit() : logicalWidth;
271 }
272
273 void Line::appendNonReplacedInlineBox(const InlineItem& inlineItem, LayoutUnit logicalWidth)
274 {
275     auto& displayBox = m_layoutState.displayBoxForLayoutBox(inlineItem.layoutBox());
276     auto horizontalMargin = displayBox.horizontalMargin();    
277     auto logicalRect = Display::Rect { };
278
279     logicalRect.setLeft(contentLogicalRight() + horizontalMargin.start);
280     logicalRect.setWidth(logicalWidth);
281     if (!m_skipVerticalAligment) {
282         adjustBaselineAndLineHeight(inlineItem, displayBox.marginBoxHeight());
283         logicalRect.setHeight(inlineItemContentHeight(inlineItem));
284     }
285
286     m_content->runs().append(std::make_unique<Content::Run>(inlineItem, logicalRect, Content::Run::TextContext { }, false, false));
287     m_contentLogicalWidth += (logicalWidth + horizontalMargin.start + horizontalMargin.end);
288     m_trimmableContent.clear();
289 }
290
291 void Line::appendReplacedInlineBox(const InlineItem& inlineItem, LayoutUnit logicalWidth)
292 {
293     // FIXME Surely replaced boxes behave differently.
294     appendNonReplacedInlineBox(inlineItem, logicalWidth);
295 }
296
297 void Line::appendHardLineBreak(const InlineItem& inlineItem)
298 {
299     auto logicalRect = Display::Rect { };
300     logicalRect.setLeft(contentLogicalRight());
301     logicalRect.setWidth({ });
302     if (!m_skipVerticalAligment) {
303         adjustBaselineAndLineHeight(inlineItem, { });
304         logicalRect.setHeight(logicalHeight());
305     }
306     m_content->runs().append(std::make_unique<Content::Run>(inlineItem, logicalRect, Content::Run::TextContext { }, false, false));
307 }
308
309 void Line::adjustBaselineAndLineHeight(const InlineItem& inlineItem, LayoutUnit runHeight)
310 {
311     ASSERT(!inlineItem.isContainerEnd());
312     auto& layoutBox = inlineItem.layoutBox();
313     auto& style = layoutBox.style();
314
315     if (inlineItem.isContainerStart()) {
316         auto& fontMetrics = style.fontMetrics();
317         auto halfLeading = halfLeadingMetrics(fontMetrics, style.computedLineHeight());
318         if (halfLeading.descent > 0)
319             m_baseline.descent = std::max(m_baseline.descent, halfLeading.descent);
320         if (halfLeading.ascent > 0)
321             m_baseline.ascent = std::max(m_baseline.ascent, halfLeading.ascent);
322         m_contentLogicalHeight = std::max(m_contentLogicalHeight, baselineAlignedContentHeight());
323         return;
324     }
325     // Apply initial strut if needed.
326     if (inlineItem.isText() || inlineItem.isHardLineBreak()) {
327         if (!m_initialStrut)
328             return;
329         m_baseline.ascent = std::max(m_initialStrut->ascent, m_baseline.ascent);
330         m_baseline.descent = std::max(m_initialStrut->descent, m_baseline.descent);
331         m_contentLogicalHeight = std::max(m_contentLogicalHeight, baselineAlignedContentHeight());
332         m_initialStrut = { };
333         return;
334     }
335     // Replaced and non-replaced inline level box.
336     switch (inlineItem.style().verticalAlign()) {
337     case VerticalAlign::Baseline:
338         if (layoutBox.isInlineBlockBox() && layoutBox.establishesInlineFormattingContext()) {
339             // Inline-blocks with inline content always have baselines.
340             auto& formattingState = downcast<InlineFormattingState>(m_layoutState.establishedFormattingState(layoutBox));
341             // Spec makes us generate at least one line -even if it is empty.
342             ASSERT(!formattingState.lineBoxes().isEmpty());
343             auto inlineBlockBaseline = formattingState.lineBoxes().last().baseline();
344             m_baseline.descent = std::max(inlineBlockBaseline.descent, m_baseline.descent);
345             m_baseline.ascent = std::max(inlineBlockBaseline.ascent, m_baseline.ascent);
346             m_contentLogicalHeight = std::max(std::max(m_contentLogicalHeight, runHeight), baselineAlignedContentHeight());
347             break;
348         }
349         m_baseline.descent = std::max<LayoutUnit>(0, m_baseline.descent);
350         m_baseline.ascent = std::max(runHeight, m_baseline.ascent);
351         m_contentLogicalHeight = std::max(m_contentLogicalHeight, baselineAlignedContentHeight());
352         break;
353     case VerticalAlign::Top:
354         // Top align content never changes the baseline offset, it only pushes the bottom of the line further down.
355         m_contentLogicalHeight = std::max(runHeight, m_contentLogicalHeight);
356         break;
357     case VerticalAlign::Bottom:
358         if (m_contentLogicalHeight < runHeight) {
359             m_baselineTop += runHeight - m_contentLogicalHeight;
360             m_contentLogicalHeight = runHeight;
361         }
362         break;
363     default:
364         ASSERT_NOT_IMPLEMENTED_YET();
365         break;
366     }
367 }
368
369 LayoutUnit Line::inlineItemContentHeight(const InlineItem& inlineItem) const
370 {
371     ASSERT(!m_skipVerticalAligment);
372     auto& fontMetrics = inlineItem.style().fontMetrics();
373     if (inlineItem.isLineBreak() || is<InlineTextItem>(inlineItem))
374         return fontMetrics.height();
375
376     auto& layoutBox = inlineItem.layoutBox();
377     ASSERT(m_layoutState.hasDisplayBox(layoutBox));
378     auto& displayBox = m_layoutState.displayBoxForLayoutBox(layoutBox);
379
380     if (layoutBox.isFloatingPositioned())
381         return displayBox.borderBoxHeight();
382
383     if (layoutBox.isReplaced())
384         return displayBox.borderBoxHeight();
385
386     if (inlineItem.isContainerStart() || inlineItem.isContainerEnd())
387         return fontMetrics.height() + displayBox.verticalBorder() + displayBox.verticalPadding().valueOr(0);
388
389     // Non-replaced inline box (e.g. inline-block)
390     return displayBox.borderBoxHeight();
391 }
392
393 LineBox::Baseline Line::halfLeadingMetrics(const FontMetrics& fontMetrics, LayoutUnit lineLogicalHeight)
394 {
395     auto ascent = fontMetrics.ascent();
396     auto descent = fontMetrics.descent();
397     // 10.8.1 Leading and half-leading
398     auto leading = lineLogicalHeight - (ascent + descent);
399     // Inline tree is all integer based.
400     auto adjustedAscent = std::max((ascent + leading / 2).floor(), 0);
401     auto adjustedDescent = std::max((descent + leading / 2).ceil(), 0);
402     return { adjustedAscent, adjustedDescent };
403 }
404
405 }
406 }
407
408 #endif