| /* |
| * Copyright (C) 2018 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 "InlineRunProvider.h" |
| |
| #if ENABLE(LAYOUT_FORMATTING_CONTEXT) |
| |
| #include "BreakLines.h" |
| #include "LayoutInlineBox.h" |
| #include <wtf/IsoMallocInlines.h> |
| |
| namespace WebCore { |
| namespace Layout { |
| |
| WTF_MAKE_ISO_ALLOCATED_IMPL(InlineRunProvider); |
| |
| InlineRunProvider::InlineRunProvider() |
| { |
| } |
| |
| void InlineRunProvider::append(const InlineItem& inlineItem) |
| { |
| switch (inlineItem.type()) { |
| case InlineItem::Type::Text: |
| // Special case text content. They can overlap multiple items. <span>foo</span><span>bar</span> |
| processInlineTextItem(inlineItem); |
| break; |
| case InlineItem::Type::HardLineBreak: |
| m_inlineRuns.append(InlineRunProvider::Run::createHardLineBreakRun(inlineItem)); |
| break; |
| case InlineItem::Type::InlineBox: |
| m_inlineRuns.append(InlineRunProvider::Run::createBoxRun(inlineItem)); |
| break; |
| case InlineItem::Type::Float: |
| m_inlineRuns.append(InlineRunProvider::Run::createFloatRun(inlineItem)); |
| break; |
| default: |
| ASSERT_NOT_IMPLEMENTED_YET(); |
| } |
| } |
| |
| void InlineRunProvider::insertBefore(const Box&, const Box&) |
| { |
| } |
| |
| void InlineRunProvider::remove(const Box&) |
| { |
| } |
| |
| static inline bool isWhitespace(char character, bool preserveNewline) |
| { |
| return character == ' ' || character == '\t' || (character == '\n' && !preserveNewline); |
| } |
| |
| static inline bool isSoftLineBreak(char character, bool preserveNewline) |
| { |
| return preserveNewline && character == '\n'; |
| } |
| |
| bool InlineRunProvider::isContinousContent(InlineRunProvider::Run::Type newRunType, const InlineItem& newInlineItem) |
| { |
| if (m_inlineRuns.isEmpty()) |
| return false; |
| |
| auto& lastRun = m_inlineRuns.last(); |
| // Same element, check type only. |
| if (&newInlineItem == &lastRun.inlineItem()) |
| return newRunType == lastRun.type(); |
| |
| // This new run is from a different inline box. |
| // FIXME: check style. |
| if (newRunType == InlineRunProvider::Run::Type::NonWhitespace && lastRun.isNonWhitespace()) |
| return true; |
| |
| if (newRunType == InlineRunProvider::Run::Type::Whitespace && lastRun.isWhitespace()) |
| return newInlineItem.style().collapseWhiteSpace() == lastRun.style().collapseWhiteSpace(); |
| |
| return false; |
| } |
| |
| void InlineRunProvider::processInlineTextItem(const InlineItem& inlineItem) |
| { |
| // We need to reset the run iterator when the text content is not continuous. |
| // <span>foo</span><img src=""><span>bar</span> (FIXME: floats?) |
| if (!m_inlineRuns.isEmpty() && !m_inlineRuns.last().isText()) { |
| m_lineBreakIterator.resetPriorContext(); |
| m_lineBreakIterator.resetStringAndReleaseIterator("", "", LineBreakIteratorMode::Default); |
| } |
| |
| auto& style = inlineItem.style(); |
| auto text = inlineItem.textContent(); |
| ItemPosition currentItemPosition = 0; |
| while (currentItemPosition < text.length()) { |
| |
| // Soft linebreak? |
| if (isSoftLineBreak(text[currentItemPosition], style.preserveNewline())) { |
| m_inlineRuns.append(InlineRunProvider::Run::createSoftLineBreakRun(inlineItem)); |
| ++currentItemPosition; |
| continue; |
| } |
| |
| auto isWhitespaceRun = isWhitespace(text[currentItemPosition], style.preserveNewline()); |
| auto length = isWhitespaceRun ? moveToNextNonWhitespacePosition(inlineItem, currentItemPosition) : moveToNextBreakablePosition(inlineItem, currentItemPosition); |
| |
| if (isContinousContent(isWhitespaceRun ? InlineRunProvider::Run::Type::Whitespace : InlineRunProvider::Run::Type::NonWhitespace, inlineItem)) |
| m_inlineRuns.last().textContext()->expand(length); |
| else { |
| m_inlineRuns.append(isWhitespaceRun ? InlineRunProvider::Run::createWhitespaceRun(inlineItem, currentItemPosition, length, style.collapseWhiteSpace()) |
| : InlineRunProvider::Run::createNonWhitespaceRun(inlineItem, currentItemPosition, length)); |
| } |
| |
| currentItemPosition += length; |
| } |
| } |
| |
| unsigned InlineRunProvider::moveToNextNonWhitespacePosition(const InlineItem& inlineItem, ItemPosition currentItemPosition) |
| { |
| auto text = inlineItem.textContent(); |
| auto preserveNewline = inlineItem.style().preserveNewline(); |
| auto nextNonWhiteSpacePosition = currentItemPosition; |
| |
| while (nextNonWhiteSpacePosition < text.length() && isWhitespace(text[nextNonWhiteSpacePosition], preserveNewline)) |
| ++nextNonWhiteSpacePosition; |
| return nextNonWhiteSpacePosition - currentItemPosition; |
| } |
| |
| unsigned InlineRunProvider::moveToNextBreakablePosition(const InlineItem& inlineItem, ItemPosition currentItemPosition) |
| { |
| auto findNextBreakablePosition = [&](auto inlineText, auto& style, ItemPosition startPosition) { |
| // Swap iterator's content if we advanced to a new string. |
| auto iteratorText = m_lineBreakIterator.stringView(); |
| |
| if (iteratorText != inlineText) { |
| auto textLength = iteratorText.length(); |
| auto lastCharacter = textLength > 0 ? iteratorText[textLength - 1] : 0; |
| auto secondToLastCharacter = textLength > 1 ? iteratorText[textLength - 2] : 0; |
| m_lineBreakIterator.setPriorContext(lastCharacter, secondToLastCharacter); |
| m_lineBreakIterator.resetStringAndReleaseIterator(inlineText, style.locale(), LineBreakIteratorMode::Default); |
| } |
| |
| auto keepAllWordsForCJK = style.wordBreak() == WordBreak::KeepAll; |
| auto breakNBSP = style.autoWrap() && style.nbspMode() == NBSPMode::Space; |
| |
| if (keepAllWordsForCJK) { |
| if (breakNBSP) |
| return nextBreakablePositionKeepingAllWords(m_lineBreakIterator, startPosition); |
| return nextBreakablePositionKeepingAllWordsIgnoringNBSP(m_lineBreakIterator, startPosition); |
| } |
| |
| if (m_lineBreakIterator.mode() == LineBreakIteratorMode::Default) { |
| if (breakNBSP) |
| return WebCore::nextBreakablePosition(m_lineBreakIterator, startPosition); |
| return nextBreakablePositionIgnoringNBSP(m_lineBreakIterator, startPosition); |
| } |
| |
| if (breakNBSP) |
| return nextBreakablePositionWithoutShortcut(m_lineBreakIterator, startPosition); |
| return nextBreakablePositionIgnoringNBSPWithoutShortcut(m_lineBreakIterator, startPosition); |
| }; |
| |
| auto& style = inlineItem.style(); |
| auto textLength = inlineItem.textContent().length(); |
| ASSERT(textLength); |
| while (currentItemPosition < textLength - 1) { |
| auto nextBreakablePosition = findNextBreakablePosition(inlineItem.textContent(), style, currentItemPosition); |
| if (nextBreakablePosition != currentItemPosition) |
| return nextBreakablePosition - currentItemPosition; |
| ++currentItemPosition; |
| } |
| return textLength; |
| } |
| |
| } |
| } |
| #endif |