diff options
author | Lorry Tar Creator <lorry-tar-importer@lorry> | 2017-06-27 06:07:23 +0000 |
---|---|---|
committer | Lorry Tar Creator <lorry-tar-importer@lorry> | 2017-06-27 06:07:23 +0000 |
commit | 1bf1084f2b10c3b47fd1a588d85d21ed0eb41d0c (patch) | |
tree | 46dcd36c86e7fbc6e5df36deb463b33e9967a6f7 /Source/WebCore/rendering/SimpleLineLayoutTextFragmentIterator.cpp | |
parent | 32761a6cee1d0dee366b885b7b9c777e67885688 (diff) | |
download | WebKitGtk-tarball-master.tar.gz |
webkitgtk-2.16.5HEADwebkitgtk-2.16.5master
Diffstat (limited to 'Source/WebCore/rendering/SimpleLineLayoutTextFragmentIterator.cpp')
-rw-r--r-- | Source/WebCore/rendering/SimpleLineLayoutTextFragmentIterator.cpp | 266 |
1 files changed, 266 insertions, 0 deletions
diff --git a/Source/WebCore/rendering/SimpleLineLayoutTextFragmentIterator.cpp b/Source/WebCore/rendering/SimpleLineLayoutTextFragmentIterator.cpp new file mode 100644 index 000000000..cfd59c3ed --- /dev/null +++ b/Source/WebCore/rendering/SimpleLineLayoutTextFragmentIterator.cpp @@ -0,0 +1,266 @@ +/* + * Copyright (C) 2015 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#include "SimpleLineLayoutTextFragmentIterator.h" + +#include "Hyphenation.h" +#include "RenderBlockFlow.h" +#include "RenderChildIterator.h" +#include "SimpleLineLayoutFlowContents.h" + +namespace WebCore { +namespace SimpleLineLayout { + +TextFragmentIterator::Style::Style(const RenderStyle& style, bool useSimplifiedTextMeasuring) + : font(style.fontCascade()) + , textAlign(style.textAlign()) + , hasKerningOrLigatures(font.enableKerning() || font.requiresShaping()) + , collapseWhitespace(style.collapseWhiteSpace()) + , preserveNewline(style.preserveNewline()) + , wrapLines(style.autoWrap()) + , breakAnyWordOnOverflow(style.wordBreak() == BreakAllWordBreak && wrapLines) + , breakFirstWordOnOverflow(breakAnyWordOnOverflow || (style.breakWords() && (wrapLines || preserveNewline))) + , breakNBSP(wrapLines && style.nbspMode() == SPACE) + , keepAllWordsForCJK(style.wordBreak() == KeepAllWordBreak) + , wordSpacing(font.wordSpacing()) + , tabWidth(collapseWhitespace ? 0 : style.tabSize()) + , shouldHyphenate(style.hyphens() == HyphensAuto && canHyphenate(style.locale())) + , hyphenStringWidth(shouldHyphenate ? (useSimplifiedTextMeasuring ? font.widthForSimpleText(style.hyphenString()) : font.width(TextRun(style.hyphenString()))) : 0) + , hyphenLimitBefore(style.hyphenationLimitBefore() < 0 ? 2 : style.hyphenationLimitBefore()) + , hyphenLimitAfter(style.hyphenationLimitAfter() < 0 ? 2 : style.hyphenationLimitAfter()) + , locale(style.locale()) +{ + if (style.hyphenationLimitLines() > -1) + hyphenLimitLines = style.hyphenationLimitLines(); +} + +TextFragmentIterator::TextFragmentIterator(const RenderBlockFlow& flow) + : m_flowContents(flow) + , m_currentSegment(m_flowContents.begin()) + , m_lineBreakIterator(m_currentSegment->text, flow.style().locale()) + , m_style(flow.style(), m_currentSegment->canUseSimplifiedTextMeasuring) +{ +} + +TextFragmentIterator::TextFragment TextFragmentIterator::nextTextFragment(float xPosition) +{ + TextFragmentIterator::TextFragment nextFragment = findNextTextFragment(xPosition); + m_atEndOfSegment = (m_currentSegment == m_flowContents.end()) || (m_position == m_currentSegment->end); + return nextFragment; +} + +TextFragmentIterator::TextFragment TextFragmentIterator::findNextTextFragment(float xPosition) +{ + // A fragment can either be + // 1. line break when <br> is present or preserveNewline is on (not considered as whitespace) or + // 2. whitespace (collasped, non-collapsed multi or single) or + // 3. non-whitespace characters. + // 4. content end. + ASSERT(m_currentSegment != m_flowContents.end()); + unsigned startPosition = m_position; + if (m_atEndOfSegment) + ++m_currentSegment; + + if (m_currentSegment == m_flowContents.end()) + return TextFragment(startPosition, startPosition, 0, TextFragment::ContentEnd); + if (isHardLineBreak(m_currentSegment)) + return TextFragment(startPosition, startPosition, 0, TextFragment::HardLineBreak); + if (isSoftLineBreak(startPosition)) { + unsigned endPosition = ++m_position; + return TextFragment(startPosition, endPosition, 0, TextFragment::SoftLineBreak); + } + float width = 0; + bool overlappingFragment = false; + unsigned endPosition = skipToNextPosition(PositionType::NonWhitespace, startPosition, width, xPosition, overlappingFragment); + unsigned segmentEndPosition = m_currentSegment->end; + ASSERT(startPosition <= endPosition); + if (startPosition < endPosition) { + bool multipleWhitespace = startPosition + 1 < endPosition; + bool isCollapsed = multipleWhitespace && m_style.collapseWhitespace; + m_position = endPosition; + return TextFragment(startPosition, endPosition, width, TextFragment::Whitespace, endPosition == segmentEndPosition, false, isCollapsed, m_style.collapseWhitespace); + } + endPosition = skipToNextPosition(PositionType::Breakable, startPosition, width, xPosition, overlappingFragment); + m_position = endPosition; + return TextFragment(startPosition, endPosition, width, TextFragment::NonWhitespace, endPosition == segmentEndPosition, overlappingFragment, false, false); +} + +void TextFragmentIterator::revertToEndOfFragment(const TextFragment& fragment) +{ + ASSERT(m_position >= fragment.end()); + while (m_currentSegment->start > fragment.end()) + --m_currentSegment; + // TODO: It reverts to the last fragment on the same position, but that's ok for now as we don't need to + // differentiate multiple renderers on the same position. + m_position = fragment.end(); + m_atEndOfSegment = false; +} + +static inline unsigned nextBreakablePositionInSegment(LazyLineBreakIterator& lineBreakIterator, unsigned startPosition, bool breakNBSP, bool keepAllWordsForCJK) +{ + if (keepAllWordsForCJK) { + if (breakNBSP) + return nextBreakablePositionKeepingAllWords(lineBreakIterator, startPosition); + return nextBreakablePositionKeepingAllWordsIgnoringNBSP(lineBreakIterator, startPosition); + } + + if (lineBreakIterator.mode() == LineBreakIteratorMode::Default) { + if (breakNBSP) + return WebCore::nextBreakablePosition(lineBreakIterator, startPosition); + return nextBreakablePositionIgnoringNBSP(lineBreakIterator, startPosition); + } + + if (breakNBSP) + return nextBreakablePositionWithoutShortcut(lineBreakIterator, startPosition); + return nextBreakablePositionIgnoringNBSPWithoutShortcut(lineBreakIterator, startPosition); +} + +unsigned TextFragmentIterator::nextBreakablePosition(const FlowContents::Segment& segment, unsigned startPosition) +{ + ASSERT(startPosition < segment.end); + StringView currentText = m_lineBreakIterator.stringView(); + StringView segmentText = StringView(segment.text); + if (segmentText != currentText) { + unsigned textLength = currentText.length(); + UChar lastCharacter = textLength > 0 ? currentText[textLength - 1] : 0; + UChar secondToLastCharacter = textLength > 1 ? currentText[textLength - 2] : 0; + m_lineBreakIterator.setPriorContext(lastCharacter, secondToLastCharacter); + m_lineBreakIterator.resetStringAndReleaseIterator(segment.text, m_style.locale, LineBreakIteratorMode::Default); + } + return segment.toRenderPosition(nextBreakablePositionInSegment(m_lineBreakIterator, segment.toSegmentPosition(startPosition), m_style.breakNBSP, m_style.keepAllWordsForCJK)); +} + +unsigned TextFragmentIterator::nextNonWhitespacePosition(const FlowContents::Segment& segment, unsigned startPosition) +{ + ASSERT(startPosition < segment.end); + unsigned position = startPosition; + for (; position < segment.end; ++position) { + auto character = segment.text[segment.toSegmentPosition(position)]; + bool isWhitespace = character == ' ' || character == '\t' || (!m_style.preserveNewline && character == '\n'); + if (!isWhitespace) + return position; + } + return position; +} + +std::optional<unsigned> TextFragmentIterator::lastHyphenPosition(const TextFragmentIterator::TextFragment& run, unsigned before) const +{ + ASSERT(run.start() < before); + auto& segment = *m_currentSegment; + ASSERT(segment.start <= before && before <= segment.end); + ASSERT(is<RenderText>(segment.renderer)); + if (!m_style.shouldHyphenate || run.type() != TextFragment::NonWhitespace) + return std::nullopt; + // Check if there are enough characters in the run. + unsigned runLength = run.end() - run.start(); + if (m_style.hyphenLimitBefore >= runLength || m_style.hyphenLimitAfter >= runLength || m_style.hyphenLimitBefore + m_style.hyphenLimitAfter > runLength) + return std::nullopt; + auto runStart = segment.toSegmentPosition(run.start()); + auto beforeIndex = segment.toSegmentPosition(before) - runStart; + if (beforeIndex <= m_style.hyphenLimitBefore) + return std::nullopt; + // Adjust before index to accommodate the limit-after value (this is the last potential hyphen location). + beforeIndex = std::min(beforeIndex, runLength - m_style.hyphenLimitAfter + 1); + auto substringForHyphenation = StringView(segment.text).substring(runStart, run.end() - run.start()); + auto hyphenLocation = lastHyphenLocation(substringForHyphenation, beforeIndex, m_style.locale); + // Check if there are enough characters before and after the hyphen. + if (hyphenLocation && hyphenLocation >= m_style.hyphenLimitBefore && m_style.hyphenLimitAfter <= (runLength - hyphenLocation)) + return segment.toRenderPosition(hyphenLocation + runStart); + return std::nullopt; +} + +unsigned TextFragmentIterator::skipToNextPosition(PositionType positionType, unsigned startPosition, float& width, float xPosition, bool& overlappingFragment) +{ + overlappingFragment = false; + unsigned currentPosition = startPosition; + unsigned nextPosition = currentPosition; + // Collapsed whitespace has constant width. Do not measure it. + if (positionType == NonWhitespace) + nextPosition = nextNonWhitespacePosition(*m_currentSegment, currentPosition); + else if (positionType == Breakable) { + nextPosition = nextBreakablePosition(*m_currentSegment, currentPosition); + // 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. + bool skipCurrentPosition = nextPosition == currentPosition; + if (skipCurrentPosition) { + // 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. + ASSERT(currentPosition < m_currentSegment->end); + if (currentPosition == m_currentSegment->end - 1) + nextPosition = m_currentSegment->end; + else + nextPosition = nextBreakablePosition(*m_currentSegment, currentPosition + 1); + } + // We need to know whether the word actually finishes at the end of this renderer or not. + if (nextPosition == m_currentSegment->end) { + const auto nextSegment = m_currentSegment + 1; + if (nextSegment != m_flowContents.end() && !isHardLineBreak(nextSegment)) + overlappingFragment = nextPosition < nextBreakablePosition(*nextSegment, nextPosition); + } + } + width = 0; + if (nextPosition == currentPosition) + return currentPosition; + // Both non-collapsed whitespace and non-whitespace runs need to be measured. + bool measureText = positionType != NonWhitespace || !m_style.collapseWhitespace; + if (measureText) + width = this->textWidth(currentPosition, nextPosition, xPosition); + else if (startPosition < nextPosition) + width = m_style.font.spaceWidth() + m_style.wordSpacing; + return nextPosition; +} + +float TextFragmentIterator::textWidth(unsigned from, unsigned to, float xPosition) const +{ + auto& segment = *m_currentSegment; + ASSERT(segment.start <= from && from <= segment.end && segment.start <= to && to <= segment.end); + ASSERT(is<RenderText>(segment.renderer)); + if (!m_style.font.size() || from == to) + return 0; + + unsigned segmentFrom = segment.toSegmentPosition(from); + unsigned segmentTo = segment.toSegmentPosition(to); + if (m_style.font.isFixedPitch()) + return downcast<RenderText>(segment.renderer).width(segmentFrom, segmentTo - segmentFrom, m_style.font, xPosition, nullptr, nullptr); + + bool measureWithEndSpace = m_style.hasKerningOrLigatures && m_style.collapseWhitespace + && segmentTo < segment.text.length() && segment.text[segmentTo] == ' '; + if (measureWithEndSpace) + ++segmentTo; + float width = 0; + if (segment.canUseSimplifiedTextMeasuring) + width = m_style.font.widthForSimpleText(StringView(segment.text).substring(segmentFrom, segmentTo - segmentFrom)); + else { + TextRun run(StringView(segment.text).substring(segmentFrom, segmentTo - segmentFrom), xPosition); + if (m_style.tabWidth) + run.setTabSize(true, m_style.tabWidth); + width = m_style.font.width(run); + } + if (measureWithEndSpace) + width -= (m_style.font.spaceWidth() + m_style.wordSpacing); + return std::max<float>(0, width); +} + +} +} |