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