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