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