Simple line layout: Add word-spacing support.
[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 "RenderBlockFlow.h"
30 #include "RenderChildIterator.h"
31 #include "SimpleLineLayoutFlowContents.h"
32
33 namespace WebCore {
34 namespace SimpleLineLayout {
35
36 TextFragmentIterator::Style::Style(const RenderStyle& style)
37     : font(style.fontCascade())
38     , textAlign(style.textAlign())
39     , collapseWhitespace(style.collapseWhiteSpace())
40     , preserveNewline(style.preserveNewline())
41     , wrapLines(style.autoWrap())
42     , breakWordOnOverflow(style.overflowWrap() == BreakOverflowWrap && (wrapLines || preserveNewline))
43     , spaceWidth(font.width(TextRun(StringView(&space, 1))))
44     , wordSpacing(font.wordSpacing())
45     , tabWidth(collapseWhitespace ? 0 : style.tabSize())
46     , locale(style.locale())
47 {
48 }
49
50 TextFragmentIterator::TextFragmentIterator(const RenderBlockFlow& flow)
51     : m_flowContents(flow)
52     , m_currentSegment(m_flowContents.begin())
53     , m_lineBreakIterator(m_currentSegment->text, flow.style().locale())
54     , m_style(flow.style())
55 {
56 }
57
58 TextFragmentIterator::TextFragment TextFragmentIterator::nextTextFragment(float xPosition)
59 {
60     TextFragmentIterator::TextFragment nextFragment = findNextTextFragment(xPosition);
61     m_atEndOfSegment = (m_currentSegment == m_flowContents.end()) || (m_position == m_currentSegment->end);
62     return nextFragment;
63 }
64
65 TextFragmentIterator::TextFragment TextFragmentIterator::findNextTextFragment(float xPosition)
66 {
67     // A fragment can either be
68     // 1. line break when <br> is present or preserveNewline is on (not considered as whitespace) or
69     // 2. whitespace (collasped, non-collapsed multi or single) or
70     // 3. non-whitespace characters.
71     // 4. content end.
72     ASSERT(m_currentSegment != m_flowContents.end());
73     unsigned startPosition = m_position;
74     if (m_atEndOfSegment)
75         ++m_currentSegment;
76
77     if (m_currentSegment == m_flowContents.end())
78         return TextFragment(startPosition, startPosition, 0, TextFragment::ContentEnd);
79     if (isHardLineBreak(m_currentSegment))
80         return TextFragment(startPosition, startPosition, 0, TextFragment::HardLineBreak);
81     if (isSoftLineBreak(startPosition)) {
82         unsigned endPosition = ++m_position;
83         return TextFragment(startPosition, endPosition, 0, TextFragment::SoftLineBreak);
84     }
85     float width = 0;
86     bool overlappingFragment = false;
87     unsigned endPosition = skipToNextPosition(PositionType::NonWhitespace, startPosition, width, xPosition, overlappingFragment);
88     unsigned segmentEndPosition = m_currentSegment->end;
89     ASSERT(startPosition <= endPosition);
90     if (startPosition < endPosition) {
91         bool multipleWhitespace = startPosition + 1 < endPosition;
92         bool isCollapsed = multipleWhitespace && m_style.collapseWhitespace;
93         bool isBreakable = !isCollapsed && multipleWhitespace;
94         m_position = endPosition;
95         return TextFragment(startPosition, endPosition, width, TextFragment::Whitespace, endPosition == segmentEndPosition, false, isCollapsed, m_style.collapseWhitespace, isBreakable);
96     }
97     endPosition = skipToNextPosition(PositionType::Breakable, startPosition, width, xPosition, overlappingFragment);
98     m_position = endPosition;
99     return TextFragment(startPosition, endPosition, width, TextFragment::NonWhitespace, endPosition == segmentEndPosition, overlappingFragment, false, false, m_style.breakWordOnOverflow);
100 }
101
102 void TextFragmentIterator::revertToEndOfFragment(const TextFragment& fragment)
103 {
104     ASSERT(m_position >= fragment.end());
105     while (m_currentSegment->start > fragment.end())
106         --m_currentSegment;
107     // TODO: It reverts to the last fragment on the same position, but that's ok for now as we don't need to
108     // differentiate multiple renderers on the same position.
109     m_position = fragment.end();
110     m_atEndOfSegment = false;
111 }
112
113 template <typename CharacterType>
114 unsigned TextFragmentIterator::nextBreakablePosition(const FlowContents::Segment& segment, unsigned startPosition)
115 {
116     ASSERT(startPosition < segment.end);
117     if (segment.text.impl() != m_lineBreakIterator.string().impl()) {
118         const String& currentText = m_lineBreakIterator.string();
119         unsigned textLength = currentText.length();
120         UChar lastCharacter = textLength > 0 ? currentText[textLength - 1] : 0;
121         UChar secondToLastCharacter = textLength > 1 ? currentText[textLength - 2] : 0;
122         m_lineBreakIterator.setPriorContext(lastCharacter, secondToLastCharacter);
123         m_lineBreakIterator.resetStringAndReleaseIterator(segment.text, m_style.locale, LineBreakIteratorModeUAX14);
124     }
125     const auto* characters = segment.text.characters<CharacterType>();
126     unsigned segmentLength = segment.end - segment.start;
127     unsigned segmentPosition = startPosition - segment.start;
128     return segment.start + nextBreakablePositionNonLoosely<CharacterType, NBSPBehavior::IgnoreNBSP>(m_lineBreakIterator, characters, segmentLength, segmentPosition);
129 }
130
131 template <typename CharacterType>
132 unsigned TextFragmentIterator::nextNonWhitespacePosition(const FlowContents::Segment& segment, unsigned startPosition)
133 {
134     ASSERT(startPosition < segment.end);
135     const auto* text = segment.text.characters<CharacterType>();
136     unsigned position = startPosition;
137     for (; position < segment.end; ++position) {
138         auto character = text[position - segment.start];
139         bool isWhitespace = character == ' ' || character == '\t' || (!m_style.preserveNewline && character == '\n');
140         if (!isWhitespace)
141             return position;
142     }
143     return position;
144 }
145
146 float TextFragmentIterator::textWidth(unsigned from, unsigned to, float xPosition) const
147 {
148     auto& segment = *m_currentSegment;
149     ASSERT(segment.start <= from && from <= segment.end && segment.start <= to && to <= segment.end);
150     ASSERT(is<RenderText>(segment.renderer));
151     if (m_style.font.isFixedPitch() || (from == segment.start && to == segment.end))
152         return downcast<RenderText>(segment.renderer).width(from - segment.start, to - from, m_style.font, xPosition, nullptr, nullptr);
153     return segment.text.is8Bit() ? runWidth<LChar>(segment, from, to, xPosition) : runWidth<UChar>(segment, from, to, xPosition);
154 }
155
156 unsigned TextFragmentIterator::skipToNextPosition(PositionType positionType, unsigned startPosition, float& width, float xPosition, bool& overlappingFragment)
157 {
158     overlappingFragment = false;
159     unsigned currentPosition = startPosition;
160     unsigned nextPosition = currentPosition;
161     // Collapsed whitespace has constant width. Do not measure it.
162     if (positionType == NonWhitespace)
163         nextPosition = m_currentSegment->text.is8Bit() ? nextNonWhitespacePosition<LChar>(*m_currentSegment, currentPosition) : nextNonWhitespacePosition<UChar>(*m_currentSegment, currentPosition);
164     else if (positionType == Breakable) {
165         nextPosition = m_currentSegment->text.is8Bit() ? nextBreakablePosition<LChar>(*m_currentSegment, currentPosition) : nextBreakablePosition<UChar>(*m_currentSegment, currentPosition);
166         // 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.
167         bool skipCurrentPosition = nextPosition == currentPosition;
168         if (skipCurrentPosition) {
169             // 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.
170             ASSERT(currentPosition < m_currentSegment->end);
171             if (currentPosition == m_currentSegment->end - 1)
172                 nextPosition = m_currentSegment->end;
173             else
174                 nextPosition = m_currentSegment->text.is8Bit() ? nextBreakablePosition<LChar>(*m_currentSegment, currentPosition + 1) : nextBreakablePosition<UChar>(*m_currentSegment, currentPosition + 1);
175         }
176         // We need to know whether the word actually finishes at the end of this renderer or not.
177         if (nextPosition == m_currentSegment->end) {
178             const auto nextSegment = m_currentSegment + 1;
179             if (nextSegment != m_flowContents.end() && !isHardLineBreak(nextSegment))
180                 overlappingFragment = nextPosition < (nextSegment->text.is8Bit() ? nextBreakablePosition<LChar>(*nextSegment, nextPosition) : nextBreakablePosition<UChar>(*nextSegment, nextPosition));
181         }
182     }
183     width = 0;
184     if (nextPosition == currentPosition)
185         return currentPosition;
186     // Both non-collapsed whitespace and non-whitespace runs need to be measured.
187     bool measureText = positionType != NonWhitespace || !m_style.collapseWhitespace;
188     if (measureText)
189         width = this->textWidth(currentPosition, nextPosition, xPosition);
190     else if (startPosition < nextPosition)
191         width = m_style.spaceWidth + m_style.wordSpacing;
192     return nextPosition;
193 }
194
195 template <typename CharacterType>
196 float TextFragmentIterator::runWidth(const FlowContents::Segment& segment, unsigned startPosition, unsigned endPosition, float xPosition) const
197 {
198     ASSERT(startPosition <= endPosition);
199     if (startPosition == endPosition)
200         return 0;
201     unsigned segmentFrom = startPosition - segment.start;
202     unsigned segmentTo = endPosition - segment.start;
203     bool measureWithEndSpace = m_style.collapseWhitespace && segmentTo < segment.text.length() && segment.text[segmentTo] == ' ';
204     if (measureWithEndSpace)
205         ++segmentTo;
206     TextRun run(StringView(segment.text).substring(segmentFrom, segmentTo - segmentFrom));
207     run.setXPos(xPosition);
208     run.setTabSize(!!m_style.tabWidth, m_style.tabWidth);
209     float width = m_style.font.width(run);
210     if (measureWithEndSpace)
211         width -= (m_style.spaceWidth + m_style.wordSpacing);
212     return std::max<float>(0, width);
213 }
214
215 }
216 }