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