blob: 9eb20f0afd712b536201d06f7cb4375e8b1e4cd9 [file] [log] [blame]
/*
* 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