| 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 | , 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 |  | 
|---|
| 63 | TextFragmentIterator::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 |  | 
|---|
| 71 | TextFragmentIterator::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 |  | 
|---|
| 78 | TextFragmentIterator::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 |  | 
|---|
| 114 | void 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 |  | 
|---|
| 125 | static 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 |  | 
|---|
| 144 | unsigned 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 |  | 
|---|
| 159 | unsigned 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 |  | 
|---|
| 172 | Optional<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 |  | 
|---|
| 198 | unsigned 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 |  | 
|---|
| 237 | float 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 |  | 
|---|