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