Simple line layout: Small tweaks to improve performance.
[WebKit-https.git] / Source / WebCore / rendering / SimpleLineLayoutTextFragmentIterator.cpp
1 /*
2  * Copyright (C) 2015 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 "SimpleLineLayoutTextFragmentIterator.h"
28
29 #include "Hyphenation.h"
30 #include "RenderBlockFlow.h"
31 #include "RenderChildIterator.h"
32 #include "SimpleLineLayoutFlowContents.h"
33
34 namespace WebCore {
35 namespace SimpleLineLayout {
36
37 TextFragmentIterator::Style::Style(const RenderStyle& style)
38     : font(style.fontCascade())
39     , textAlign(style.textAlign())
40     , collapseWhitespace(style.collapseWhiteSpace())
41     , preserveNewline(style.preserveNewline())
42     , wrapLines(style.autoWrap())
43     , breakAnyWordOnOverflow(style.wordBreak() == BreakAllWordBreak && wrapLines)
44     , breakFirstWordOnOverflow(breakAnyWordOnOverflow || (style.breakWords() && (wrapLines || preserveNewline)))
45     , breakNBSP(wrapLines && style.nbspMode() == SPACE)
46     , keepAllWordsForCJK(style.wordBreak() == KeepAllWordBreak)
47     , spaceWidth(font.width(TextRun(StringView(&space, 1))))
48     , wordSpacing(font.wordSpacing())
49     , tabWidth(collapseWhitespace ? 0 : style.tabSize())
50     , shouldHyphenate(style.hyphens() == HyphensAuto && canHyphenate(style.locale()))
51     , hyphenStringWidth(shouldHyphenate ? font.width(TextRun(style.hyphenString())) : 0)
52     , hyphenLimitBefore(style.hyphenationLimitBefore() < 0 ? 2 : style.hyphenationLimitBefore())
53     , hyphenLimitAfter(style.hyphenationLimitAfter() < 0 ? 2 : style.hyphenationLimitAfter())
54     , locale(style.locale())
55 {
56     if (style.hyphenationLimitLines() > -1)
57         hyphenLimitLines = style.hyphenationLimitLines();
58 }
59
60 TextFragmentIterator::TextFragmentIterator(const RenderBlockFlow& flow)
61     : m_flowContents(flow)
62     , m_currentSegment(m_flowContents.begin())
63     , m_lineBreakIterator(m_currentSegment->text, flow.style().locale())
64     , m_style(flow.style())
65 {
66 }
67
68 TextFragmentIterator::TextFragment TextFragmentIterator::nextTextFragment(float xPosition)
69 {
70     TextFragmentIterator::TextFragment nextFragment = findNextTextFragment(xPosition);
71     m_atEndOfSegment = (m_currentSegment == m_flowContents.end()) || (m_position == m_currentSegment->end);
72     return nextFragment;
73 }
74
75 TextFragmentIterator::TextFragment TextFragmentIterator::findNextTextFragment(float xPosition)
76 {
77     // A fragment can either be
78     // 1. line break when <br> is present or preserveNewline is on (not considered as whitespace) or
79     // 2. whitespace (collasped, non-collapsed multi or single) or
80     // 3. non-whitespace characters.
81     // 4. content end.
82     ASSERT(m_currentSegment != m_flowContents.end());
83     unsigned startPosition = m_position;
84     if (m_atEndOfSegment)
85         ++m_currentSegment;
86
87     if (m_currentSegment == m_flowContents.end())
88         return TextFragment(startPosition, startPosition, 0, TextFragment::ContentEnd);
89     if (isHardLineBreak(m_currentSegment))
90         return TextFragment(startPosition, startPosition, 0, TextFragment::HardLineBreak);
91     if (isSoftLineBreak(startPosition)) {
92         unsigned endPosition = ++m_position;
93         return TextFragment(startPosition, endPosition, 0, TextFragment::SoftLineBreak);
94     }
95     float width = 0;
96     bool overlappingFragment = false;
97     unsigned endPosition = skipToNextPosition(PositionType::NonWhitespace, startPosition, width, xPosition, overlappingFragment);
98     unsigned segmentEndPosition = m_currentSegment->end;
99     ASSERT(startPosition <= endPosition);
100     if (startPosition < endPosition) {
101         bool multipleWhitespace = startPosition + 1 < endPosition;
102         bool isCollapsed = multipleWhitespace && m_style.collapseWhitespace;
103         m_position = endPosition;
104         return TextFragment(startPosition, endPosition, width, TextFragment::Whitespace, endPosition == segmentEndPosition, false, isCollapsed, m_style.collapseWhitespace);
105     }
106     endPosition = skipToNextPosition(PositionType::Breakable, startPosition, width, xPosition, overlappingFragment);
107     m_position = endPosition;
108     return TextFragment(startPosition, endPosition, width, TextFragment::NonWhitespace, endPosition == segmentEndPosition, overlappingFragment, false, false);
109 }
110
111 void TextFragmentIterator::revertToEndOfFragment(const TextFragment& fragment)
112 {
113     ASSERT(m_position >= fragment.end());
114     while (m_currentSegment->start > fragment.end())
115         --m_currentSegment;
116     // TODO: It reverts to the last fragment on the same position, but that's ok for now as we don't need to
117     // differentiate multiple renderers on the same position.
118     m_position = fragment.end();
119     m_atEndOfSegment = false;
120 }
121
122 static unsigned nextBreakablePositionInSegment(LazyLineBreakIterator& lineBreakIterator, unsigned startPosition, bool breakNBSP, bool keepAllWordsForCJK)
123 {
124     if (keepAllWordsForCJK) {
125         if (breakNBSP)
126             return nextBreakablePositionKeepingAllWords(lineBreakIterator, startPosition);
127         return nextBreakablePositionKeepingAllWordsIgnoringNBSP(lineBreakIterator, startPosition);
128     }
129
130     if (lineBreakIterator.isLooseCJKMode()) {
131         if (breakNBSP)
132             return nextBreakablePositionLoose(lineBreakIterator, startPosition);
133         return nextBreakablePositionIgnoringNBSPLoose(lineBreakIterator, startPosition);
134     }
135         
136     if (breakNBSP)
137         return WebCore::nextBreakablePosition(lineBreakIterator, startPosition);
138     return nextBreakablePositionIgnoringNBSP(lineBreakIterator, startPosition);
139 }
140
141 unsigned TextFragmentIterator::nextBreakablePosition(const FlowContents::Segment& segment, unsigned startPosition)
142 {
143     ASSERT(startPosition < segment.end);
144     StringView currentText = m_lineBreakIterator.stringView();
145     StringView segmentText = StringView(segment.text);
146     if (segmentText != currentText) {
147         unsigned textLength = currentText.length();
148         UChar lastCharacter = textLength > 0 ? currentText[textLength - 1] : 0;
149         UChar secondToLastCharacter = textLength > 1 ? currentText[textLength - 2] : 0;
150         m_lineBreakIterator.setPriorContext(lastCharacter, secondToLastCharacter);
151         m_lineBreakIterator.resetStringAndReleaseIterator(segment.text, m_style.locale, LineBreakIteratorMode::Default);
152     }
153     return segment.toRenderPosition(nextBreakablePositionInSegment(m_lineBreakIterator, segment.toSegmentPosition(startPosition), m_style.breakNBSP, m_style.keepAllWordsForCJK));
154 }
155
156 unsigned TextFragmentIterator::nextNonWhitespacePosition(const FlowContents::Segment& segment, unsigned startPosition)
157 {
158     ASSERT(startPosition < segment.end);
159     unsigned position = startPosition;
160     for (; position < segment.end; ++position) {
161         auto character = segment.text[segment.toSegmentPosition(position)];
162         bool isWhitespace = character == ' ' || character == '\t' || (!m_style.preserveNewline && character == '\n');
163         if (!isWhitespace)
164             return position;
165     }
166     return position;
167 }
168
169 float TextFragmentIterator::textWidth(unsigned from, unsigned to, float xPosition) const
170 {
171     auto& segment = *m_currentSegment;
172     ASSERT(segment.start <= from && from <= segment.end && segment.start <= to && to <= segment.end);
173     ASSERT(is<RenderText>(segment.renderer));
174     if (!m_style.font.size())
175         return 0;
176     if (m_style.font.isFixedPitch() || (from == segment.start && to == segment.end))
177         return downcast<RenderText>(segment.renderer).width(segment.toSegmentPosition(from), to - from, m_style.font, xPosition, nullptr, nullptr);
178     return runWidth(segment, from, to, xPosition);
179 }
180
181 std::optional<unsigned> TextFragmentIterator::lastHyphenPosition(const TextFragmentIterator::TextFragment& run, unsigned before) const
182 {
183     ASSERT(run.start() < before);
184     auto& segment = *m_currentSegment;
185     ASSERT(segment.start <= before && before <= segment.end);
186     ASSERT(is<RenderText>(segment.renderer));
187     if (!m_style.shouldHyphenate || run.type() != TextFragment::NonWhitespace)
188         return std::nullopt;
189     // Check if there are enough characters in the run.
190     unsigned runLength = run.end() - run.start();
191     if (m_style.hyphenLimitBefore >= runLength || m_style.hyphenLimitAfter >= runLength || m_style.hyphenLimitBefore + m_style.hyphenLimitAfter > runLength)
192         return std::nullopt;
193     auto runStart = segment.toSegmentPosition(run.start());
194     auto beforeIndex = segment.toSegmentPosition(before) - runStart;
195     if (beforeIndex <= m_style.hyphenLimitBefore)
196         return std::nullopt;
197     // Adjust before index to accommodate the limit-after value (this is the last potential hyphen location).
198     beforeIndex = std::min(beforeIndex, runLength - m_style.hyphenLimitAfter + 1);
199     auto substringForHyphenation = StringView(segment.text).substring(runStart, run.end() - run.start());
200     auto hyphenLocation = lastHyphenLocation(substringForHyphenation, beforeIndex, m_style.locale);
201     // Check if there are enough characters before and after the hyphen.
202     if (hyphenLocation && hyphenLocation >= m_style.hyphenLimitBefore && m_style.hyphenLimitAfter <= (runLength - hyphenLocation))
203         return segment.toRenderPosition(hyphenLocation + runStart);
204     return std::nullopt;
205 }
206
207 unsigned TextFragmentIterator::skipToNextPosition(PositionType positionType, unsigned startPosition, float& width, float xPosition, bool& overlappingFragment)
208 {
209     overlappingFragment = false;
210     unsigned currentPosition = startPosition;
211     unsigned nextPosition = currentPosition;
212     // Collapsed whitespace has constant width. Do not measure it.
213     if (positionType == NonWhitespace)
214         nextPosition = nextNonWhitespacePosition(*m_currentSegment, currentPosition);
215     else if (positionType == Breakable) {
216         nextPosition = nextBreakablePosition(*m_currentSegment, currentPosition);
217         // nextBreakablePosition returns the same position for certain characters such as hyphens. Call next again with modified position unless we are at the end of the segment.
218         bool skipCurrentPosition = nextPosition == currentPosition;
219         if (skipCurrentPosition) {
220             // When we are skipping the last character in the segment, just move to the end of the segment and we'll check the next segment whether it is an overlapping fragment.
221             ASSERT(currentPosition < m_currentSegment->end);
222             if (currentPosition == m_currentSegment->end - 1)
223                 nextPosition = m_currentSegment->end;
224             else
225                 nextPosition = nextBreakablePosition(*m_currentSegment, currentPosition + 1);
226         }
227         // We need to know whether the word actually finishes at the end of this renderer or not.
228         if (nextPosition == m_currentSegment->end) {
229             const auto nextSegment = m_currentSegment + 1;
230             if (nextSegment != m_flowContents.end() && !isHardLineBreak(nextSegment))
231                 overlappingFragment = nextPosition < nextBreakablePosition(*nextSegment, nextPosition);
232         }
233     }
234     width = 0;
235     if (nextPosition == currentPosition)
236         return currentPosition;
237     // Both non-collapsed whitespace and non-whitespace runs need to be measured.
238     bool measureText = positionType != NonWhitespace || !m_style.collapseWhitespace;
239     if (measureText)
240         width = this->textWidth(currentPosition, nextPosition, xPosition);
241     else if (startPosition < nextPosition)
242         width = m_style.spaceWidth + m_style.wordSpacing;
243     return nextPosition;
244 }
245
246 float TextFragmentIterator::runWidth(const FlowContents::Segment& segment, unsigned startPosition, unsigned endPosition, float xPosition) const
247 {
248     ASSERT(m_style.font.size());
249     ASSERT(startPosition <= endPosition);
250     if (startPosition == endPosition)
251         return 0;
252     unsigned segmentFrom = segment.toSegmentPosition(startPosition);
253     unsigned segmentTo = segment.toSegmentPosition(endPosition);
254     bool measureWithEndSpace = m_style.collapseWhitespace && segmentTo < segment.text.length() && segment.text[segmentTo] == ' ';
255     if (measureWithEndSpace)
256         ++segmentTo;
257     TextRun run(StringView(segment.text).substring(segmentFrom, segmentTo - segmentFrom));
258     run.setXPos(xPosition);
259     run.setTabSize(!!m_style.tabWidth, m_style.tabWidth);
260     float width = m_style.font.width(run);
261     if (measureWithEndSpace)
262         width -= (m_style.spaceWidth + m_style.wordSpacing);
263     return std::max<float>(0, width);
264 }
265
266 }
267 }