blob: 378a1d0ac6585dbce21d67b78ea6f7e1d7c7374b [file] [log] [blame]
/*
* Copyright (C) 2020 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 "DisplayCSSPainter.h"
#if ENABLE(LAYOUT_FORMATTING_CONTEXT)
#include "DisplayBoxClip.h"
#include "DisplayBoxPainter.h"
#include "DisplayBoxRareGeometry.h"
#include "DisplayContainerBox.h"
#include "DisplayPaintingContext.h"
#include "DisplayStackingItem.h"
#include "DisplayStyle.h"
#include "DisplayTree.h"
#include "GraphicsContext.h"
#include "IntRect.h"
#include "TransformationMatrix.h"
#define SHOW_ITEM_EXTENTS 0
namespace WebCore {
namespace Display {
static void applyClipIfNecessary(const Box& box, PaintingContext& paintingContext, GraphicsContextStateSaver& stateSaver)
{
if (is<BoxModelBox>(box) && box.style().hasClippedOverflow()) {
stateSaver.save();
auto roundedInnerBorder = downcast<BoxModelBox>(box).innerBorderRoundedRect();
paintingContext.context.clipRoundedRect(roundedInnerBorder);
}
}
static void applyAncestorClip(const BoxModelBox& box, PaintingContext& paintingContext)
{
auto* boxClip = box.ancestorClip();
if (!boxClip || !boxClip->clipRect())
return;
if (!boxClip->affectedByBorderRadius()) {
paintingContext.context.clip(*boxClip->clipRect());
return;
}
for (auto& roundedRect : boxClip->clipStack())
paintingContext.context.clipRoundedRect(roundedRect);
}
static void applyEffects(const StackingItem& stackingItem, const Box& box, PaintingContext& paintingContext, TransparencyLayerScope& transparencyScope)
{
if (box.style().opacity() < 1) {
paintingContext.context.clip(stackingItem.paintedBoundsIncludingDescendantItems());
transparencyScope.beginLayer(box.style().opacity());
}
}
// FIXME: Make this an iterator.
void CSSPainter::recursivePaintDescendantsForPhase(const ContainerBox& containerBox, PaintingContext& paintingContext, PaintPhase paintPhase)
{
auto stateSaver = GraphicsContextStateSaver { paintingContext.context, false };
applyClipIfNecessary(containerBox, paintingContext, stateSaver);
for (const auto* child = containerBox.firstChild(); child; child = child->nextSibling()) {
auto& box = *child;
ASSERT(!box.participatesInZOrderSorting());
switch (paintPhase) {
case PaintPhase::BlockBackgrounds:
if (!box.style().isFloating() && !box.style().isPositioned() && is<BoxModelBox>(box))
BoxPainter::paintBoxDecorations(downcast<BoxModelBox>(box), paintingContext);
break;
case PaintPhase::Floats:
if (box.style().isFloating() && !box.style().isPositioned() && is<BoxModelBox>(box))
BoxPainter::paintBoxDecorations(downcast<BoxModelBox>(box), paintingContext);
break;
case PaintPhase::BlockForegrounds:
if (!box.style().isFloating() && !box.style().isPositioned())
BoxPainter::paintBoxContent(box, paintingContext);
};
if (is<ContainerBox>(box))
recursivePaintDescendantsForPhase(downcast<ContainerBox>(box), paintingContext, paintPhase);
}
}
void CSSPainter::recursivePaintDescendants(const ContainerBox& containerBox, PaintingContext& paintingContext)
{
// For all its in-flow, non-positioned, block-level descendants in tree order: If the element is a block, list-item, or other block equivalent:
// Box decorations.
// Table decorations.
recursivePaintDescendantsForPhase(containerBox, paintingContext, PaintPhase::BlockBackgrounds);
// All non-positioned floating descendants, in tree order. For each one of these, treat the element as if it created a new stacking context,
// but any positioned descendants and descendants which actually create a new stacking context should be considered part of the parent
// stacking context, not this new one.
recursivePaintDescendantsForPhase(containerBox, paintingContext, PaintPhase::Floats);
// If the element is an inline element that generates a stacking context, then:
// FIXME: Handle this case.
// Otherwise: first for the element, then for all its in-flow, non-positioned, block-level descendants in tree order:
// 1. If the element is a block-level replaced element, then: the replaced content, atomically.
// 2. Otherwise, for each line box of that element...
recursivePaintDescendantsForPhase(containerBox, paintingContext, PaintPhase::BlockForegrounds);
}
void CSSPainter::paintAtomicallyPaintedBox(const StackingItem& stackingItem, PaintingContext& paintingContext, const IntRect& dirtyRect, IncludeStackingContextDescendants includeStackingContextDescendants)
{
UNUSED_PARAM(dirtyRect);
auto& box = stackingItem.box();
auto needToSaveState = [](const Box& box) {
if (!is<BoxModelBox>(box))
return false;
auto& boxModelBox = downcast<BoxModelBox>(box);
return boxModelBox.hasAncestorClip() || boxModelBox.hasTransform() || boxModelBox.style().opacity() < 1;
};
auto stateSaver = GraphicsContextStateSaver { paintingContext.context, needToSaveState(box) };
if (is<BoxModelBox>(box))
applyAncestorClip(downcast<BoxModelBox>(box), paintingContext);
if (is<BoxModelBox>(box) && box.hasTransform()) {
auto transformationMatrix = downcast<BoxModelBox>(box).rareGeometry()->transform();
auto absoluteBorderBox = box.absoluteBoxRect();
// Equivalent to adjusting the CTM so that the origin is in the top left of the border box.
transformationMatrix.translateRight(absoluteBorderBox.x(), absoluteBorderBox.y());
// Allow descendants rendered using absolute coordinates to paint relative to this box.
transformationMatrix.translate(-absoluteBorderBox.x(), -absoluteBorderBox.y());
auto affineTransform = transformationMatrix.toAffineTransform();
paintingContext.context.concatCTM(affineTransform);
}
auto transparencyScope = TransparencyLayerScope { paintingContext.context, 1, false };
applyEffects(stackingItem, box, paintingContext, transparencyScope);
#if SHOW_ITEM_EXTENTS
{
GraphicsContextStateSaver saver(paintingContext.context);
paintingContext.context.setStrokeColor(Color::gray);
paintingContext.context.strokeRect(stackingItem.paintedBoundsIncludingDescendantItems(), 1);
}
#endif
BoxPainter::paintBox(box, paintingContext, dirtyRect);
if (!is<ContainerBox>(box))
return;
if (includeStackingContextDescendants == IncludeStackingContextDescendants::Yes) {
// Stacking contexts formed by positioned descendants with negative z-indices (excluding 0) in z-index order (most negative first) then tree order.
for (auto& stackingItem : stackingItem.negativeZOrderList())
paintStackingContext(*stackingItem, paintingContext, dirtyRect);
}
auto& containerBox = downcast<ContainerBox>(box);
recursivePaintDescendants(containerBox, paintingContext);
if (includeStackingContextDescendants == IncludeStackingContextDescendants::Yes) {
// All positioned descendants with 'z-index: auto' or 'z-index: 0', in tree order. For those with 'z-index: auto', treat the element
// as if it created a new stacking context, but any positioned descendants and descendants which actually create a new stacking context
// should be considered part of the parent stacking context, not this new one. For those with 'z-index: 0', treat the stacking context
// generated atomically.
for (auto& stackingItem : stackingItem.positiveZOrderList()) {
if (stackingItem->isStackingContext())
paintStackingContext(*stackingItem, paintingContext, dirtyRect);
else
paintAtomicallyPaintedBox(*stackingItem, paintingContext, dirtyRect);
}
}
}
void CSSPainter::paintStackingContext(const StackingItem& stackingItem, PaintingContext& paintingContext, const IntRect& dirtyRect)
{
paintAtomicallyPaintedBox(stackingItem, paintingContext, dirtyRect, IncludeStackingContextDescendants::Yes);
}
void CSSPainter::paintTree(const Tree& displayTree, PaintingContext& paintingContext, const IntRect& dirtyRect)
{
paintStackingContext(displayTree.rootStackingItem(), paintingContext, dirtyRect);
}
} // namespace Display
} // namespace WebCore
#endif // ENABLE(LAYOUT_FORMATTING_CONTEXT)