blob: 8944c18d0c5463e23117e72ee9ff03c4fc6ec693 [file] [log] [blame]
/*
* Copyright (C) 1999 Lars Knoll (knoll@kde.org)
* (C) 1999 Antti Koivisto (koivisto@kde.org)
* (C) 2005 Allan Sandfeld Jensen (kde@carewolf.com)
* (C) 2005, 2006 Samuel Weinig (sam.weinig@gmail.com)
* Copyright (C) 2005-2025 Apple Inc. All rights reserved.
* Copyright (C) 2010-2013 Google Inc. All rights reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*
*/
#include "config.h"
#include "BackgroundPainter.h"
#include "BorderPainter.h"
#include "BorderShape.h"
#include "CachedImage.h"
#include "ColorBlending.h"
#include "ContainerNodeInlines.h"
#include "FloatRoundedRect.h"
#include "GeometryUtilities.h"
#include "GraphicsContext.h"
#include "InlineIteratorInlineBox.h"
#include "PaintInfo.h"
#include "RenderBoxInlines.h"
#include "RenderBoxModelObjectInlines.h"
#include "RenderElementStyleInlines.h"
#include "RenderImage.h"
#include "RenderLayer.h"
#include "RenderLayerBacking.h"
#include "RenderObjectInlines.h"
#include "RenderTableCell.h"
#include "RenderView.h"
#include "Settings.h"
#include "StyleBoxShadow.h"
#include "StylePrimitiveNumericTypes+Evaluation.h"
#include "TextBoxPainter.h"
namespace WebCore {
BackgroundImageGeometry::BackgroundImageGeometry(const LayoutRect& destinationRect, const LayoutSize& tileSizeWithoutPixelSnapping, const LayoutSize& tileSize, const LayoutSize& phase, const LayoutSize& spaceSize, bool fixedAttachment)
: destinationRect(destinationRect)
, destinationOrigin(destinationRect.location())
, tileSizeWithoutPixelSnapping(tileSizeWithoutPixelSnapping)
, tileSize(tileSize)
, phase(phase)
, spaceSize(spaceSize)
, hasNonLocalGeometry(fixedAttachment)
{
}
BackgroundPainter::BackgroundPainter(RenderBoxModelObject& renderer, const PaintInfo& paintInfo)
: m_renderer(renderer)
, m_paintInfo(paintInfo)
{
// background-clip has no effect when painting the root background.
// https://www.w3.org/TR/css-backgrounds-3/#background-clip
if (m_renderer.isDocumentElementRenderer())
setOverrideClip(FillBox::BorderBox);
}
void BackgroundPainter::paintBackground(const LayoutRect& paintRect, BleedAvoidance bleedAvoidance) const
{
if (m_renderer.isDocumentElementRenderer()) {
paintRootBoxFillLayers();
return;
}
if (!paintsOwnBackground(m_renderer))
return;
if (auto* renderBox = dynamicDowncast<RenderBox>(m_renderer)) {
if (renderBox->backgroundIsKnownToBeObscured(paintRect.location()) && !boxShadowShouldBeAppliedToBackground(*renderBox, paintRect.location(), bleedAvoidance, { }))
return;
}
auto backgroundColor = m_renderer.style().visitedDependentBackgroundColorApplyingColorFilter();
auto compositeOp = document().compositeOperatorForBackgroundColor(backgroundColor, m_renderer);
paintFillLayers(backgroundColor, m_renderer.style().backgroundLayers(), paintRect, bleedAvoidance, compositeOp);
}
void BackgroundPainter::paintRootBoxFillLayers() const
{
ASSERT(m_renderer.isDocumentElementRenderer());
if (m_paintInfo.skipRootBackground())
return;
auto* rootBackgroundRenderer = view().rendererForRootBackground();
if (!rootBackgroundRenderer)
return;
auto& style = rootBackgroundRenderer->style();
auto backgroundColor = style.visitedDependentBackgroundColorApplyingColorFilter();
auto compositeOp = document().compositeOperatorForBackgroundColor(backgroundColor, m_renderer);
paintFillLayers(backgroundColor, style.backgroundLayers(), view().backgroundRect(), BleedAvoidance::None, compositeOp, rootBackgroundRenderer);
}
bool BackgroundPainter::paintsOwnBackground(const RenderBoxModelObject& renderer)
{
if (!renderer.isBody())
return true;
if (renderer.shouldApplyAnyContainment())
return true;
// The <body> only paints its background if the root element has defined a background independent of the body,
// or if the <body>'s parent is not the document element's renderer (e.g. inside SVG foreignObject).
auto documentElementRenderer = renderer.document().documentElement()->renderer();
return !documentElementRenderer || documentElementRenderer->shouldApplyAnyContainment() || documentElementRenderer->hasBackground() || documentElementRenderer != renderer.parent();
}
void BackgroundPainter::paintFillLayers(const Color& color, const Style::BackgroundLayers& fillLayerList, const LayoutRect& rect, BleedAvoidance bleedAvoidance, CompositeOperator op, RenderElement* backgroundObject) const
{
paintFillLayersImpl(color, fillLayerList, rect, bleedAvoidance, op, backgroundObject);
}
void BackgroundPainter::paintFillLayers(const Color& color, const Style::MaskLayers& fillLayerList, const LayoutRect& rect, BleedAvoidance bleedAvoidance, CompositeOperator op, RenderElement* backgroundObject) const
{
paintFillLayersImpl(color, fillLayerList, rect, bleedAvoidance, op, backgroundObject);
}
template<typename Layers> void BackgroundPainter::paintFillLayersImpl(const Color& color, const Layers& fillLayers, const LayoutRect& rect, BleedAvoidance bleedAvoidance, CompositeOperator op, RenderElement* backgroundObject) const
{
bool shouldDrawBackgroundInSeparateBuffer = false;
Style::computeClipMax(fillLayers);
for (auto& layer : fillLayers.usedValues()) {
if (layer.blendMode() != BlendMode::Normal)
shouldDrawBackgroundInSeparateBuffer = true;
// Stop traversal when an opaque layer is encountered.
// FIXME: It would be possible for the following occlusion culling test to be more aggressive
// on layers with no repeat by testing whether the image covers the layout rect.
// Testing that here would imply duplicating a lot of calculations that are currently done in
// BackgroundPainter::paintFillLayer. A more efficient solution might be to move
// the layer recursion into paintFillLayer, or to compute the layer geometry here
// and pass it down.
if (layer.clipOccludesNextLayers()
&& layer.hasOpaqueImage(m_renderer)
&& layer.image().tryStyleImage()->canRender(&m_renderer, m_renderer.style().usedZoom())
&& layer.hasRepeatXY()
&& layer.blendMode() == BlendMode::Normal
&& !boxShadowShouldBeAppliedToBackground(m_renderer, rect.location(), bleedAvoidance, { }))
break;
}
auto& context = m_paintInfo.context();
auto baseBgColorUsage = BaseBackgroundColorUse;
if (shouldDrawBackgroundInSeparateBuffer) {
paintFillLayerImpl(color, FillLayerToPaint<typename Layers::value_type> { .layer = fillLayers.usedLast(), .isLast = true }, rect, bleedAvoidance, { }, { }, op, backgroundObject, BaseBackgroundColorOnly);
baseBgColorUsage = BaseBackgroundColorSkip;
context.beginTransparencyLayer(1);
}
for (auto& layer : fillLayers.usedValues() | std::views::reverse)
paintFillLayerImpl(color, FillLayerToPaint<typename Layers::value_type> { .layer = layer, .isLast = &layer == &fillLayers.usedLast() }, rect, bleedAvoidance, { }, { }, op, backgroundObject, baseBgColorUsage);
if (shouldDrawBackgroundInSeparateBuffer)
context.endTransparencyLayer();
}
static void applyBoxShadowForBackground(GraphicsContext& context, const RenderStyle& style)
{
Style::ColorResolver colorResolver { style };
const auto& zoomFactor = style.usedZoomForLength();
for (const auto& shadow : style.boxShadow()) {
if (shadow.inset)
continue;
context.setDropShadow({
FloatSize {
shadow.location.x().resolveZoom(zoomFactor),
shadow.location.y().resolveZoom(zoomFactor),
},
shadow.blur.resolveZoom(zoomFactor),
colorResolver.colorResolvingCurrentColorApplyingColorFilter(shadow.color),
shadow.isWebkitBoxShadow ? ShadowRadiusMode::Legacy : ShadowRadiusMode::Default
});
break;
}
}
void BackgroundPainter::paintFillLayer(const Color& color, const FillLayerToPaint<Style::BackgroundLayer>& layer, const LayoutRect& rect, BleedAvoidance bleedAvoidance, const InlineIterator::InlineBoxIterator& inlineBoxIterator, const LayoutRect& backgroundImageStrip, CompositeOperator op, RenderElement* backgroundObject, BaseBackgroundColorUsage baseBgColorUsage) const
{
paintFillLayerImpl(color, layer, rect, bleedAvoidance, inlineBoxIterator, backgroundImageStrip, op, backgroundObject, baseBgColorUsage);
}
void BackgroundPainter::paintFillLayer(const Color& color, const FillLayerToPaint<Style::MaskLayer>& layer, const LayoutRect& rect, BleedAvoidance bleedAvoidance, const InlineIterator::InlineBoxIterator& inlineBoxIterator, const LayoutRect& backgroundImageStrip, CompositeOperator op, RenderElement* backgroundObject, BaseBackgroundColorUsage baseBgColorUsage) const
{
paintFillLayerImpl(color, layer, rect, bleedAvoidance, inlineBoxIterator, backgroundImageStrip, op, backgroundObject, baseBgColorUsage);
}
template<typename Layer> void BackgroundPainter::paintFillLayerImpl(const Color& color, const FillLayerToPaint<Layer>& layer, const LayoutRect& rect, BleedAvoidance bleedAvoidance, const InlineIterator::InlineBoxIterator& inlineBoxIterator, const LayoutRect& backgroundImageStrip, CompositeOperator op, RenderElement* backgroundObject, BaseBackgroundColorUsage baseBgColorUsage) const
{
GraphicsContext& context = m_paintInfo.context();
if ((context.paintingDisabled() && !context.detectingContentfulPaint()) || rect.isEmpty())
return;
auto closedEdges = inlineBoxIterator
? inlineBoxIterator->closedEdges()
: RectEdges<bool> { true };
auto& style = m_renderer.style();
auto layerClip = m_overrideClip.value_or(layer.layer.clip());
bool hasRoundedBorder = style.hasBorderRadius()
&& (closedEdges.start(style.writingMode()) || closedEdges.end(style.writingMode()));
bool clippedWithLocalScrolling = m_renderer.hasNonVisibleOverflow() && layer.layer.attachment() == FillAttachment::LocalBackground;
bool isBorderFill = layerClip == FillBox::BorderBox;
bool isRoot = m_renderer.isDocumentElementRenderer();
Color bgColor = color;
RefPtr bgImage = layer.layer.image().tryStyleImage();
bool shouldPaintBackgroundImage = bgImage && bgImage->canRender(&m_renderer, style.usedZoom());
if (context.detectingContentfulPaint()) {
if (!context.contentfulPaintDetected() && shouldPaintBackgroundImage && bgImage->cachedImage()) {
if (style.backgroundLayers().usedFirst().size().isEmpty())
context.setContentfulPaintDetected();
return;
}
}
if (context.invalidatingImagesWithAsyncDecodes()) {
if (shouldPaintBackgroundImage && bgImage->cachedImage()->isClientWaitingForAsyncDecoding(m_renderer.cachedImageClient()))
bgImage->cachedImage()->removeAllClientsWaitingForAsyncDecoding();
return;
}
bool forceBackgroundToWhite = false;
if (document().printing()) {
if (style.printColorAdjust() == PrintColorAdjust::Economy)
forceBackgroundToWhite = true;
if (document().settings().shouldPrintBackgrounds())
forceBackgroundToWhite = false;
}
// When printing backgrounds is disabled or using economy mode,
// change existing background colors and images to a solid white background.
// If there's no bg color or image, leave it untouched to avoid affecting transparency.
// We don't try to avoid loading the background images, because this style flag is only set
// when printing, and at that point we've already loaded the background images anyway. (To avoid
// loading the background images we'd have to do this check when applying styles rather than
// while rendering.)
if (forceBackgroundToWhite) {
// Note that we can't reuse this variable below because the bgColor might be changed
bool shouldPaintBackgroundColor = layer.isLast && bgColor.isVisible();
if (shouldPaintBackgroundImage || shouldPaintBackgroundColor) {
bgColor = Color::white;
shouldPaintBackgroundImage = false;
}
if (layerClip == FillBox::Text)
layerClip = FillBox::BorderBox;
}
bool baseBgColorOnly = (baseBgColorUsage == BaseBackgroundColorOnly);
if (baseBgColorOnly && (!isRoot || !layer.isLast || bgColor.isOpaque()))
return;
bool colorVisible = bgColor.isVisible();
float deviceScaleFactor = document().deviceScaleFactor();
FloatRect pixelSnappedRect = snapRectToDevicePixels(rect, deviceScaleFactor);
auto borderShapeRespectingBleedAvoidance = [&](RectEdges<bool> closedEdges, bool shrinkForBleedAvoidance = true) {
auto borderRect = rect;
if (shrinkForBleedAvoidance && bleedAvoidance == BleedAvoidance::ShrinkBackground) {
// Ideally we'd use the border rect, but add a device pixel of additional inset to preserve corner shape.
borderRect = shrinkRectByOneDevicePixel(m_paintInfo.context(), borderRect, deviceScaleFactor);
}
return BorderShape::shapeForBorderRect(style, borderRect, closedEdges);
};
// Fast path for drawing simple color backgrounds.
if (!isRoot && !clippedWithLocalScrolling && !shouldPaintBackgroundImage && isBorderFill && layer.isLast) {
if (!colorVisible)
return;
bool applyBoxShadowToBackground = boxShadowShouldBeAppliedToBackground(m_renderer, rect.location(), bleedAvoidance, inlineBoxIterator);
GraphicsContextStateSaver shadowStateSaver(context, applyBoxShadowToBackground);
if (applyBoxShadowToBackground)
applyBoxShadowForBackground(context, style);
if (hasRoundedBorder && bleedAvoidance != BleedAvoidance::UseTransparencyLayer) {
auto borderShape = borderShapeRespectingBleedAvoidance(closedEdges);
auto previousOperator = context.compositeOperation();
bool saveRestoreCompositeOp = op != previousOperator;
if (saveRestoreCompositeOp)
context.setCompositeOperation(op);
if (bleedAvoidance == BleedAvoidance::BackgroundOverBorder)
borderShape.fillInnerShape(context, bgColor, deviceScaleFactor);
else
borderShape.fillOuterShape(context, bgColor, deviceScaleFactor);
if (saveRestoreCompositeOp)
context.setCompositeOperation(previousOperator);
} else
context.fillRect(pixelSnappedRect, bgColor, op);
return;
}
auto clipForBorder = [&]() -> std::optional<FillBox> {
if (!hasRoundedBorder)
return { };
// FillBox::BorderBox radius clipping is taken care of by BleedAvoidance::UseTransparencyLayer
if (layerClip == FillBox::BorderBox && bleedAvoidance == BleedAvoidance::UseTransparencyLayer)
return { };
if (bleedAvoidance == BleedAvoidance::BackgroundOverBorder)
return FillBox::PaddingBox;
return layerClip;
}();
GraphicsContextStateSaver clipToBorderStateSaver(context, clipForBorder.has_value());
if (clipForBorder) {
switch (*clipForBorder) {
case FillBox::BorderBox:
case FillBox::BorderArea:
case FillBox::Text:
case FillBox::NoClip: {
auto borderShape = borderShapeRespectingBleedAvoidance(closedEdges, isBorderFill);
borderShape.clipToOuterShape(context, deviceScaleFactor);
break;
}
case FillBox::PaddingBox: {
auto borderShape = borderShapeRespectingBleedAvoidance(closedEdges, isBorderFill);
borderShape.clipToInnerShape(context, deviceScaleFactor);
break;
}
case FillBox::ContentBox: {
auto borderShape = m_renderer.borderShapeForContentClipping(rect);
borderShape.clipToInnerShape(context, deviceScaleFactor);
break;
}
}
}
LayoutUnit bLeft = closedEdges.left() ? m_renderer.borderLeft() : 0_lu;
LayoutUnit bRight = closedEdges.right() ? m_renderer.borderRight() : 0_lu;
LayoutUnit pLeft = closedEdges.left() ? m_renderer.paddingLeft() : 0_lu;
LayoutUnit pRight = closedEdges.right() ? m_renderer.paddingRight() : 0_lu;
GraphicsContextStateSaver clipWithScrollingStateSaver(context, clippedWithLocalScrolling);
LayoutRect scrolledPaintRect = rect;
if (clippedWithLocalScrolling) {
// Clip to the overflow area.
auto& renderBox = downcast<RenderBox>(m_renderer);
context.clip(renderBox.overflowClipRect(rect.location()));
// Adjust the paint rect to reflect a scrolled content box with borders at the ends.
scrolledPaintRect.moveBy(-renderBox.scrollPosition());
scrolledPaintRect.setWidth(bLeft + renderBox.layer()->scrollWidth() + bRight);
scrolledPaintRect.setHeight(renderBox.borderTop() + renderBox.layer()->scrollHeight() + renderBox.borderBottom());
}
GraphicsContextStateSaver backgroundClipStateSaver(context, false);
auto layerBlendMode = layer.layer.blendMode();
auto backgroundClipOuterLayerScope = TransparencyLayerScope(context, 1, false);
auto backgroundClipInnerLayerScope = TransparencyLayerScope(context, 1, false);
auto setupMaskingBackgroundClip = [&](const LayoutRect& borderRect, const std::function<void(GraphicsContext& context, const LayoutRect&, const FloatRect&)>& paintFunction) {
auto transparencyLayerBounds = snapRectToDevicePixels(rect, deviceScaleFactor);
transparencyLayerBounds.intersect(snapRectToDevicePixels(m_paintInfo.rect, deviceScaleFactor));
transparencyLayerBounds.inflate(1);
// The last layer is blended over the background color below, so only change the blend mode here for non-last layers.
if (layerBlendMode != BlendMode::Normal && !layer.isLast) {
context.setCompositeOperation(context.compositeOperation(), layerBlendMode);
layerBlendMode = BlendMode::Normal;
}
backgroundClipStateSaver.save();
context.clip(transparencyLayerBounds);
backgroundClipOuterLayerScope.beginLayer(1);
if (context.renderingMode() == RenderingMode::PDFDocument) {
// CG doesn't support some compositing operations when printing, so clip using an imageBuffer instead.
static constexpr auto printBufferResolution = 3.0f; // Chosen to result in output that is not too blurry.
RefPtr maskImage = context.createImageBuffer(transparencyLayerBounds.size(), printBufferResolution);
if (!maskImage)
return;
auto& maskContext = maskImage->context();
maskContext.translate(-transparencyLayerBounds.location());
paintFunction(maskContext, borderRect, transparencyLayerBounds);
context.clipToImageBuffer(*maskImage, transparencyLayerBounds);
} else {
paintFunction(context, borderRect, transparencyLayerBounds);
context.setCompositeOperation(CompositeOperator::SourceIn);
}
backgroundClipInnerLayerScope.beginLayer(1);
context.setCompositeOperation(CompositeOperator::SourceOver);
};
switch (layerClip) {
case FillBox::BorderBox:
break;
case FillBox::PaddingBox:
case FillBox::ContentBox: {
// Clip to the padding or content boxes as necessary.
if (!clipForBorder) {
bool includePadding = layerClip == FillBox::ContentBox;
LayoutRect clipRect = LayoutRect(scrolledPaintRect.x() + bLeft + (includePadding ? pLeft : 0_lu),
scrolledPaintRect.y() + m_renderer.borderTop() + (includePadding ? m_renderer.paddingTop() : 0_lu),
scrolledPaintRect.width() - bLeft - bRight - (includePadding ? pLeft + pRight : 0_lu),
scrolledPaintRect.height() - m_renderer.borderTop() - m_renderer.borderBottom() - (includePadding ? m_renderer.paddingTop() + m_renderer.paddingBottom() : 0_lu));
backgroundClipStateSaver.save();
context.clip(clipRect);
}
break;
}
case FillBox::Text: {
// We have to draw our text into a mask that can then be used to clip background drawing.
// First figure out how big the mask has to be. It should be no bigger than what we need
// to actually render, so we should intersect the dirty rect with the border box of the background.
setupMaskingBackgroundClip(rect, [&](GraphicsContext& context, const LayoutRect&, const FloatRect& paintRect) {
m_renderer.paintMaskForTextFillBox(context, paintRect, inlineBoxIterator, scrolledPaintRect);
});
break;
}
case FillBox::BorderArea: {
auto borderAreaPath = BorderPainter::pathForBorderArea(rect, style, deviceScaleFactor, closedEdges);
if (borderAreaPath) {
backgroundClipStateSaver.save();
context.clipPath(borderAreaPath.value());
break;
}
setupMaskingBackgroundClip(rect, [&](GraphicsContext& context, const LayoutRect& borderRect, const FloatRect& paintRect) {
auto borderPaintInfo = PaintInfo { context, LayoutRect { paintRect }, PaintPhase::BlockBackground, PaintBehavior::ForceBlackBorder };
auto borderPainter = BorderPainter { m_renderer, borderPaintInfo };
borderPainter.paintBorder(borderRect, style);
});
break;
}
case FillBox::NoClip:
break;
}
auto isOpaqueRoot = false;
if (isRoot) {
bool shouldPaintBaseBackground = view().rootElementShouldPaintBaseBackground();
isOpaqueRoot = !layer.isLast || bgColor.isOpaque() || shouldPaintBaseBackground;
if (!shouldPaintBaseBackground)
baseBgColorUsage = BaseBackgroundColorSkip;
view().frameView().setContentIsOpaque(isOpaqueRoot);
}
// Paint the color first underneath all images, culled if background image occludes it.
// FIXME: In the bgLayer.hasFiniteBounds() case, we could improve the culling test
// by verifying whether the background image covers the entire layout rect.
if (layer.isLast) {
LayoutRect backgroundRect(scrolledPaintRect);
bool applyBoxShadowToBackground = boxShadowShouldBeAppliedToBackground(m_renderer, rect.location(), bleedAvoidance, inlineBoxIterator);
if (applyBoxShadowToBackground || !shouldPaintBackgroundImage || !layer.layer.hasOpaqueImage(m_renderer) || !layer.layer.hasRepeatXY() || layer.layer.size().isEmpty()) {
if (!applyBoxShadowToBackground)
backgroundRect.intersect(m_paintInfo.rect);
// If we have an alpha and we are painting the root element, blend with the base background color.
Color baseColor;
bool shouldClearBackground = false;
if ((baseBgColorUsage != BaseBackgroundColorSkip) && isOpaqueRoot) {
baseColor = view().frameView().baseBackgroundColor();
if (!baseColor.isVisible())
shouldClearBackground = true;
}
GraphicsContextStateSaver shadowStateSaver(context, applyBoxShadowToBackground);
if (applyBoxShadowToBackground)
applyBoxShadowForBackground(context, style);
FloatRect backgroundRectForPainting = snapRectToDevicePixels(backgroundRect, deviceScaleFactor);
if (baseColor.isVisible()) {
if (!baseBgColorOnly && bgColor.isVisible())
baseColor = blendSourceOver(baseColor, bgColor);
context.fillRect(backgroundRectForPainting, baseColor, CompositeOperator::Copy);
} else if (!baseBgColorOnly && bgColor.isVisible()) {
auto operation = context.compositeOperation();
if (shouldClearBackground) {
if (op == CompositeOperator::DestinationOut) // We're punching out the background.
operation = op;
else
operation = CompositeOperator::Copy;
}
context.fillRect(backgroundRectForPainting, bgColor, operation);
} else if (shouldClearBackground)
context.clearRect(backgroundRectForPainting);
}
}
// no progressive loading of the background image
if (!baseBgColorOnly && shouldPaintBackgroundImage) {
// Multiline inline boxes paint like the image was one long strip spanning lines. The backgroundImageStrip is this fictional rectangle.
auto imageRect = backgroundImageStrip.isEmpty() ? scrolledPaintRect : backgroundImageStrip;
auto paintOffset = backgroundImageStrip.isEmpty() ? rect.location() : backgroundImageStrip.location();
auto geometry = calculateFillLayerImageGeometry(m_renderer, m_paintInfo.paintContainer, layer.layer, paintOffset, imageRect, m_overrideOrigin);
auto& clientForBackgroundImage = backgroundObject ? *backgroundObject : m_renderer;
bgImage->setContainerContextForRenderer(clientForBackgroundImage, geometry.tileSizeWithoutPixelSnapping, m_renderer.style().usedZoom());
geometry.clip(LayoutRect(pixelSnappedRect));
RefPtr<Image> image;
bool isFirstLine = inlineBoxIterator && inlineBoxIterator->lineBox()->isFirst();
if (!geometry.destinationRect.isEmpty() && (image = bgImage->image(backgroundObject ? backgroundObject : &m_renderer, geometry.tileSize, context, isFirstLine))) {
context.setDrawLuminanceMask(layer.layer.maskMode() == Style::MaskMode::Luminance);
ImagePaintingOptions options = {
op == CompositeOperator::SourceOver ? layer.layer.compositeForPainting(layer.isLast) : op,
layerBlendMode,
m_renderer.decodingModeForImageDraw(*image, m_paintInfo),
ImageOrientation::Orientation::FromImage,
m_renderer.chooseInterpolationQuality(context, *image, &layer.layer, geometry.tileSize),
document().settings().imageSubsamplingEnabled() ? AllowImageSubsampling::Yes : AllowImageSubsampling::No,
document().settings().showDebugBorders() ? ShowDebugBackground::Yes : ShowDebugBackground::No,
m_paintInfo.paintBehavior.contains(PaintBehavior::DrawsHDRContent) ? DrawsHDRContent::Yes : DrawsHDRContent::No,
style.dynamicRangeLimit().toPlatformDynamicRangeLimit()
};
auto drawResult = context.drawTiledImage(*image, geometry.destinationRect, toLayoutPoint(geometry.relativePhase()), geometry.tileSize, geometry.spaceSize, options);
if (drawResult == ImageDrawResult::DidRequestDecoding) {
ASSERT(bgImage->hasCachedImage());
bgImage->cachedImage()->addClientWaitingForAsyncDecoding(m_renderer.cachedImageClient());
}
if (!context.paintingDisabled()) {
if (m_renderer.element())
m_renderer.element()->setHasEverPaintedImages(true);
if (auto* image = bgImage->cachedImage(); image && image->currentFrameIsComplete(&m_renderer)) {
if (auto styleable = Styleable::fromRenderer(m_renderer))
document().didPaintImage(styleable->element, image, geometry.destinationRect);
}
}
}
}
}
void BackgroundPainter::clipRoundedInnerRect(GraphicsContext& context, const FloatRoundedRect& clipRect)
{
if (!clipRect.isRenderable()) [[unlikely]] {
auto adjustedClipRect = clipRect;
adjustedClipRect.adjustRadii();
context.clipRoundedRect(adjustedClipRect);
return;
}
context.clipRoundedRect(clipRect);
}
static inline std::optional<LayoutUnit> getSpace(LayoutUnit areaSize, LayoutUnit tileSize)
{
if (int numberOfTiles = areaSize / tileSize; numberOfTiles > 1)
return (areaSize - numberOfTiles * tileSize) / (numberOfTiles - 1);
return std::nullopt;
}
static void pixelSnapBackgroundImageGeometryForPainting(LayoutRect& destinationRect, LayoutSize& tileSize, LayoutSize& phase, LayoutSize& space, float scaleFactor)
{
tileSize = LayoutSize(snapRectToDevicePixels(LayoutRect(destinationRect.location(), tileSize), scaleFactor).size());
phase = LayoutSize(snapRectToDevicePixels(LayoutRect(destinationRect.location(), phase), scaleFactor).size());
space = LayoutSize(snapRectToDevicePixels(LayoutRect(LayoutPoint(), space), scaleFactor).size());
destinationRect = LayoutRect(snapRectToDevicePixels(destinationRect, scaleFactor));
}
BackgroundImageGeometry BackgroundPainter::calculateFillLayerImageGeometry(const RenderBoxModelObject& renderer, const RenderLayerModelObject* paintContainer, const Style::BackgroundLayer& fillLayer, const LayoutPoint& paintOffset, const LayoutRect& borderBoxRect, std::optional<FillBox> overrideOrigin)
{
return calculateFillLayerImageGeometryImpl(renderer, paintContainer, fillLayer, paintOffset, borderBoxRect, overrideOrigin);
}
BackgroundImageGeometry BackgroundPainter::calculateFillLayerImageGeometry(const RenderBoxModelObject& renderer, const RenderLayerModelObject* paintContainer, const Style::MaskLayer& fillLayer, const LayoutPoint& paintOffset, const LayoutRect& borderBoxRect, std::optional<FillBox> overrideOrigin)
{
return calculateFillLayerImageGeometryImpl(renderer, paintContainer, fillLayer, paintOffset, borderBoxRect, overrideOrigin);
}
template<typename Layer> BackgroundImageGeometry BackgroundPainter::calculateFillLayerImageGeometryImpl(const RenderBoxModelObject& renderer, const RenderLayerModelObject* paintContainer, const Layer& fillLayer, const LayoutPoint& paintOffset, const LayoutRect& borderBoxRect, std::optional<FillBox> overrideOrigin)
{
auto& view = renderer.view();
LayoutUnit left;
LayoutUnit top;
LayoutSize positioningAreaSize;
// Determine the background positioning area and set destination rect to the background painting area.
// Destination rect will be adjusted later if the background is non-repeating.
CheckedPtr enclosingLayer = renderer.enclosingLayer();
bool isTransformed = renderer.isTransformed() || (enclosingLayer && enclosingLayer->hasTransformedAncestor());
bool fixedAttachment = fillLayer.attachment() == FillAttachment::FixedBackground && !isTransformed;
LayoutRect destinationRect(borderBoxRect);
float deviceScaleFactor = renderer.document().deviceScaleFactor();
if (!fixedAttachment) {
LayoutUnit right;
LayoutUnit bottom;
// Scroll and Local.
auto fillLayerOrigin = overrideOrigin.value_or(fillLayer.origin());
if (fillLayerOrigin != FillBox::BorderBox) {
left = renderer.borderLeft();
right = renderer.borderRight();
top = renderer.borderTop();
bottom = renderer.borderBottom();
if (fillLayerOrigin == FillBox::ContentBox) {
left += renderer.paddingLeft();
right += renderer.paddingRight();
top += renderer.paddingTop();
bottom += renderer.paddingBottom();
}
}
// The background of the box generated by the root element covers the entire canvas including
// its margins. Since those were added in already, we have to factor them out when computing
// the background positioning area.
if (renderer.isDocumentElementRenderer()) {
positioningAreaSize = downcast<RenderBox>(renderer).size() - LayoutSize(left + right, top + bottom);
positioningAreaSize = LayoutSize(snapSizeToDevicePixel(positioningAreaSize, LayoutPoint(), deviceScaleFactor));
if (view.frameView().hasExtendedBackgroundRectForPainting()) {
LayoutRect extendedBackgroundRect = view.frameView().extendedBackgroundRectForPainting();
left += (renderer.marginLeft() - extendedBackgroundRect.x());
top += (renderer.marginTop() - extendedBackgroundRect.y());
}
} else {
positioningAreaSize = borderBoxRect.size() - LayoutSize(left + right, top + bottom);
positioningAreaSize = LayoutSize(snapRectToDevicePixels(LayoutRect(paintOffset, positioningAreaSize), deviceScaleFactor).size());
}
} else {
LayoutRect viewportRect;
FloatBoxExtent obscuredContentInsets;
if (renderer.settings().fixedBackgroundsPaintRelativeToDocument())
viewportRect = view.unscaledDocumentRect();
else {
LocalFrameView& frameView = view.frameView();
bool useFixedLayout = frameView.useFixedLayout() && !frameView.fixedLayoutSize().isEmpty();
if (useFixedLayout) {
// Use the fixedLayoutSize() when useFixedLayout() because the rendering will scale
// down the frameView to to fit in the current viewport.
viewportRect.setSize(frameView.fixedLayoutSize());
} else
viewportRect.setSize(frameView.sizeForVisibleContent());
if (renderer.fixedBackgroundPaintsInLocalCoordinates()) {
if (!useFixedLayout) {
// Shifting location by the content insets is needed for layout tests which expect
// layout to be shifted when calling window.internals.setObscuredContentInsets().
obscuredContentInsets = frameView.obscuredContentInsets(ScrollView::InsetType::WebCoreOrPlatformInset);
viewportRect.setLocation({ -obscuredContentInsets.left(), -obscuredContentInsets.top() });
}
} else if (useFixedLayout || frameView.frameScaleFactor() != 1) {
// scrollPositionForFixedPosition() is adjusted for page scale and it does not include
// insets so do not add it to the calculation below.
viewportRect.setLocation(frameView.scrollPositionForFixedPosition());
} else {
// documentScrollPositionRelativeToViewOrigin() is already adjusted for content insets
// so we need to account for that in calculating the phase size
obscuredContentInsets = frameView.obscuredContentInsets(ScrollView::InsetType::WebCoreOrPlatformInset);
viewportRect.setLocation(frameView.documentScrollPositionRelativeToViewOrigin());
}
left += obscuredContentInsets.left();
top += obscuredContentInsets.top();
}
if (paintContainer)
viewportRect.moveBy(LayoutPoint(-paintContainer->localToAbsolute(FloatPoint())));
destinationRect = viewportRect;
positioningAreaSize = destinationRect.size();
positioningAreaSize.setWidth(positioningAreaSize.width() - obscuredContentInsets.left());
positioningAreaSize.setHeight(positioningAreaSize.height() - obscuredContentInsets.top());
positioningAreaSize = LayoutSize(snapRectToDevicePixels(LayoutRect(destinationRect.location(), positioningAreaSize), deviceScaleFactor).size());
}
LayoutSize tileSize = calculateFillTileSize(renderer, fillLayer, positioningAreaSize);
auto backgroundRepeatX = fillLayer.repeat().x();
auto backgroundRepeatY = fillLayer.repeat().y();
LayoutUnit availableWidth = positioningAreaSize.width() - tileSize.width();
LayoutUnit availableHeight = positioningAreaSize.height() - tileSize.height();
LayoutSize spaceSize;
LayoutSize phase;
auto computedXPosition = Style::evaluate<LayoutUnit>(fillLayer.positionX(), availableWidth, Style::ZoomNeeded { });
if (backgroundRepeatX == FillRepeat::Round && positioningAreaSize.width() > 0 && tileSize.width() > 0) {
int numTiles = std::max(1, roundToInt(positioningAreaSize.width() / tileSize.width()));
if (!fillLayer.size().specifiedHeight() && backgroundRepeatY != FillRepeat::Round)
tileSize.setHeight(tileSize.height() * positioningAreaSize.width() / (numTiles * tileSize.width()));
tileSize.setWidth(positioningAreaSize.width() / numTiles);
phase.setWidth(tileSize.width() ? tileSize.width() - fmodf((computedXPosition + left), tileSize.width()) : 0);
}
auto computedYPosition = Style::evaluate<LayoutUnit>(fillLayer.positionY(), availableHeight, Style::ZoomNeeded { });
if (backgroundRepeatY == FillRepeat::Round && positioningAreaSize.height() > 0 && tileSize.height() > 0) {
int numTiles = std::max(1, roundToInt(positioningAreaSize.height() / tileSize.height()));
if (!fillLayer.size().specifiedWidth() && backgroundRepeatX != FillRepeat::Round)
tileSize.setWidth(tileSize.width() * positioningAreaSize.height() / (numTiles * tileSize.height()));
tileSize.setHeight(positioningAreaSize.height() / numTiles);
phase.setHeight(tileSize.height() ? tileSize.height() - fmodf((computedYPosition + top), tileSize.height()) : 0);
}
if (backgroundRepeatX == FillRepeat::Repeat) {
phase.setWidth(tileSize.width() ? tileSize.width() - fmodf(computedXPosition + left, tileSize.width()) : 0);
spaceSize.setWidth(0);
} else if (backgroundRepeatX == FillRepeat::Space && tileSize.width() > 0) {
if (auto space = getSpace(positioningAreaSize.width(), tileSize.width())) {
LayoutUnit actualWidth = tileSize.width() + *space;
computedXPosition = 0;
spaceSize.setWidth(*space);
spaceSize.setHeight(0);
phase.setWidth(actualWidth ? actualWidth - fmodf((computedXPosition + left), actualWidth) : 0);
} else
backgroundRepeatX = FillRepeat::NoRepeat;
}
if (backgroundRepeatX == FillRepeat::NoRepeat) {
LayoutUnit xOffset = left + computedXPosition;
if (xOffset > 0)
destinationRect.move(xOffset, 0_lu);
xOffset = std::min<LayoutUnit>(xOffset, 0);
phase.setWidth(-xOffset);
destinationRect.setWidth(tileSize.width() + xOffset);
spaceSize.setWidth(0);
}
if (backgroundRepeatY == FillRepeat::Repeat) {
phase.setHeight(tileSize.height() ? tileSize.height() - fmodf(computedYPosition + top, tileSize.height()) : 0);
spaceSize.setHeight(0);
} else if (backgroundRepeatY == FillRepeat::Space && tileSize.height() > 0) {
if (auto space = getSpace(positioningAreaSize.height(), tileSize.height())) {
LayoutUnit actualHeight = tileSize.height() + *space;
computedYPosition = 0;
spaceSize.setHeight(*space);
phase.setHeight(actualHeight ? actualHeight - fmodf((computedYPosition + top), actualHeight) : 0);
} else
backgroundRepeatY = FillRepeat::NoRepeat;
}
if (backgroundRepeatY == FillRepeat::NoRepeat) {
LayoutUnit yOffset = top + computedYPosition;
if (yOffset > 0)
destinationRect.move(0_lu, yOffset);
yOffset = std::min<LayoutUnit>(yOffset, 0);
phase.setHeight(-yOffset);
destinationRect.setHeight(tileSize.height() + yOffset);
spaceSize.setHeight(0);
}
if (fixedAttachment) {
LayoutPoint attachmentPoint = borderBoxRect.location();
phase.expand(std::max<LayoutUnit>(attachmentPoint.x() - destinationRect.x(), 0), std::max<LayoutUnit>(attachmentPoint.y() - destinationRect.y(), 0));
}
destinationRect.intersect(borderBoxRect);
auto tileSizeWithoutPixelSnapping = tileSize;
pixelSnapBackgroundImageGeometryForPainting(destinationRect, tileSize, phase, spaceSize, deviceScaleFactor);
return BackgroundImageGeometry(destinationRect, tileSizeWithoutPixelSnapping, tileSize, phase, spaceSize, fixedAttachment);
}
template<typename Layer> LayoutSize BackgroundPainter::calculateFillTileSize(const RenderBoxModelObject& renderer, const Layer& fillLayer, const LayoutSize& positioningAreaSize)
{
RefPtr image = fillLayer.image().tryStyleImage();
auto devicePixelSize = LayoutUnit { 1.0 / renderer.document().deviceScaleFactor() };
LayoutSize imageIntrinsicSize;
if (image) {
imageIntrinsicSize = renderer.calculateImageIntrinsicDimensions(image.get(), positioningAreaSize, RenderBoxModelObject::ScaleByUsedZoom::Yes);
imageIntrinsicSize.scale(1 / image->imageScaleFactor(), 1 / image->imageScaleFactor());
} else
imageIntrinsicSize = positioningAreaSize;
auto handleKeyword = [&](auto keyword) -> LayoutSize {
// Scale computation needs higher precision than what LayoutUnit can offer.
FloatSize localImageIntrinsicSize = imageIntrinsicSize;
FloatSize localPositioningAreaSize = positioningAreaSize;
float horizontalScaleFactor = localImageIntrinsicSize.width() ? (localPositioningAreaSize.width() / localImageIntrinsicSize.width()) : 1;
float verticalScaleFactor = localImageIntrinsicSize.height() ? (localPositioningAreaSize.height() / localImageIntrinsicSize.height()) : 1;
float scaleFactor = keyword.value == CSSValueContain ? std::min(horizontalScaleFactor, verticalScaleFactor) : std::max(horizontalScaleFactor, verticalScaleFactor);
if (localImageIntrinsicSize.isEmpty())
return { };
return LayoutSize(localImageIntrinsicSize.scaled(scaleFactor).expandedTo({ devicePixelSize, devicePixelSize }));
};
return WTF::switchOn(fillLayer.size(),
[&](const CSS::Keyword::Cover& keyword) {
return handleKeyword(keyword);
},
[&](const CSS::Keyword::Contain& keyword) {
return handleKeyword(keyword);
},
[&](const Style::BackgroundSize::LengthSize& size) {
auto tileSize = positioningAreaSize;
auto& layerWidth = size.width();
auto& layerHeight = size.height();
if (auto fixed = layerWidth.tryFixed())
tileSize.setWidth(Style::evaluate<LayoutUnit>(*fixed, Style::ZoomNeeded { }));
else if (layerWidth.isPercentOrCalculated()) {
auto resolvedWidth = Style::evaluate<LayoutUnit>(layerWidth, positioningAreaSize.width(), Style::ZoomNeeded { });
// Non-zero resolved value should always produce some content.
tileSize.setWidth(!resolvedWidth ? resolvedWidth : std::max(devicePixelSize, resolvedWidth));
}
if (auto fixed = layerHeight.tryFixed())
tileSize.setHeight(Style::evaluate<LayoutUnit>(*fixed, Style::ZoomNeeded { }));
else if (layerHeight.isPercentOrCalculated()) {
auto resolvedHeight = Style::evaluate<LayoutUnit>(layerHeight, positioningAreaSize.height(), Style::ZoomNeeded { });
// Non-zero resolved value should always produce some content.
tileSize.setHeight(!resolvedHeight ? resolvedHeight : std::max(devicePixelSize, resolvedHeight));
}
// If one of the values is auto we have to use the appropriate
// scale to maintain our aspect ratio.
bool hasNaturalAspectRatio = image && image->imageHasNaturalDimensions();
if (layerWidth.isAuto() && !layerHeight.isAuto()) {
if (hasNaturalAspectRatio && imageIntrinsicSize.height())
tileSize.setWidth(imageIntrinsicSize.width() * tileSize.height() / imageIntrinsicSize.height());
} else if (!layerWidth.isAuto() && layerHeight.isAuto()) {
if (hasNaturalAspectRatio && imageIntrinsicSize.width())
tileSize.setHeight(imageIntrinsicSize.height() * tileSize.width() / imageIntrinsicSize.width());
} else if (layerWidth.isAuto() && layerHeight.isAuto()) {
// If both width and height are auto, use the image's intrinsic size.
tileSize = imageIntrinsicSize;
}
tileSize.clampNegativeToZero();
return tileSize;
}
);
}
void BackgroundPainter::paintBoxShadow(const LayoutRect& paintRect, const RenderStyle& style, Style::ShadowStyle shadowStyle, RectEdges<bool> closedEdges) const
{
// FIXME: Deal with border-image. Would be great to use border-image as a mask.
GraphicsContext& context = m_paintInfo.context();
if (context.paintingDisabled() || !style.hasBoxShadow())
return;
const auto borderShape = BorderShape::shapeForBorderRect(style, paintRect, closedEdges);
bool hasBorderRadius = style.hasBorderRadius();
float deviceScaleFactor = document().deviceScaleFactor();
bool hasOpaqueBackground = style.visitedDependentBackgroundColorApplyingColorFilter().isOpaque();
const auto& zoomFactor = style.usedZoomForLength();
for (const auto& shadow : style.boxShadow()) {
if (Style::shadowStyle(shadow) != shadowStyle)
continue;
LayoutSize shadowOffset(shadow.location.x().resolveZoom(zoomFactor), shadow.location.y().resolveZoom(zoomFactor));
LayoutUnit shadowPaintingExtent = Style::paintingExtent(shadow, zoomFactor);
LayoutUnit shadowSpread = LayoutUnit(shadow.spread.resolveZoom(zoomFactor));
auto shadowRadius = shadow.blur.resolveZoom(zoomFactor);
if (shadowOffset.isZero() && !shadowRadius && !shadowSpread)
continue;
Style::ColorResolver colorResolver { style };
auto shadowColor = colorResolver.colorResolvingCurrentColorApplyingColorFilter(shadow.color);
auto shouldInflateBorderRect = [&]() {
if (!hasOpaqueBackground)
return false;
// FIXME: The function to decide on the policy based on the transform should be a named function.
// FIXME: It's not clear if this check is right. What about integral scale factors?
auto transform = context.getCTM();
if (transform.a() != 1 || (transform.d() != 1 && transform.d() != -1) || transform.b() || transform.c())
return true;
return false;
};
if (!Style::isInset(shadow)) {
auto shadowShape = [&] {
if (!shadowSpread)
return borderShape;
if (shadowSpread > 0) {
auto spreadRect = paintRect;
spreadRect.inflate(shadowSpread);
return BorderShape::shapeForOutsetRect(style, paintRect, spreadRect, { }, closedEdges);
}
auto spreadRect = paintRect;
auto inflateX = std::max(shadowSpread, -paintRect.width() / 2);
auto inflateY = std::max(shadowSpread, -paintRect.height() / 2);
spreadRect.inflate(LayoutSize { inflateX, inflateY });
return BorderShape::shapeForInsetRect(style, paintRect, spreadRect /* , closedEdges*/);
}();
if (shadowShape.isEmpty())
continue;
// If the box is opaque, it is unnecessary to clip it out. However, doing so saves time
// when painting the shadow. On the other hand, it introduces subpixel gaps along the
// corners. Those are avoided by insetting the clipping path by one pixel.
auto adjustedBorderShape = borderShape;
if (shouldInflateBorderRect())
adjustedBorderShape.inflate(-1_lu);
auto shadowRect = paintRect;
shadowRect.inflate(shadowPaintingExtent + shadowSpread);
shadowRect.move(shadowOffset);
if (!closedEdges.left())
shadowRect.shiftXEdgeTo(paintRect.x());
if (!closedEdges.top())
shadowRect.shiftYEdgeTo(paintRect.y());
if (!closedEdges.right())
shadowRect.shiftMaxXEdgeTo(paintRect.maxX());
if (!closedEdges.bottom())
shadowRect.shiftMaxYEdgeTo(paintRect.maxY());
auto pixelSnappedShadowRect = snapRectToDevicePixels(shadowRect, deviceScaleFactor);
GraphicsContextStateSaver stateSaver(context);
context.clip(pixelSnappedShadowRect);
// Move the fill just outside the clip, adding at least 1 pixel of separation so that the fill does not
// bleed in (due to antialiasing) if the context is transformed.
LayoutUnit xOffset = paintRect.width() + std::max<LayoutUnit>(0, shadowOffset.width()) + shadowPaintingExtent + 2 * shadowSpread + LayoutUnit(1);
LayoutSize extraOffset(xOffset.ceil(), 0);
shadowOffset -= extraOffset;
shadowShape.move(extraOffset);
auto pixelSnappedFillRect = shadowShape.snappedOuterRect(deviceScaleFactor);
LayoutPoint shadowRectOrigin = shadowShape.borderRect().location() + shadowOffset;
FloatPoint snappedShadowOrigin = FloatPoint(roundToDevicePixel(shadowRectOrigin.x(), deviceScaleFactor), roundToDevicePixel(shadowRectOrigin.y(), deviceScaleFactor));
FloatSize snappedShadowOffset = snappedShadowOrigin - pixelSnappedFillRect.location();
context.setDropShadow({ snappedShadowOffset, shadowRadius, shadowColor, shadow.isWebkitBoxShadow ? ShadowRadiusMode::Legacy : ShadowRadiusMode::Default });
adjustedBorderShape.clipOutOuterShape(context, deviceScaleFactor);
if (hasBorderRadius) {
auto influenceShape = BorderShape::shapeForBorderRect(style, shadowRect);
auto influenceRadii = influenceShape.radii();
influenceRadii.expand(2 * shadowPaintingExtent + shadowSpread);
influenceShape.setRadii(influenceRadii);
if (influenceShape.outerShapeContains(m_paintInfo.rect))
context.fillRect(shadowShape.snappedOuterRect(deviceScaleFactor), Color::black);
else
shadowShape.fillOuterShape(context, Color::black, deviceScaleFactor);
} else
context.fillRect(pixelSnappedFillRect, Color::black);
} else {
// Inset shadow.
auto borderWidthsWithSpread = borderShape.borderWidths() + RectEdges<LayoutUnit> { shadowSpread };
auto outerRectExpandedToObscureOpenEdges = paintRect;
auto shadowInfluence = shadowPaintingExtent + shadowSpread;
if (!closedEdges.left())
outerRectExpandedToObscureOpenEdges.shiftXEdgeBy(-(std::max<LayoutUnit>(shadowOffset.width(), 0) + shadowInfluence));
if (!closedEdges.top())
outerRectExpandedToObscureOpenEdges.shiftYEdgeBy(-(std::max<LayoutUnit>(shadowOffset.height(), 0) + shadowInfluence));
if (!closedEdges.right())
outerRectExpandedToObscureOpenEdges.setWidth(outerRectExpandedToObscureOpenEdges.width() - std::min<LayoutUnit>(shadowOffset.width(), 0) + shadowInfluence);
if (!closedEdges.bottom())
outerRectExpandedToObscureOpenEdges.setHeight(outerRectExpandedToObscureOpenEdges.height() - std::min<LayoutUnit>(shadowOffset.height(), 0) + shadowInfluence);
auto shapeForInnerHole = BorderShape(outerRectExpandedToObscureOpenEdges, borderWidthsWithSpread, borderShape.radii());
if (shapeForInnerHole.snappedInnerRect(deviceScaleFactor).isEmpty()) {
shapeForInnerHole.fillOuterShape(context, shadowColor, deviceScaleFactor);
continue;
}
auto areaCastingShadowInHole = [](const LayoutRect& holeRect, LayoutUnit shadowExtent, LayoutUnit shadowSpread, const LayoutSize& shadowOffset) {
auto bounds = holeRect;
bounds.inflate(shadowExtent);
if (shadowSpread < 0)
bounds.inflate(-shadowSpread);
LayoutRect offsetBounds = bounds;
offsetBounds.move(-shadowOffset);
return unionRect(bounds, offsetBounds);
};
auto fillColor = shadowColor.opaqueColor();
auto shadowCastingRect = areaCastingShadowInHole(borderShape.innerEdgeRect(), shadowPaintingExtent, shadowSpread, shadowOffset);
GraphicsContextStateSaver stateSaver(context);
borderShape.clipToInnerShape(context, deviceScaleFactor);
LayoutUnit xOffset = 2 * paintRect.width() + std::max<LayoutUnit>(0, shadowOffset.width()) + shadowPaintingExtent - 2 * shadowSpread + LayoutUnit(1);
LayoutSize extraOffset(xOffset.ceil(), 0);
context.translate(extraOffset);
shadowOffset -= extraOffset;
auto snappedShadowOffset = roundSizeToDevicePixels(shadowOffset, deviceScaleFactor);
context.setDropShadow({ snappedShadowOffset, shadowRadius, shadowColor, shadow.isWebkitBoxShadow ? ShadowRadiusMode::Legacy : ShadowRadiusMode::Default });
shapeForInnerHole.fillRectWithInnerHoleShape(context, shadowCastingRect, fillColor, deviceScaleFactor);
}
}
}
bool BackgroundPainter::boxShadowShouldBeAppliedToBackground(const RenderBoxModelObject& renderer, const LayoutPoint& paintOffset, BleedAvoidance bleedAvoidance, const InlineIterator::InlineBoxIterator& inlineBox)
{
if (bleedAvoidance != BleedAvoidance::None)
return false;
auto& style = renderer.style();
if (style.hasUsedAppearance())
return false;
bool hasOneNormalBoxShadow = false;
for (const auto& currentShadow : style.boxShadow()) {
if (Style::isInset(currentShadow))
continue;
if (hasOneNormalBoxShadow)
return false;
hasOneNormalBoxShadow = true;
if (!Style::isZero(currentShadow.spread))
return false;
}
if (!hasOneNormalBoxShadow)
return false;
Color backgroundColor = style.visitedDependentBackgroundColorApplyingColorFilter();
if (!backgroundColor.isOpaque())
return false;
auto& lastBackgroundLayer = style.backgroundLayers().usedLast();
if (lastBackgroundLayer.clip() != FillBox::BorderBox)
return false;
RefPtr image = lastBackgroundLayer.image().tryStyleImage();
if (image && style.hasBorderRadius())
return false;
auto applyToInlineBox = [&] {
// The checks here match how paintFillLayer() decides whether to clip (if it does, the shadow
// would be clipped out, so it has to be drawn separately).
if (inlineBox->isRootInlineBox())
return true;
if (!inlineBox->nextInlineBoxLineLeftward() && !inlineBox->nextInlineBoxLineRightward())
return true;
auto& renderer = inlineBox->renderer();
bool hasFillImage = image && image->canRender(&renderer, renderer.style().usedZoom());
return !hasFillImage && !renderer.style().hasBorderRadius();
};
if (inlineBox && !applyToInlineBox())
return false;
if (renderer.hasNonVisibleOverflow() && lastBackgroundLayer.attachment() == FillAttachment::LocalBackground)
return false;
if (is<RenderTableCell>(renderer))
return false;
if (auto* imageRenderer = dynamicDowncast<RenderImage>(renderer))
return !const_cast<RenderImage&>(*imageRenderer).backgroundIsKnownToBeObscured(paintOffset);
return true;
}
const Document& BackgroundPainter::document() const
{
return m_renderer.document();
}
const RenderView& BackgroundPainter::view() const
{
return m_renderer.view();
}
}