c1daa3ff13e7e18fca4431096ea58bf1b09a76f8
[WebKit-https.git] / Source / WebCore / layout / inlineformatting / Line.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 #include "InlineFormattingState.h"
29 #include "InlineRunProvider.h"
30
31 #if ENABLE(LAYOUT_FORMATTING_CONTEXT)
32
33 namespace WebCore {
34 namespace Layout {
35
36 InlineFormattingContext::Line::Line(InlineFormattingState& formattingState, const Box& formattingRoot)
37     : m_formattingState(formattingState)
38     , m_formattingRoot(formattingRoot)
39     , m_alignmentIsJustify(m_formattingRoot.style().textAlign() == TextAlignMode::Justify)
40 {
41 }
42
43 void InlineFormattingContext::Line::init(const Display::Box::Rect& logicalRect)
44 {
45     m_logicalRect = logicalRect;
46     m_availableWidth = logicalRect.width();
47
48     m_firstRunIndex = { };
49     m_lastRunIsWhitespace = false;
50     m_lastRunCanExpand = false;
51     m_trailingTrimmableContent = { };
52     m_closed = false;
53 }
54
55 void InlineFormattingContext::Line::adjustLogicalLeft(LayoutUnit delta)
56 {
57     ASSERT(delta > 0);
58
59     m_availableWidth -= delta;
60     m_logicalRect.shiftLeftTo(m_logicalRect.left() + delta);
61
62     if (!m_firstRunIndex)
63         return;
64
65     auto& inlineRuns = m_formattingState.inlineRuns();
66     for (auto runIndex = *m_firstRunIndex; runIndex < inlineRuns.size(); ++runIndex)
67         inlineRuns[runIndex].moveHorizontally(delta);
68 }
69
70 void InlineFormattingContext::Line::adjustLogicalRight(LayoutUnit delta)
71 {
72     ASSERT(delta > 0);
73
74     m_availableWidth -= delta;
75     m_logicalRect.shiftRightTo(m_logicalRect.right() - delta);
76 }
77
78 static LayoutUnit adjustedLineLogicalLeft(TextAlignMode align, LayoutUnit lineLogicalLeft, LayoutUnit remainingWidth)
79 {
80     switch (align) {
81     case TextAlignMode::Left:
82     case TextAlignMode::WebKitLeft:
83     case TextAlignMode::Start:
84         return lineLogicalLeft;
85     case TextAlignMode::Right:
86     case TextAlignMode::WebKitRight:
87     case TextAlignMode::End:
88         return lineLogicalLeft + std::max(remainingWidth, LayoutUnit());
89     case TextAlignMode::Center:
90     case TextAlignMode::WebKitCenter:
91         return lineLogicalLeft + std::max(remainingWidth / 2, LayoutUnit());
92     case TextAlignMode::Justify:
93         ASSERT_NOT_REACHED();
94         break;
95     }
96     ASSERT_NOT_REACHED();
97     return lineLogicalLeft;
98 }
99
100 static bool isTrimmableContent(const InlineRunProvider::Run& inlineRun)
101 {
102     return inlineRun.isWhitespace() && inlineRun.style().collapseWhiteSpace();
103 }
104
105 LayoutUnit InlineFormattingContext::Line::contentLogicalRight()
106 {
107     if (!m_firstRunIndex.has_value())
108         return m_logicalRect.left();
109
110     return m_formattingState.inlineRuns().last().logicalRight();
111 }
112
113 void InlineFormattingContext::Line::computeExpansionOpportunities(const InlineLineBreaker::Run& run)
114 {
115     if (!m_alignmentIsJustify)
116         return;
117
118     auto isExpansionOpportunity = [](auto currentRunIsWhitespace, auto lastRunIsWhitespace) {
119         return currentRunIsWhitespace || (!currentRunIsWhitespace && !lastRunIsWhitespace);
120     };
121
122     auto expansionBehavior = [](auto isAtExpansionOpportunity) {
123         ExpansionBehavior expansionBehavior = AllowTrailingExpansion;
124         expansionBehavior |= isAtExpansionOpportunity ? ForbidLeadingExpansion : AllowLeadingExpansion;
125         return expansionBehavior;
126     };
127
128     auto isAtExpansionOpportunity = isExpansionOpportunity(run.content.isWhitespace(), m_lastRunIsWhitespace);
129
130     auto& currentInlineRun = m_formattingState.inlineRuns().last();
131     auto& expansionOpportunity = currentInlineRun.expansionOpportunity();
132     if (isAtExpansionOpportunity)
133         ++expansionOpportunity.count;
134
135     expansionOpportunity.behavior = expansionBehavior(isAtExpansionOpportunity);
136 }
137
138 void InlineFormattingContext::Line::appendContent(const InlineLineBreaker::Run& run)
139 {
140     ASSERT(!isClosed());
141
142     auto& content = run.content;
143
144     // Append this text run to the end of the last text run, if the last run is continuous.
145     std::optional<InlineRun::TextContext> textRun;
146     if (content.isText()) {
147         auto textContext = content.textContext();
148         auto runLength = textContext->isCollapsed() ? 1 : textContext->length();
149         textRun = InlineRun::TextContext { textContext->start(), runLength };
150     }
151
152     auto requiresNewInlineRun = !hasContent() || !content.isText() || !m_lastRunCanExpand;
153     if (requiresNewInlineRun) {
154         // FIXME: This needs proper baseline handling
155         auto inlineRun = InlineRun { { logicalTop(), contentLogicalRight(), run.width, logicalBottom() - logicalTop() }, content.inlineItem() };
156         if (textRun)
157             inlineRun.setTextContext({ textRun->start(), textRun->length() });
158         m_formattingState.appendInlineRun(inlineRun);
159     } else {
160         // Non-text runs always require new inline run.
161         ASSERT(textRun);
162         auto& inlineRun = m_formattingState.inlineRuns().last();
163         inlineRun.setWidth(inlineRun.width() + run.width);
164         inlineRun.textContext()->setLength(inlineRun.textContext()->length() + textRun->length());
165     }
166
167     computeExpansionOpportunities(run);
168
169     m_availableWidth -= run.width;
170     m_lastRunIsWhitespace = content.isWhitespace();
171     m_lastRunCanExpand = content.isText() && !content.textContext()->isCollapsed();
172     m_firstRunIndex = m_firstRunIndex.value_or(m_formattingState.inlineRuns().size() - 1);
173     m_trailingTrimmableContent = { };
174     if (isTrimmableContent(content))
175         m_trailingTrimmableContent = TrailingTrimmableContent { run.width, textRun->length() };
176 }
177
178 void InlineFormattingContext::Line::justifyRuns()
179 {
180     if (!hasContent())
181         return;
182
183     auto& inlineRuns = m_formattingState.inlineRuns();
184     auto& lastInlineRun = inlineRuns.last();
185
186     // Adjust (forbid) trailing expansion for the last text run on line.
187     auto expansionBehavior = lastInlineRun.expansionOpportunity().behavior;
188     // Remove allow and add forbid.
189     expansionBehavior ^= AllowTrailingExpansion;
190     expansionBehavior |= ForbidTrailingExpansion;
191     lastInlineRun.expansionOpportunity().behavior = expansionBehavior;
192
193     // Collect expansion opportunities and justify the runs.
194     auto widthToDistribute = availableWidth();
195     if (widthToDistribute <= 0)
196         return;
197
198     auto expansionOpportunities = 0;
199     for (auto runIndex = *m_firstRunIndex; runIndex < inlineRuns.size(); ++runIndex)
200         expansionOpportunities += inlineRuns[runIndex].expansionOpportunity().count;
201
202     if (!expansionOpportunities)
203         return;
204
205     float expansion = widthToDistribute.toFloat() / expansionOpportunities;
206     LayoutUnit accumulatedExpansion = 0;
207     for (auto runIndex = *m_firstRunIndex; runIndex < inlineRuns.size(); ++runIndex) {
208         auto& inlineRun = inlineRuns[runIndex];
209         auto expansionForRun = inlineRun.expansionOpportunity().count * expansion;
210
211         inlineRun.expansionOpportunity().expansion = expansionForRun;
212         inlineRun.setLogicalLeft(inlineRun.logicalLeft() + accumulatedExpansion);
213         inlineRun.setWidth(inlineRun.width() + expansionForRun);
214         accumulatedExpansion += expansionForRun;
215     }
216 }
217
218 void InlineFormattingContext::Line::close(LastLine isLastLine)
219 {
220     auto trimTrailingContent = [&]{
221
222         if (!m_trailingTrimmableContent)
223             return;
224
225         auto& lastInlineRun = m_formattingState.inlineRuns().last();
226         lastInlineRun.setWidth(lastInlineRun.width() - m_trailingTrimmableContent->width);
227         lastInlineRun.textContext()->setLength(lastInlineRun.textContext()->length() - m_trailingTrimmableContent->length);
228
229         if (!lastInlineRun.textContext()->length()) {
230             if (*m_firstRunIndex == m_formattingState.inlineRuns().size() - 1)
231                 m_firstRunIndex = { };
232             m_formattingState.inlineRuns().removeLast();
233         }
234         m_availableWidth += m_trailingTrimmableContent->width;
235         m_trailingTrimmableContent = { };
236     };
237
238     auto alignRuns = [&]{
239
240         if (!hasContent())
241             return;
242
243         auto textAlignment = !m_alignmentIsJustify ? m_formattingRoot.style().textAlign() : isLastLine == LastLine::No ? TextAlignMode::Justify : TextAlignMode::Left;
244         if (textAlignment == TextAlignMode::Justify) {
245             justifyRuns();
246             return;
247         }
248
249         auto adjustedLogicalLeft = adjustedLineLogicalLeft(textAlignment, m_logicalRect.left(), m_availableWidth);
250         if (m_logicalRect.left() == adjustedLogicalLeft)
251             return;
252
253         auto& inlineRuns = m_formattingState.inlineRuns();
254         auto delta = adjustedLogicalLeft - m_logicalRect.left();
255         for (auto runIndex = *m_firstRunIndex; runIndex < inlineRuns.size(); ++runIndex)
256             inlineRuns[runIndex].setLogicalLeft(inlineRuns[runIndex].logicalLeft() + delta);
257     };
258
259     if (!hasContent())
260         return;
261
262     trimTrailingContent();
263     alignRuns();
264
265     m_isFirstLine = false;
266     m_closed = true;
267 }
268
269 }
270 }
271
272 #endif