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