[LCF][IFC] Add support for hyphenation.
[WebKit-https.git] / Source / WebCore / layout / inlineformatting / textlayout / TextContentProvider.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 "TextContentProvider.h"
28
29 #if ENABLE(LAYOUT_FORMATTING_CONTEXT)
30
31 #include "FontCascade.h"
32 #include "Hyphenation.h"
33 #include "RenderStyle.h"
34 #include "SimpleTextRunGenerator.h"
35 #include <wtf/IsoMallocInlines.h>
36
37 namespace WebCore {
38 namespace Layout {
39
40 WTF_MAKE_ISO_ALLOCATED_IMPL(TextContentProvider);
41
42 TextContentProvider::TextItem::Style::Style(const RenderStyle& style)
43     : font(style.fontCascade())
44     , collapseWhitespace(style.collapseWhiteSpace())
45     , hasKerningOrLigatures(font.enableKerning() || font.requiresShaping())
46     , wordSpacing(font.wordSpacing())
47     , tabWidth(collapseWhitespace ? 0 : style.tabSize())
48     , preserveNewline(style.preserveNewline())
49     , breakNBSP(style.autoWrap() && style.nbspMode() == NBSPMode::Space)
50     , keepAllWordsForCJK(style.wordBreak() == WordBreak::KeepAll)
51     , locale(style.locale())
52 {
53 }
54
55 TextContentProvider::TextContentProvider()
56     : m_textContentIterator(m_textContent)
57 {
58 }
59
60 TextContentProvider::~TextContentProvider()
61 {
62 }
63
64 void TextContentProvider::appendText(String text, const RenderStyle& style, bool canUseSimplifiedMeasure)
65 {
66     ASSERT(text.length());
67
68     auto start = length();
69     auto end = start + text.length();
70     m_textContent.append({ text, start, end, TextItem::Style(style), canUseSimplifiedMeasure });
71 }
72
73 void TextContentProvider::appendLineBreak()
74 {
75     m_hardLineBreaks.append(length());
76 }
77
78 static bool contains(ContentPosition position, const TextContentProvider::TextItem& textItem)
79 {
80     return textItem.start <= position && position < textItem.end;
81 }
82
83 const TextContentProvider::TextItem* TextContentProvider::findTextItemSlow(ContentPosition position) const
84 {
85     // Since this is an iterator like class, check the next item first (instead of starting the search from the beginning).
86     if (auto* textItem = (++m_textContentIterator).current()) {
87         if (contains(position, *textItem))
88             return textItem;
89     }
90
91     m_textContentIterator.reset();
92     while (auto* textItem = m_textContentIterator.current()) {
93         if (contains(position, *textItem))
94             return textItem;
95         ++m_textContentIterator;
96     }
97
98     ASSERT_NOT_REACHED();
99     return nullptr;
100 }
101
102 float TextContentProvider::width(ContentPosition from, ContentPosition to, float xPosition) const
103 {
104     if (from >= to) {
105         ASSERT_NOT_REACHED();
106         return 0;
107     }
108
109     auto contentLength = length();
110     if (from >= contentLength || to > contentLength) {
111         ASSERT_NOT_REACHED();
112         return 0;
113     }
114
115     float width = 0;
116     auto length = to - from;
117     auto* textItem = m_textContentIterator.current();
118     auto startPosition = from - (textItem && contains(from, *textItem) ? textItem->start : findTextItemSlow(from)->start);
119
120     while (length) {
121         textItem = m_textContentIterator.current();
122         if (!textItem) {
123             ASSERT_NOT_REACHED();
124             break;
125         }
126
127         auto endPosition = std::min<ItemPosition>(startPosition + length, textItem->text.length());
128         ASSERT(endPosition >= startPosition);
129         auto textWidth = this->textWidth(*textItem, startPosition, endPosition, xPosition);
130
131         xPosition += textWidth;
132         width += textWidth;
133         length -= (endPosition - startPosition);
134
135         startPosition = 0;
136         if (length)
137             ++m_textContentIterator;
138     }
139
140     return width;
141 }
142
143 float TextContentProvider::textWidth(const TextItem& textItem, ItemPosition from, ItemPosition to, float xPosition) const
144 {
145     if (from > to || to > textItem.end - textItem.start) {
146         ASSERT_NOT_REACHED();
147         return 0;
148     }
149
150     auto& style = textItem.style;
151     auto& font = style.font;
152     if (!font.size() || from == to)
153         return 0;
154
155     if (font.isFixedPitch())
156         return fixedPitchWidth(textItem.text, style, from, to, xPosition);
157
158     auto measureWithEndSpace = style.hasKerningOrLigatures && to < textItem.text.length() && textItem.text[to] == ' ';
159     if (measureWithEndSpace)
160         ++to;
161     float width = 0;
162     if (textItem.canUseSimplifiedMeasure)
163         width = font.widthForSimpleText(StringView(textItem.text).substring(from, to - from));
164     else  {
165         WebCore::TextRun run(StringView(textItem.text).substring(from, to - from), xPosition);
166         if (style.tabWidth)
167             run.setTabSize(true, style.tabWidth);
168         width = font.width(run);
169     }
170
171     if (measureWithEndSpace)
172         width -= (font.spaceWidth() + style.wordSpacing);
173
174     return std::max<float>(0, width);
175 }
176
177 float TextContentProvider::fixedPitchWidth(String text, const TextItem::Style& style, ItemPosition from, ItemPosition to, float xPosition) const
178 {
179     auto monospaceCharacterWidth = style.font.spaceWidth();
180     float width = 0;
181     for (auto i = from; i < to; ++i) {
182         auto character = text[i];
183         if (character >= ' ' || character == '\n')
184             width += monospaceCharacterWidth;
185         else if (character == '\t')
186             width += style.collapseWhitespace ? monospaceCharacterWidth : style.font.tabWidth(style.tabWidth, xPosition + width);
187
188         if (i > from && (character == ' ' || character == '\t' || character == '\n'))
189             width += style.font.wordSpacing();
190     }
191
192     return width;
193 }
194
195 std::optional<ContentPosition> TextContentProvider::hyphenPositionBefore(ContentPosition from, ContentPosition to, ContentPosition before) const
196 {
197     auto contentLength = length();
198     if (before >= contentLength || before < from || before > to) {
199         ASSERT_NOT_REACHED();
200         return { };
201     }
202
203     auto* textItem = m_textContentIterator.current();
204     if (!textItem || !contains(before, *textItem))
205         textItem = findTextItemSlow(before);
206
207     auto fromItemPosition = from - textItem->start;
208     auto stringForHyphenLocation = StringView(textItem->text).substring(fromItemPosition, to - from);
209
210     // adjustedBefore -> ContentPosition -> ItemPosition -> run position.
211     auto adjustedBefore = before - from;
212     auto hyphenLocation = lastHyphenLocation(stringForHyphenLocation, adjustedBefore, textItem->style.locale);
213     if (!hyphenLocation)
214         return { };
215
216     return from + hyphenLocation;
217 }
218
219 unsigned TextContentProvider::length() const
220 {
221     if (!m_textContent.size())
222         return 0;
223     return m_textContent.last().end;
224 }
225
226 TextContentProvider::Iterator TextContentProvider::iterator()
227 {
228     // TODO: This is where we decide whether we need a simple or a more complex provider.
229     if (!m_simpleTextRunGenerator)
230         m_simpleTextRunGenerator = std::make_unique<SimpleTextRunGenerator>(*this);
231     else
232         m_simpleTextRunGenerator->reset();
233
234     m_simpleTextRunGenerator->findNextRun();
235     return Iterator(*this);
236 }
237
238 TextContentProvider::TextRunList TextContentProvider::textRuns()
239 {
240     TextRunList textRunList;
241
242     auto textRunIterator = iterator();
243     while (auto textRum = textRunIterator.current()) {
244         textRunList.append(*textRum);
245         ++textRunIterator;
246     }
247     return textRunList;
248 }
249
250 void TextContentProvider::findNextRun()
251 {
252     m_simpleTextRunGenerator->findNextRun();
253 }
254
255 std::optional<TextRun> TextContentProvider::current() const
256 {
257     return m_simpleTextRunGenerator->current();
258 }
259
260 }
261 }
262 #endif