blob: 0a0be57bdf1fbb5d67b4abf4fdc5e8c8feec0624 [file] [log] [blame]
/*
* Copyright (C) 2019 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 "LayoutIntegrationLineLayout.h"
#if ENABLE(LAYOUT_FORMATTING_CONTEXT)
#include "DisplayBox.h"
#include "EventRegion.h"
#include "HitTestLocation.h"
#include "HitTestRequest.h"
#include "HitTestResult.h"
#include "InlineFormattingState.h"
#include "InvalidationState.h"
#include "LayoutContext.h"
#include "LayoutTreeBuilder.h"
#include "PaintInfo.h"
#include "RenderBlockFlow.h"
#include "RenderChildIterator.h"
#include "RenderLineBreak.h"
#include "RuntimeEnabledFeatures.h"
#include "Settings.h"
#include "SimpleLineLayout.h"
#include "TextDecorationPainter.h"
#include "TextPainter.h"
namespace WebCore {
namespace LayoutIntegration {
LineLayout::LineLayout(const RenderBlockFlow& flow)
: m_flow(flow)
{
m_treeContent = Layout::TreeBuilder::buildLayoutTreeForIntegration(flow);
}
LineLayout::~LineLayout() = default;
bool LineLayout::canUseFor(const RenderBlockFlow& flow)
{
if (!RuntimeEnabledFeatures::sharedFeatures().layoutFormattingContextIntegrationEnabled())
return false;
// Initially only a subset of SLL features is supported.
if (!SimpleLineLayout::canUseFor(flow))
return false;
if (flow.containsFloats())
return false;
if (flow.fragmentedFlowState() != RenderObject::NotInsideFragmentedFlow)
return false;
return true;
}
void LineLayout::updateStyle()
{
auto& root = rootLayoutBox();
// FIXME: Encapsulate style updates better.
root.updateStyle(m_flow.style());
for (auto* child = root.firstChild(); child; child = child->nextSibling()) {
if (child->isAnonymous())
child->updateStyle(RenderStyle::createAnonymousStyleWithDisplay(root.style(), DisplayType::Inline));
}
}
void LineLayout::layout()
{
if (!m_layoutState)
m_layoutState = makeUnique<Layout::LayoutState>(*m_treeContent);
prepareRootGeometryForLayout();
auto layoutContext = Layout::LayoutContext { *m_layoutState };
auto invalidationState = Layout::InvalidationState { };
layoutContext.layoutWithPreparedRootGeometry(invalidationState);
auto& lineBoxes = downcast<Layout::InlineFormattingState>(m_layoutState->establishedFormattingState(rootLayoutBox())).displayInlineContent()->lineBoxes;
m_contentLogicalHeight = lineBoxes.last().logicalBottom() - lineBoxes.first().logicalTop();
}
void LineLayout::prepareRootGeometryForLayout()
{
auto& displayBox = m_layoutState->displayBoxForRootLayoutBox();
m_layoutState->setViewportSize(m_flow.frame().view()->size());
// Don't set marging properties or height. These should not be be accessed by inline layout.
displayBox.setBorder(Layout::Edges { { m_flow.borderStart(), m_flow.borderEnd() }, { m_flow.borderBefore(), m_flow.borderAfter() } });
displayBox.setPadding(Layout::Edges { { m_flow.paddingStart(), m_flow.paddingEnd() }, { m_flow.paddingBefore(), m_flow.paddingAfter() } });
displayBox.setContentBoxWidth(m_flow.contentSize().width());
}
size_t LineLayout::lineCount() const
{
auto* inlineContent = displayInlineContent();
if (!inlineContent)
return 0;
if (inlineContent->runs.isEmpty())
return 0;
return inlineContent->lineBoxes.size();
}
LayoutUnit LineLayout::firstLineBaseline() const
{
auto* inlineContent = displayInlineContent();
if (!inlineContent) {
ASSERT_NOT_REACHED();
return 0_lu;
}
auto& firstLineBox = inlineContent->lineBoxes.first();
return Layout::toLayoutUnit(firstLineBox.logicalTop() + firstLineBox.baselineOffset());
}
LayoutUnit LineLayout::lastLineBaseline() const
{
auto* inlineContent = displayInlineContent();
if (!inlineContent) {
ASSERT_NOT_REACHED();
return 0_lu;
}
auto& lastLineBox = inlineContent->lineBoxes.last();
return Layout::toLayoutUnit(lastLineBox.logicalTop() + lastLineBox.baselineOffset());
}
void LineLayout::collectOverflow(RenderBlockFlow& flow)
{
ASSERT(&flow == &m_flow);
ASSERT(!flow.hasOverflowClip());
for (auto& lineBox : displayInlineContent()->lineBoxes) {
flow.addLayoutOverflow(Layout::toLayoutRect(lineBox.scrollableOverflow()));
flow.addVisualOverflow(Layout::toLayoutRect(lineBox.inkOverflow()));
}
}
const Display::InlineContent* LineLayout::displayInlineContent() const
{
return downcast<Layout::InlineFormattingState>(m_layoutState->establishedFormattingState(rootLayoutBox())).displayInlineContent();
}
LineLayoutTraversal::TextBoxIterator LineLayout::textBoxesFor(const RenderText& renderText) const
{
auto* inlineContent = displayInlineContent();
if (!inlineContent)
return { };
auto* layoutBox = m_treeContent->layoutBoxForRenderer(renderText);
ASSERT(layoutBox);
Optional<size_t> firstIndex;
size_t lastIndex = 0;
for (size_t i = 0; i < inlineContent->runs.size(); ++i) {
auto& run = inlineContent->runs[i];
if (&run.layoutBox() == layoutBox) {
if (!firstIndex)
firstIndex = i;
lastIndex = i;
} else if (firstIndex)
break;
}
if (!firstIndex)
return { };
return { LineLayoutTraversal::DisplayRunPath(*inlineContent, *firstIndex, lastIndex + 1) };
}
LineLayoutTraversal::ElementBoxIterator LineLayout::elementBoxFor(const RenderLineBreak& renderLineBreak) const
{
auto* inlineContent = displayInlineContent();
if (!inlineContent)
return { };
auto* layoutBox = m_treeContent->layoutBoxForRenderer(renderLineBreak);
ASSERT(layoutBox);
for (size_t i = 0; i < inlineContent->runs.size(); ++i) {
auto& run = inlineContent->runs[i];
if (&run.layoutBox() == layoutBox)
return { LineLayoutTraversal::DisplayRunPath(*inlineContent, i, i + 1) };
}
return { };
}
const Layout::Container& LineLayout::rootLayoutBox() const
{
return m_treeContent->rootLayoutBox();
}
Layout::Container& LineLayout::rootLayoutBox()
{
return m_treeContent->rootLayoutBox();
}
void LineLayout::paint(PaintInfo& paintInfo, const LayoutPoint& paintOffset)
{
if (!displayInlineContent())
return;
if (paintInfo.phase != PaintPhase::Foreground && paintInfo.phase != PaintPhase::EventRegion)
return;
auto& inlineContent = *displayInlineContent();
float deviceScaleFactor = m_flow.document().deviceScaleFactor();
auto paintRect = paintInfo.rect;
paintRect.moveBy(-paintOffset);
for (auto& run : inlineContent.runsForRect(paintRect)) {
if (!run.textContext())
continue;
auto& textContext = *run.textContext();
if (!textContext.length())
continue;
auto& style = run.style();
if (style.visibility() != Visibility::Visible)
continue;
auto rect = FloatRect { run.logicalRect() };
auto visualOverflowRect = FloatRect { run.inkOverflow() };
if (paintRect.y() > visualOverflowRect.maxY() || paintRect.maxY() < visualOverflowRect.y())
continue;
if (paintInfo.eventRegionContext) {
if (style.pointerEvents() != PointerEvents::None)
paintInfo.eventRegionContext->unite(enclosingIntRect(visualOverflowRect), style);
continue;
}
auto& lineBox = inlineContent.lineBoxForRun(run);
auto baselineOffset = paintOffset.y() + lineBox.logicalTop() + lineBox.baselineOffset();
auto behavior = textContext.expansion() ? textContext.expansion()->behavior : DefaultExpansion;
auto horizontalExpansion = textContext.expansion() ? textContext.expansion()->horizontalExpansion : 0;
String textWithHyphen;
if (textContext.needsHyphen())
textWithHyphen = makeString(textContext.content(), style.hyphenString());
TextRun textRun { !textWithHyphen.isEmpty() ? textWithHyphen : textContext.content(), run.logicalLeft() - lineBox.logicalLeft(), horizontalExpansion, behavior };
textRun.setTabSize(!style.collapseWhiteSpace(), style.tabSize());
FloatPoint textOrigin { rect.x() + paintOffset.x(), roundToDevicePixel(baselineOffset, deviceScaleFactor) };
TextPainter textPainter(paintInfo.context());
textPainter.setFont(style.fontCascade());
textPainter.setStyle(computeTextPaintStyle(m_flow.frame(), style, paintInfo));
if (auto* debugShadow = debugTextShadow())
textPainter.setShadow(debugShadow);
textPainter.paint(textRun, rect, textOrigin);
if (!style.textDecorationsInEffect().isEmpty()) {
// FIXME: Use correct RenderText.
if (auto* textRenderer = childrenOfType<RenderText>(m_flow).first()) {
auto painter = TextDecorationPainter { paintInfo.context(), style.textDecorationsInEffect(), *textRenderer, false, style.fontCascade() };
painter.setWidth(rect.width());
painter.paintTextDecoration(textRun, textOrigin, rect.location() + paintOffset);
}
}
}
}
bool LineLayout::hitTest(const HitTestRequest& request, HitTestResult& result, const HitTestLocation& locationInContainer, const LayoutPoint& accumulatedOffset, HitTestAction hitTestAction)
{
if (hitTestAction != HitTestForeground)
return false;
if (!displayInlineContent())
return false;
auto& inlineContent = *displayInlineContent();
// FIXME: This should do something efficient to find the run range.
for (auto& run : inlineContent.runs) {
auto runRect = Layout::toLayoutRect(run.logicalRect());
runRect.moveBy(accumulatedOffset);
if (!locationInContainer.intersects(runRect))
continue;
auto& style = run.style();
if (style.visibility() != Visibility::Visible || style.pointerEvents() == PointerEvents::None)
continue;
auto& renderer = const_cast<RenderObject&>(*m_treeContent->rendererForLayoutBox(run.layoutBox()));
renderer.updateHitTestResult(result, locationInContainer.point() - toLayoutSize(accumulatedOffset));
if (result.addNodeToListBasedTestResult(renderer.node(), request, locationInContainer, runRect) == HitTestProgress::Stop)
return true;
}
return false;
}
ShadowData* LineLayout::debugTextShadow()
{
if (!m_flow.settings().simpleLineLayoutDebugBordersEnabled())
return nullptr;
static NeverDestroyed<ShadowData> debugTextShadow(IntPoint(0, 0), 10, 20, ShadowStyle::Normal, true, Color(0, 0, 150, 150));
return &debugTextShadow.get();
}
}
}
#endif