[LFC][IFC] Add generic inline line breaker
[WebKit-https.git] / Source / WebCore / layout / inlineformatting / InlineRunProvider.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 "InlineRunProvider.h"
28
29 #if ENABLE(LAYOUT_FORMATTING_CONTEXT)
30
31 #include "BreakLines.h"
32 #include "LayoutInlineBox.h"
33 #include <wtf/IsoMallocInlines.h>
34
35 namespace WebCore {
36 namespace Layout {
37
38 WTF_MAKE_ISO_ALLOCATED_IMPL(InlineRunProvider);
39
40 InlineRunProvider::InlineRunProvider(InlineFormattingState& inlineFormattingState)
41     : m_inlineFormattingState(inlineFormattingState)
42 {
43 }
44
45 void InlineRunProvider::append(const Box& layoutBox)
46 {
47     auto inlineItem = std::make_unique<InlineItem>(layoutBox);
48
49     // Special case text item. Texts can overlap multiple items. <span>foo</span><span>bar</span>
50     switch (inlineItem->type()) {
51     case InlineItem::Type::Text:
52         processInlineTextItem(*inlineItem);
53         break;
54     case InlineItem::Type::HardLineBreak:
55         m_inlineRuns.append(InlineRunProvider::Run::createHardLineBreakRun(*inlineItem));
56         break;
57     case InlineItem::Type::InlineBox:
58         m_inlineRuns.append(InlineRunProvider::Run::createBoxRun(*inlineItem));
59         break;
60     case InlineItem::Type::Float:
61         m_inlineRuns.append(InlineRunProvider::Run::createFloatRun(*inlineItem));
62         break;
63     default:
64         ASSERT_NOT_IMPLEMENTED_YET();
65     }
66
67     m_inlineFormattingState.inlineContent().add(WTFMove(inlineItem));
68 }
69
70 void InlineRunProvider::insertBefore(const Box&, const Box&)
71 {
72 }
73
74 void InlineRunProvider::remove(const Box&)
75 {
76 }
77
78 static inline bool isWhitespace(char character, bool preserveNewline)
79 {
80     return character == ' ' || character == '\t' || (character == '\n' && !preserveNewline);
81 }
82
83 static inline bool isSoftLineBreak(char character, bool preserveNewline)
84 {
85     return preserveNewline && character == '\n';
86 }
87
88 bool InlineRunProvider::isContinousContent(InlineRunProvider::Run::Type newRunType, const InlineItem& newInlineItem)
89 {
90     if (m_inlineRuns.isEmpty())
91         return false;
92
93     auto& lastRun = m_inlineRuns.last();
94     // Same element, check type only.
95     if (&newInlineItem == &lastRun.inlineItem())
96         return newRunType == lastRun.type();
97
98     // This new run is from a different inline box.
99     // FIXME: check style.
100     if (newRunType == InlineRunProvider::Run::Type::NonWhitespace && lastRun.isNonWhitespace())
101         return true;
102
103     if (newRunType == InlineRunProvider::Run::Type::Whitespace && lastRun.isWhitespace())
104         return newInlineItem.style().collapseWhiteSpace() == lastRun.style().collapseWhiteSpace();
105
106     return false;
107 }
108
109 void InlineRunProvider::processInlineTextItem(const InlineItem& inlineItem)
110 {
111     // We need to reset the run iterator when the text content is not continuous.
112     // <span>foo</span><img src=""><span>bar</span> (FIXME: floats?)
113     if (!m_inlineRuns.isEmpty() && !m_inlineRuns.last().isText()) {
114         m_lineBreakIterator.resetPriorContext();
115         m_lineBreakIterator.resetStringAndReleaseIterator("", "", LineBreakIteratorMode::Default);
116     }
117
118     auto& style = inlineItem.style();
119     auto text = inlineItem.textContent();
120     ItemPosition currentItemPosition = 0;
121     while (currentItemPosition < text.length()) {
122
123         // Soft linebreak?
124         if (isSoftLineBreak(text[currentItemPosition], style.preserveNewline())) {
125             m_inlineRuns.append(InlineRunProvider::Run::createSoftLineBreakRun(inlineItem));
126             ++currentItemPosition;
127             continue;
128         }
129
130         auto isWhitespaceRun = isWhitespace(text[currentItemPosition], style.preserveNewline());
131         auto length = isWhitespaceRun ? moveToNextNonWhitespacePosition(inlineItem, currentItemPosition) : moveToNextBreakablePosition(inlineItem, currentItemPosition);
132
133         if (isContinousContent(isWhitespaceRun ? InlineRunProvider::Run::Type::Whitespace : InlineRunProvider::Run::Type::NonWhitespace, inlineItem)) {
134             auto textContext = m_inlineRuns.last().textContext();
135             textContext->setLength(textContext->length() + length);
136         } else {
137             m_inlineRuns.append(isWhitespaceRun ? InlineRunProvider::Run::createWhitespaceRun(inlineItem, currentItemPosition, length, style.collapseWhiteSpace())
138                 : InlineRunProvider::Run::createNonWhitespaceRun(inlineItem, currentItemPosition, length));
139         }
140
141         currentItemPosition += length;
142     }
143 }
144
145 unsigned InlineRunProvider::moveToNextNonWhitespacePosition(const InlineItem& inlineItem, ItemPosition currentItemPosition)
146 {
147     auto text = inlineItem.textContent();
148     auto preserveNewline = inlineItem.style().preserveNewline();
149     auto nextNonWhiteSpacePosition = currentItemPosition;
150
151     while (nextNonWhiteSpacePosition < text.length() && isWhitespace(text[nextNonWhiteSpacePosition], preserveNewline))
152         ++nextNonWhiteSpacePosition;
153     return nextNonWhiteSpacePosition - currentItemPosition;
154 }
155
156 unsigned InlineRunProvider::moveToNextBreakablePosition(const InlineItem& inlineItem, ItemPosition currentItemPosition)
157 {
158     auto findNextBreakablePosition = [&](auto inlineText, auto& style, ItemPosition startPosition) {
159         // Swap iterator's content if we advanced to a new string.
160         auto iteratorText = m_lineBreakIterator.stringView();
161
162         if (iteratorText != inlineText) {
163             auto textLength = iteratorText.length();
164             auto lastCharacter = textLength > 0 ? iteratorText[textLength - 1] : 0;
165             auto secondToLastCharacter = textLength > 1 ? iteratorText[textLength - 2] : 0;
166             m_lineBreakIterator.setPriorContext(lastCharacter, secondToLastCharacter);
167             m_lineBreakIterator.resetStringAndReleaseIterator(inlineText, style.locale(), LineBreakIteratorMode::Default);
168         }
169
170         auto keepAllWordsForCJK = style.wordBreak() == WordBreak::KeepAll;
171         auto breakNBSP = style.autoWrap() && style.nbspMode() == NBSPMode::Space;
172
173         if (keepAllWordsForCJK) {
174             if (breakNBSP)
175                 return nextBreakablePositionKeepingAllWords(m_lineBreakIterator, startPosition);
176             return nextBreakablePositionKeepingAllWordsIgnoringNBSP(m_lineBreakIterator, startPosition);
177         }
178
179         if (m_lineBreakIterator.mode() == LineBreakIteratorMode::Default) {
180             if (breakNBSP)
181                 return WebCore::nextBreakablePosition(m_lineBreakIterator, startPosition);
182             return nextBreakablePositionIgnoringNBSP(m_lineBreakIterator, startPosition);
183         }
184
185         if (breakNBSP)
186             return nextBreakablePositionWithoutShortcut(m_lineBreakIterator, startPosition);
187         return nextBreakablePositionIgnoringNBSPWithoutShortcut(m_lineBreakIterator, startPosition);
188     };
189
190     auto& style = inlineItem.style();
191     auto nextBreakablePosition = findNextBreakablePosition(inlineItem.textContent(), style, currentItemPosition);
192     return nextBreakablePosition - currentItemPosition;
193 }
194
195 }
196 }
197 #endif