blob: 4b8ef685c4150d1a357a0e1f47929563b5257726 [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 "DisplayPainter.h"
#if ENABLE(LAYOUT_FORMATTING_CONTEXT)
#include "CachedImage.h"
#include "Color.h"
#include "DisplayBox.h"
#include "GraphicsContext.h"
#include "InlineFormattingState.h"
#include "InlineTextItem.h"
#include "IntRect.h"
#include "LayoutContainer.h"
#include "LayoutDescendantIterator.h"
#include "LayoutState.h"
#include "RenderStyle.h"
#include "TextRun.h"
namespace WebCore {
namespace Display {
static void paintBoxDecoration(GraphicsContext& context, const Box& absoluteDisplayBox, const RenderStyle& style, bool needsMarginPainting)
{
auto decorationBoxTopLeft = needsMarginPainting ? absoluteDisplayBox.rectWithMargin().topLeft() : absoluteDisplayBox.topLeft();
auto decorationBoxSize = needsMarginPainting ? absoluteDisplayBox.rectWithMargin().size() : LayoutSize(absoluteDisplayBox.borderBoxWidth(), absoluteDisplayBox.borderBoxHeight());
// Background color
if (style.hasBackground()) {
context.fillRect({ decorationBoxTopLeft, decorationBoxSize }, style.backgroundColor());
if (style.hasBackgroundImage()) {
auto& backgroundLayer = style.backgroundLayers();
if (backgroundLayer.image() && backgroundLayer.image()->cachedImage() && backgroundLayer.image()->cachedImage()->image())
context.drawImage(*backgroundLayer.image()->cachedImage()->image(), { decorationBoxTopLeft, backgroundLayer.image()->cachedImage()->image()->size() });
}
}
// Border
if (style.hasVisibleBorder()) {
auto drawBorderSide = [&](auto start, auto end, const auto& borderStyle) {
if (!borderStyle.width())
return;
if (borderStyle.style() == BorderStyle::None || borderStyle.style() == BorderStyle::Hidden)
return;
context.setStrokeColor(borderStyle.color());
context.setStrokeThickness(borderStyle.width());
context.drawLine(start, end);
};
context.setFillColor(Color::transparent);
auto decorationBoxWidth = decorationBoxSize.width();
auto decorationBoxHeight = decorationBoxSize.height();
// Top
{
auto borderWidth = style.borderTop().width();
auto start = LayoutPoint { decorationBoxTopLeft };
auto end = LayoutPoint { start.x() + decorationBoxWidth, start.y() + borderWidth };
drawBorderSide(start, end, style.borderTop());
}
// Right
{
auto borderWidth = style.borderRight().width();
auto start = LayoutPoint { decorationBoxTopLeft.x() + decorationBoxWidth - borderWidth, decorationBoxTopLeft.y() };
auto end = LayoutPoint { start.x() + borderWidth, decorationBoxTopLeft.y() + decorationBoxHeight };
drawBorderSide(start, end, style.borderRight());
}
// Bottom
{
auto borderWidth = style.borderBottom().width();
auto start = LayoutPoint { decorationBoxTopLeft.x(), decorationBoxTopLeft.y() + decorationBoxHeight - borderWidth };
auto end = LayoutPoint { start.x() + decorationBoxWidth, start.y() + borderWidth };
drawBorderSide(start, end, style.borderBottom());
}
// Left
{
auto borderWidth = style.borderLeft().width();
auto start = decorationBoxTopLeft;
auto end = LayoutPoint { start.x() + borderWidth, decorationBoxTopLeft.y() + decorationBoxHeight };
drawBorderSide(start, end, style.borderLeft());
}
}
}
static void paintInlineContent(GraphicsContext& context, LayoutPoint absoluteOffset, const Layout::InlineFormattingState& formattingState)
{
auto* displayInlineContent = formattingState.displayInlineContent();
if (!displayInlineContent)
return;
auto& displayRuns = displayInlineContent->runs;
if (displayRuns.isEmpty())
return;
for (auto& run : displayRuns) {
if (auto& textContext = run.textContext()) {
auto& style = run.style();
context.setStrokeColor(style.color());
context.setFillColor(style.color());
auto logicalLeft = absoluteOffset.x() + run.logicalLeft();
// FIXME: Add non-baseline align painting
auto& lineBox = displayInlineContent->lineBoxForRun(run);
auto baselineOffset = absoluteOffset.y() + lineBox.logicalTop() + lineBox.baselineOffset();
if (auto expansionContext = textContext->expansion())
context.drawText(style.fontCascade(), TextRun { textContext->content(), logicalLeft, expansionContext->horizontalExpansion, expansionContext->behavior }, { logicalLeft, baselineOffset });
else
context.drawText(style.fontCascade(), TextRun { textContext->content(), logicalLeft }, { logicalLeft, baselineOffset });
} else if (auto* cachedImage = run.image()) {
auto runAbsoluteRect = FloatRect { absoluteOffset.x() + run.logicalLeft(), absoluteOffset.y() + run.logicalTop(), run.logicalWidth(), run.logicalHeight() };
context.drawImage(*cachedImage->image(), runAbsoluteRect);
}
}
}
static Box absoluteDisplayBox(const Layout::LayoutState& layoutState, const Layout::Box& layoutBox)
{
// Should never really happen but table code is way too incomplete.
if (!layoutState.hasDisplayBox(layoutBox))
return { };
auto absoluteBox = Box { layoutState.displayBoxForLayoutBox(layoutBox) };
for (auto* container = layoutBox.containingBlock(); container != &layoutBox.initialContainingBlock(); container = container->containingBlock())
absoluteBox.moveBy(layoutState.displayBoxForLayoutBox(*container).topLeft());
return absoluteBox;
}
static void paintBoxDecorationAndChildren(GraphicsContext& context, const Layout::LayoutState& layoutState, const Layout::Box& layoutBox, const IntRect& dirtyRect)
{
if (!layoutBox.isAnonymous()) {
auto absoluteDisplayBox = Display::absoluteDisplayBox(layoutState, layoutBox);
if (dirtyRect.intersects(snappedIntRect(absoluteDisplayBox.rect())))
paintBoxDecoration(context, absoluteDisplayBox, layoutBox.style(), layoutBox.isBodyBox());
}
if (!is<Layout::Container>(layoutBox))
return;
for (auto& childLayoutBox : Layout::childrenOfType<Layout::Box>(downcast<Layout::Container>(layoutBox))) {
if (childLayoutBox.style().visibility() != Visibility::Visible)
continue;
paintBoxDecorationAndChildren(context, layoutState, childLayoutBox, dirtyRect);
}
}
void Painter::paint(const Layout::LayoutState& layoutState, GraphicsContext& context, const IntRect& dirtyRect)
{
auto& layoutRoot = layoutState.root();
if (!layoutRoot.firstChild())
return;
// Fill the entire content area.
auto rootRect = LayoutRect { layoutState.displayBoxForLayoutBox(layoutRoot).rect() };
for (auto& layoutBox : Layout::descendantsOfType<Layout::Box>(layoutRoot))
rootRect.uniteIfNonZero(Display::absoluteDisplayBox(layoutState, layoutBox).rect());
context.fillRect(rootRect, Color::white);
// 1. Paint box decoration (both block and inline).
paintBoxDecorationAndChildren(context, layoutState, *layoutRoot.firstChild(), dirtyRect);
// 2. Paint content
for (auto& layoutBox : Layout::descendantsOfType<Layout::Box>(layoutRoot)) {
auto absoluteDisplayBox = Display::absoluteDisplayBox(layoutState, layoutBox);
// FIXME: This is the best we can do with no layout overflow support.
if (!dirtyRect.intersects(snappedIntRect(absoluteDisplayBox.rect())))
continue;
if (layoutBox.style().visibility() != Visibility::Visible)
continue;
if (layoutBox.establishesInlineFormattingContext()) {
auto& container = downcast<Layout::Container>(layoutBox);
paintInlineContent(context, absoluteDisplayBox.topLeft(), downcast<Layout::InlineFormattingState>(layoutState.establishedFormattingState(container)));
continue;
}
}
}
void Painter::paintInlineFlow(const Layout::LayoutState& layoutState, GraphicsContext& context)
{
auto& layoutRoot = layoutState.root();
ASSERT(layoutRoot.establishesInlineFormattingContext());
paintInlineContent(context, { }, downcast<Layout::InlineFormattingState>(layoutState.establishedFormattingState(layoutRoot)));
}
}
}
#endif