gecko-dev/layout/painting/nsCSSRendering.cpp

4897 строки
188 KiB
C++
Исходник Ответственный История

Этот файл содержит неоднозначные символы Юникода!

Этот файл содержит неоднозначные символы Юникода, которые могут быть перепутаны с другими в текущей локали. Если это намеренно, можете спокойно проигнорировать это предупреждение. Используйте кнопку Экранировать, чтобы подсветить эти символы.

/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/* utility functions for drawing borders and backgrounds */
#include <ctime>
#include "gfx2DGlue.h"
#include "gfxContext.h"
#include "mozilla/ArrayUtils.h"
#include "mozilla/ComputedStyle.h"
#include "mozilla/DebugOnly.h"
#include "mozilla/StaticPrefs_layout.h"
#include "mozilla/gfx/2D.h"
#include "mozilla/gfx/Helpers.h"
#include "mozilla/gfx/PathHelpers.h"
#include "mozilla/HashFunctions.h"
#include "mozilla/MathAlgorithms.h"
#include "mozilla/PresShell.h"
#include "gfxFont.h"
#include "ScaledFontBase.h"
#include "skia/include/core/SkTextBlob.h"
#include "BorderConsts.h"
#include "nsStyleConsts.h"
#include "nsPresContext.h"
#include "nsIFrame.h"
#include "nsIFrameInlines.h"
#include "nsPoint.h"
#include "nsRect.h"
#include "nsFrameManager.h"
#include "nsGkAtoms.h"
#include "nsCSSAnonBoxes.h"
#include "nsIContent.h"
#include "mozilla/dom/DocumentInlines.h"
#include "nsIScrollableFrame.h"
#include "imgIContainer.h"
#include "ImageOps.h"
#include "nsCSSRendering.h"
#include "nsCSSColorUtils.h"
#include "nsITheme.h"
#include "nsLayoutUtils.h"
#include "nsBlockFrame.h"
#include "nsStyleStructInlines.h"
#include "nsCSSFrameConstructor.h"
#include "nsCSSProps.h"
#include "nsContentUtils.h"
#include "SVGObserverUtils.h"
#include "nsSVGIntegrationUtils.h"
#include "gfxDrawable.h"
#include "GeckoProfiler.h"
#include "nsCSSRenderingBorders.h"
#include "mozilla/css/ImageLoader.h"
#include "ImageContainer.h"
#include "mozilla/StaticPrefs_layout.h"
#include "mozilla/Telemetry.h"
#include "gfxUtils.h"
#include "gfxGradientCache.h"
#include "nsInlineFrame.h"
#include "nsRubyTextContainerFrame.h"
#include <algorithm>
#include "SVGImageContext.h"
#include "TextDrawTarget.h"
using namespace mozilla;
using namespace mozilla::css;
using namespace mozilla::gfx;
using namespace mozilla::image;
using mozilla::CSSSizeOrRatio;
using mozilla::dom::Document;
static int gFrameTreeLockCount = 0;
// To avoid storing this data on nsInlineFrame (bloat) and to avoid
// recalculating this for each frame in a continuation (perf), hold
// a cache of various coordinate information that we need in order
// to paint inline backgrounds.
struct InlineBackgroundData {
InlineBackgroundData()
: mFrame(nullptr),
mLineContainer(nullptr),
mContinuationPoint(0),
mUnbrokenMeasure(0),
mLineContinuationPoint(0),
mPIStartBorderData{},
mBidiEnabled(false),
mVertical(false) {}
~InlineBackgroundData() = default;
void Reset() {
mBoundingBox.SetRect(0, 0, 0, 0);
mContinuationPoint = mLineContinuationPoint = mUnbrokenMeasure = 0;
mFrame = mLineContainer = nullptr;
mPIStartBorderData.Reset();
}
/**
* Return a continuous rect for (an inline) aFrame relative to the
* continuation that draws the left-most part of the background.
* This is used when painting backgrounds.
*/
nsRect GetContinuousRect(nsIFrame* aFrame) {
MOZ_ASSERT(static_cast<nsInlineFrame*>(do_QueryFrame(aFrame)));
SetFrame(aFrame);
nscoord pos; // an x coordinate if writing-mode is horizontal;
// y coordinate if vertical
if (mBidiEnabled) {
pos = mLineContinuationPoint;
// Scan continuations on the same line as aFrame and accumulate the widths
// of frames that are to the left (if this is an LTR block) or right
// (if it's RTL) of the current one.
bool isRtlBlock = (mLineContainer->StyleVisibility()->mDirection ==
StyleDirection::Rtl);
nscoord curOffset = mVertical ? aFrame->GetOffsetTo(mLineContainer).y
: aFrame->GetOffsetTo(mLineContainer).x;
// If the continuation is fluid we know inlineFrame is not on the same
// line. If it's not fluid, we need to test further to be sure.
nsIFrame* inlineFrame = aFrame->GetPrevContinuation();
while (inlineFrame && !inlineFrame->GetNextInFlow() &&
AreOnSameLine(aFrame, inlineFrame)) {
nscoord frameOffset = mVertical
? inlineFrame->GetOffsetTo(mLineContainer).y
: inlineFrame->GetOffsetTo(mLineContainer).x;
if (isRtlBlock == (frameOffset >= curOffset)) {
pos += mVertical ? inlineFrame->GetSize().height
: inlineFrame->GetSize().width;
}
inlineFrame = inlineFrame->GetPrevContinuation();
}
inlineFrame = aFrame->GetNextContinuation();
while (inlineFrame && !inlineFrame->GetPrevInFlow() &&
AreOnSameLine(aFrame, inlineFrame)) {
nscoord frameOffset = mVertical
? inlineFrame->GetOffsetTo(mLineContainer).y
: inlineFrame->GetOffsetTo(mLineContainer).x;
if (isRtlBlock == (frameOffset >= curOffset)) {
pos += mVertical ? inlineFrame->GetSize().height
: inlineFrame->GetSize().width;
}
inlineFrame = inlineFrame->GetNextContinuation();
}
if (isRtlBlock) {
// aFrame itself is also to the right of its left edge, so add its
// width.
pos += mVertical ? aFrame->GetSize().height : aFrame->GetSize().width;
// pos is now the distance from the left [top] edge of aFrame to the
// right [bottom] edge of the unbroken content. Change it to indicate
// the distance from the left [top] edge of the unbroken content to the
// left [top] edge of aFrame.
pos = mUnbrokenMeasure - pos;
}
} else {
pos = mContinuationPoint;
}
// Assume background-origin: border and return a rect with offsets
// relative to (0,0). If we have a different background-origin,
// then our rect should be deflated appropriately by our caller.
return mVertical
? nsRect(0, -pos, mFrame->GetSize().width, mUnbrokenMeasure)
: nsRect(-pos, 0, mUnbrokenMeasure, mFrame->GetSize().height);
}
/**
* Return a continuous rect for (an inline) aFrame relative to the
* continuation that should draw the left[top]-border. This is used when
* painting borders and clipping backgrounds. This may NOT be the same
* continuous rect as for drawing backgrounds; the continuation with the
* left[top]-border might be somewhere in the middle of that rect (e.g. BIDI),
* in those cases we need the reverse background order starting at the
* left[top]-border continuation.
*/
nsRect GetBorderContinuousRect(nsIFrame* aFrame, nsRect aBorderArea) {
// Calling GetContinuousRect(aFrame) here may lead to Reset/Init which
// resets our mPIStartBorderData so we save it ...
PhysicalInlineStartBorderData saved(mPIStartBorderData);
nsRect joinedBorderArea = GetContinuousRect(aFrame);
if (!saved.mIsValid || saved.mFrame != mPIStartBorderData.mFrame) {
if (aFrame == mPIStartBorderData.mFrame) {
if (mVertical) {
mPIStartBorderData.SetCoord(joinedBorderArea.y);
} else {
mPIStartBorderData.SetCoord(joinedBorderArea.x);
}
} else if (mPIStartBorderData.mFrame) {
// Copy data to a temporary object so that computing the
// continous rect here doesn't clobber our normal state.
InlineBackgroundData temp = *this;
if (mVertical) {
mPIStartBorderData.SetCoord(
temp.GetContinuousRect(mPIStartBorderData.mFrame).y);
} else {
mPIStartBorderData.SetCoord(
temp.GetContinuousRect(mPIStartBorderData.mFrame).x);
}
}
} else {
// ... and restore it when possible.
mPIStartBorderData.SetCoord(saved.mCoord);
}
if (mVertical) {
if (joinedBorderArea.y > mPIStartBorderData.mCoord) {
joinedBorderArea.y =
-(mUnbrokenMeasure + joinedBorderArea.y - aBorderArea.height);
} else {
joinedBorderArea.y -= mPIStartBorderData.mCoord;
}
} else {
if (joinedBorderArea.x > mPIStartBorderData.mCoord) {
joinedBorderArea.x =
-(mUnbrokenMeasure + joinedBorderArea.x - aBorderArea.width);
} else {
joinedBorderArea.x -= mPIStartBorderData.mCoord;
}
}
return joinedBorderArea;
}
nsRect GetBoundingRect(nsIFrame* aFrame) {
SetFrame(aFrame);
// Move the offsets relative to (0,0) which puts the bounding box into
// our coordinate system rather than our parent's. We do this by
// moving it the back distance from us to the bounding box.
// This also assumes background-origin: border, so our caller will
// need to deflate us if needed.
nsRect boundingBox(mBoundingBox);
nsPoint point = mFrame->GetPosition();
boundingBox.MoveBy(-point.x, -point.y);
return boundingBox;
}
protected:
// This is a coordinate on the inline axis, but is not a true logical inline-
// coord because it is always measured from left to right (if horizontal) or
// from top to bottom (if vertical), ignoring any bidi RTL directionality.
// We'll call this "physical inline start", or PIStart for short.
struct PhysicalInlineStartBorderData {
nsIFrame* mFrame; // the continuation that may have a left-border
nscoord mCoord; // cached GetContinuousRect(mFrame).x or .y
bool mIsValid; // true if mCoord is valid
void Reset() {
mFrame = nullptr;
mIsValid = false;
}
void SetCoord(nscoord aCoord) {
mCoord = aCoord;
mIsValid = true;
}
};
nsIFrame* mFrame;
nsIFrame* mLineContainer;
nsRect mBoundingBox;
nscoord mContinuationPoint;
nscoord mUnbrokenMeasure;
nscoord mLineContinuationPoint;
PhysicalInlineStartBorderData mPIStartBorderData;
bool mBidiEnabled;
bool mVertical;
void SetFrame(nsIFrame* aFrame) {
MOZ_ASSERT(aFrame, "Need a frame");
NS_ASSERTION(gFrameTreeLockCount > 0,
"Can't call this when frame tree is not locked");
if (aFrame == mFrame) {
return;
}
nsIFrame* prevContinuation = GetPrevContinuation(aFrame);
if (!prevContinuation || mFrame != prevContinuation) {
// Ok, we've got the wrong frame. We have to start from scratch.
Reset();
Init(aFrame);
return;
}
// Get our last frame's size and add its width to our continuation
// point before we cache the new frame.
mContinuationPoint +=
mVertical ? mFrame->GetSize().height : mFrame->GetSize().width;
// If this a new line, update mLineContinuationPoint.
if (mBidiEnabled &&
(aFrame->GetPrevInFlow() || !AreOnSameLine(mFrame, aFrame))) {
mLineContinuationPoint = mContinuationPoint;
}
mFrame = aFrame;
}
nsIFrame* GetPrevContinuation(nsIFrame* aFrame) {
nsIFrame* prevCont = aFrame->GetPrevContinuation();
if (!prevCont && (aFrame->GetStateBits() & NS_FRAME_PART_OF_IBSPLIT)) {
nsIFrame* block = aFrame->GetProperty(nsIFrame::IBSplitPrevSibling());
if (block) {
// The {ib} properties are only stored on first continuations
NS_ASSERTION(!block->GetPrevContinuation(),
"Incorrect value for IBSplitPrevSibling");
prevCont = block->GetProperty(nsIFrame::IBSplitPrevSibling());
NS_ASSERTION(prevCont, "How did that happen?");
}
}
return prevCont;
}
nsIFrame* GetNextContinuation(nsIFrame* aFrame) {
nsIFrame* nextCont = aFrame->GetNextContinuation();
if (!nextCont && (aFrame->GetStateBits() & NS_FRAME_PART_OF_IBSPLIT)) {
// The {ib} properties are only stored on first continuations
aFrame = aFrame->FirstContinuation();
nsIFrame* block = aFrame->GetProperty(nsIFrame::IBSplitSibling());
if (block) {
nextCont = block->GetProperty(nsIFrame::IBSplitSibling());
NS_ASSERTION(nextCont, "How did that happen?");
}
}
return nextCont;
}
void Init(nsIFrame* aFrame) {
mPIStartBorderData.Reset();
mBidiEnabled = aFrame->PresContext()->BidiEnabled();
if (mBidiEnabled) {
// Find the line container frame
mLineContainer = aFrame;
while (mLineContainer &&
mLineContainer->IsFrameOfType(nsIFrame::eLineParticipant)) {
mLineContainer = mLineContainer->GetParent();
}
MOZ_ASSERT(mLineContainer, "Cannot find line containing frame.");
MOZ_ASSERT(mLineContainer != aFrame,
"line container frame "
"should be an ancestor of the target frame.");
}
mVertical = aFrame->GetWritingMode().IsVertical();
// Start with the previous flow frame as our continuation point
// is the total of the widths of the previous frames.
nsIFrame* inlineFrame = GetPrevContinuation(aFrame);
bool changedLines = false;
while (inlineFrame) {
if (!mPIStartBorderData.mFrame &&
!(mVertical ? inlineFrame->GetSkipSides().Top()
: inlineFrame->GetSkipSides().Left())) {
mPIStartBorderData.mFrame = inlineFrame;
}
nsRect rect = inlineFrame->GetRect();
mContinuationPoint += mVertical ? rect.height : rect.width;
if (mBidiEnabled &&
(changedLines || !AreOnSameLine(aFrame, inlineFrame))) {
mLineContinuationPoint += mVertical ? rect.height : rect.width;
changedLines = true;
}
mUnbrokenMeasure += mVertical ? rect.height : rect.width;
mBoundingBox.UnionRect(mBoundingBox, rect);
inlineFrame = GetPrevContinuation(inlineFrame);
}
// Next add this frame and subsequent frames to the bounding box and
// unbroken width.
inlineFrame = aFrame;
while (inlineFrame) {
if (!mPIStartBorderData.mFrame &&
!(mVertical ? inlineFrame->GetSkipSides().Top()
: inlineFrame->GetSkipSides().Left())) {
mPIStartBorderData.mFrame = inlineFrame;
}
nsRect rect = inlineFrame->GetRect();
mUnbrokenMeasure += mVertical ? rect.height : rect.width;
mBoundingBox.UnionRect(mBoundingBox, rect);
inlineFrame = GetNextContinuation(inlineFrame);
}
mFrame = aFrame;
}
bool AreOnSameLine(nsIFrame* aFrame1, nsIFrame* aFrame2) {
if (nsBlockFrame* blockFrame = do_QueryFrame(mLineContainer)) {
bool isValid1, isValid2;
nsBlockInFlowLineIterator it1(blockFrame, aFrame1, &isValid1);
nsBlockInFlowLineIterator it2(blockFrame, aFrame2, &isValid2);
return isValid1 && isValid2 &&
// Make sure aFrame1 and aFrame2 are in the same continuation of
// blockFrame.
it1.GetContainer() == it2.GetContainer() &&
// And on the same line in it
it1.GetLine().get() == it2.GetLine().get();
}
if (nsRubyTextContainerFrame* rtcFrame = do_QueryFrame(mLineContainer)) {
nsBlockFrame* block = nsLayoutUtils::FindNearestBlockAncestor(rtcFrame);
// Ruby text container can only hold one line of text, so if they
// are in the same continuation, they are in the same line. Since
// ruby text containers are bidi isolate, they are never split for
// bidi reordering, which means being in different continuation
// indicates being in different lines.
for (nsIFrame* frame = rtcFrame->FirstContinuation(); frame;
frame = frame->GetNextContinuation()) {
bool isDescendant1 =
nsLayoutUtils::IsProperAncestorFrame(frame, aFrame1, block);
bool isDescendant2 =
nsLayoutUtils::IsProperAncestorFrame(frame, aFrame2, block);
if (isDescendant1 && isDescendant2) {
return true;
}
if (isDescendant1 || isDescendant2) {
return false;
}
}
MOZ_ASSERT_UNREACHABLE("None of the frames is a descendant of this rtc?");
}
MOZ_ASSERT_UNREACHABLE("Do we have any other type of line container?");
return false;
}
};
static InlineBackgroundData* gInlineBGData = nullptr;
// Initialize any static variables used by nsCSSRendering.
void nsCSSRendering::Init() {
NS_ASSERTION(!gInlineBGData, "Init called twice");
gInlineBGData = new InlineBackgroundData();
}
// Clean up any global variables used by nsCSSRendering.
void nsCSSRendering::Shutdown() {
delete gInlineBGData;
gInlineBGData = nullptr;
}
/**
* Make a bevel color
*/
static nscolor MakeBevelColor(mozilla::Side whichSide, StyleBorderStyle style,
nscolor aBorderColor) {
nscolor colors[2];
nscolor theColor;
// Given a background color and a border color
// calculate the color used for the shading
NS_GetSpecial3DColors(colors, aBorderColor);
if ((style == StyleBorderStyle::Outset) ||
(style == StyleBorderStyle::Ridge)) {
// Flip colors for these two border styles
switch (whichSide) {
case eSideBottom:
whichSide = eSideTop;
break;
case eSideRight:
whichSide = eSideLeft;
break;
case eSideTop:
whichSide = eSideBottom;
break;
case eSideLeft:
whichSide = eSideRight;
break;
}
}
switch (whichSide) {
case eSideBottom:
theColor = colors[1];
break;
case eSideRight:
theColor = colors[1];
break;
case eSideTop:
theColor = colors[0];
break;
case eSideLeft:
default:
theColor = colors[0];
break;
}
return theColor;
}
static bool GetRadii(nsIFrame* aForFrame, const nsStyleBorder& aBorder,
const nsRect& aOrigBorderArea, const nsRect& aBorderArea,
nscoord aRadii[8]) {
bool haveRoundedCorners;
nsSize sz = aBorderArea.Size();
nsSize frameSize = aForFrame->GetSize();
if (&aBorder == aForFrame->StyleBorder() &&
frameSize == aOrigBorderArea.Size()) {
haveRoundedCorners = aForFrame->GetBorderRadii(sz, sz, Sides(), aRadii);
} else {
haveRoundedCorners = nsIFrame::ComputeBorderRadii(
aBorder.mBorderRadius, frameSize, sz, Sides(), aRadii);
}
return haveRoundedCorners;
}
static bool GetRadii(nsIFrame* aForFrame, const nsStyleBorder& aBorder,
const nsRect& aOrigBorderArea, const nsRect& aBorderArea,
RectCornerRadii* aBgRadii) {
nscoord radii[8];
bool haveRoundedCorners =
GetRadii(aForFrame, aBorder, aOrigBorderArea, aBorderArea, radii);
if (haveRoundedCorners) {
auto d2a = aForFrame->PresContext()->AppUnitsPerDevPixel();
nsCSSRendering::ComputePixelRadii(radii, d2a, aBgRadii);
}
return haveRoundedCorners;
}
static nsRect JoinBoxesForBlockAxisSlice(nsIFrame* aFrame,
const nsRect& aBorderArea) {
// Inflate the block-axis size as if our continuations were laid out
// adjacent in that axis. Note that we don't touch the inline size.
nsRect borderArea = aBorderArea;
nscoord bSize = 0;
auto wm = aFrame->GetWritingMode();
nsIFrame* f = aFrame->GetNextContinuation();
for (; f; f = f->GetNextContinuation()) {
bSize += f->BSize(wm);
}
(wm.IsVertical() ? borderArea.width : borderArea.height) += bSize;
bSize = 0;
f = aFrame->GetPrevContinuation();
for (; f; f = f->GetPrevContinuation()) {
bSize += f->BSize(wm);
}
(wm.IsVertical() ? borderArea.x : borderArea.y) -= bSize;
(wm.IsVertical() ? borderArea.width : borderArea.height) += bSize;
return borderArea;
}
/**
* Inflate aBorderArea which is relative to aFrame's origin to calculate
* a hypothetical non-split frame area for all the continuations.
* See "Joining Boxes for 'slice'" in
* http://dev.w3.org/csswg/css-break/#break-decoration
*/
enum InlineBoxOrder { eForBorder, eForBackground };
static nsRect JoinBoxesForSlice(nsIFrame* aFrame, const nsRect& aBorderArea,
InlineBoxOrder aOrder) {
if (static_cast<nsInlineFrame*>(do_QueryFrame(aFrame))) {
return (aOrder == eForBorder
? gInlineBGData->GetBorderContinuousRect(aFrame, aBorderArea)
: gInlineBGData->GetContinuousRect(aFrame)) +
aBorderArea.TopLeft();
}
return JoinBoxesForBlockAxisSlice(aFrame, aBorderArea);
}
/* static */
bool nsCSSRendering::IsBoxDecorationSlice(const nsStyleBorder& aStyleBorder) {
return aStyleBorder.mBoxDecorationBreak == StyleBoxDecorationBreak::Slice;
}
/* static */
nsRect nsCSSRendering::BoxDecorationRectForBorder(
nsIFrame* aFrame, const nsRect& aBorderArea, Sides aSkipSides,
const nsStyleBorder* aStyleBorder) {
if (!aStyleBorder) {
aStyleBorder = aFrame->StyleBorder();
}
// If aSkipSides.IsEmpty() then there are no continuations, or it's
// a ::first-letter that wants all border sides on the first continuation.
return IsBoxDecorationSlice(*aStyleBorder) && !aSkipSides.IsEmpty()
? ::JoinBoxesForSlice(aFrame, aBorderArea, eForBorder)
: aBorderArea;
}
/* static */
nsRect nsCSSRendering::BoxDecorationRectForBackground(
nsIFrame* aFrame, const nsRect& aBorderArea, Sides aSkipSides,
const nsStyleBorder* aStyleBorder) {
if (!aStyleBorder) {
aStyleBorder = aFrame->StyleBorder();
}
// If aSkipSides.IsEmpty() then there are no continuations, or it's
// a ::first-letter that wants all border sides on the first continuation.
return IsBoxDecorationSlice(*aStyleBorder) && !aSkipSides.IsEmpty()
? ::JoinBoxesForSlice(aFrame, aBorderArea, eForBackground)
: aBorderArea;
}
//----------------------------------------------------------------------
// Thebes Border Rendering Code Start
/*
* Compute the float-pixel radii that should be used for drawing
* this border/outline, given the various input bits.
*/
/* static */
void nsCSSRendering::ComputePixelRadii(const nscoord* aAppUnitsRadii,
nscoord aAppUnitsPerPixel,
RectCornerRadii* oBorderRadii) {
Float radii[8];
for (const auto corner : mozilla::AllPhysicalHalfCorners()) {
radii[corner] = Float(aAppUnitsRadii[corner]) / aAppUnitsPerPixel;
}
(*oBorderRadii)[C_TL] = Size(radii[eCornerTopLeftX], radii[eCornerTopLeftY]);
(*oBorderRadii)[C_TR] =
Size(radii[eCornerTopRightX], radii[eCornerTopRightY]);
(*oBorderRadii)[C_BR] =
Size(radii[eCornerBottomRightX], radii[eCornerBottomRightY]);
(*oBorderRadii)[C_BL] =
Size(radii[eCornerBottomLeftX], radii[eCornerBottomLeftY]);
}
static Maybe<nsStyleBorder> GetBorderIfVisited(const ComputedStyle& aStyle) {
Maybe<nsStyleBorder> result;
// Don't check RelevantLinkVisited here, since we want to take the
// same amount of time whether or not it's true.
const ComputedStyle* styleIfVisited = aStyle.GetStyleIfVisited();
if (MOZ_LIKELY(!styleIfVisited)) {
return result;
}
result.emplace(*aStyle.StyleBorder());
auto& newBorder = result.ref();
for (const auto side : mozilla::AllPhysicalSides()) {
nscolor color = aStyle.GetVisitedDependentColor(
nsStyleBorder::BorderColorFieldFor(side));
newBorder.BorderColorFor(side) = StyleColor::FromColor(color);
}
return result;
}
ImgDrawResult nsCSSRendering::PaintBorder(
nsPresContext* aPresContext, gfxContext& aRenderingContext,
nsIFrame* aForFrame, const nsRect& aDirtyRect, const nsRect& aBorderArea,
ComputedStyle* aStyle, PaintBorderFlags aFlags, Sides aSkipSides) {
AUTO_PROFILER_LABEL("nsCSSRendering::PaintBorder", GRAPHICS);
Maybe<nsStyleBorder> visitedBorder = GetBorderIfVisited(*aStyle);
return PaintBorderWithStyleBorder(
aPresContext, aRenderingContext, aForFrame, aDirtyRect, aBorderArea,
visitedBorder.refOr(*aStyle->StyleBorder()), aStyle, aFlags, aSkipSides);
}
Maybe<nsCSSBorderRenderer> nsCSSRendering::CreateBorderRenderer(
nsPresContext* aPresContext, DrawTarget* aDrawTarget, nsIFrame* aForFrame,
const nsRect& aDirtyRect, const nsRect& aBorderArea, ComputedStyle* aStyle,
bool* aOutBorderIsEmpty, Sides aSkipSides) {
Maybe<nsStyleBorder> visitedBorder = GetBorderIfVisited(*aStyle);
return CreateBorderRendererWithStyleBorder(
aPresContext, aDrawTarget, aForFrame, aDirtyRect, aBorderArea,
visitedBorder.refOr(*aStyle->StyleBorder()), aStyle, aOutBorderIsEmpty,
aSkipSides);
}
ImgDrawResult nsCSSRendering::CreateWebRenderCommandsForBorder(
nsDisplayItem* aItem, nsIFrame* aForFrame, const nsRect& aBorderArea,
mozilla::wr::DisplayListBuilder& aBuilder,
mozilla::wr::IpcResourceUpdateQueue& aResources,
const mozilla::layers::StackingContextHelper& aSc,
mozilla::layers::RenderRootStateManager* aManager,
nsDisplayListBuilder* aDisplayListBuilder) {
const auto* style = aForFrame->Style();
Maybe<nsStyleBorder> visitedBorder = GetBorderIfVisited(*style);
return nsCSSRendering::CreateWebRenderCommandsForBorderWithStyleBorder(
aItem, aForFrame, aBorderArea, aBuilder, aResources, aSc, aManager,
aDisplayListBuilder, visitedBorder.refOr(*style->StyleBorder()));
}
void nsCSSRendering::CreateWebRenderCommandsForNullBorder(
nsDisplayItem* aItem, nsIFrame* aForFrame, const nsRect& aBorderArea,
mozilla::wr::DisplayListBuilder& aBuilder,
mozilla::wr::IpcResourceUpdateQueue& aResources,
const mozilla::layers::StackingContextHelper& aSc,
const nsStyleBorder& aStyleBorder) {
bool borderIsEmpty = false;
Maybe<nsCSSBorderRenderer> br =
nsCSSRendering::CreateNullBorderRendererWithStyleBorder(
aForFrame->PresContext(), nullptr, aForFrame, nsRect(), aBorderArea,
aStyleBorder, aForFrame->Style(), &borderIsEmpty,
aForFrame->GetSkipSides());
if (!borderIsEmpty && br) {
br->CreateWebRenderCommands(aItem, aBuilder, aResources, aSc);
}
}
ImgDrawResult nsCSSRendering::CreateWebRenderCommandsForBorderWithStyleBorder(
nsDisplayItem* aItem, nsIFrame* aForFrame, const nsRect& aBorderArea,
mozilla::wr::DisplayListBuilder& aBuilder,
mozilla::wr::IpcResourceUpdateQueue& aResources,
const mozilla::layers::StackingContextHelper& aSc,
mozilla::layers::RenderRootStateManager* aManager,
nsDisplayListBuilder* aDisplayListBuilder,
const nsStyleBorder& aStyleBorder) {
auto& borderImage = aStyleBorder.mBorderImageSource;
// First try to create commands for simple borders.
if (borderImage.IsNone()) {
CreateWebRenderCommandsForNullBorder(
aItem, aForFrame, aBorderArea, aBuilder, aResources, aSc, aStyleBorder);
return ImgDrawResult::SUCCESS;
}
// Next we try image and gradient borders. Gradients are not supported at
// this very moment.
if (!borderImage.IsImageRequestType()) {
return ImgDrawResult::NOT_SUPPORTED;
}
if (aStyleBorder.mBorderImageRepeatH == StyleBorderImageRepeat::Space ||
aStyleBorder.mBorderImageRepeatV == StyleBorderImageRepeat::Space) {
return ImgDrawResult::NOT_SUPPORTED;
}
uint32_t flags = 0;
if (aDisplayListBuilder->IsPaintingToWindow()) {
flags |= nsImageRenderer::FLAG_PAINTING_TO_WINDOW;
}
if (aDisplayListBuilder->ShouldSyncDecodeImages()) {
flags |= nsImageRenderer::FLAG_SYNC_DECODE_IMAGES;
}
image::ImgDrawResult result;
Maybe<nsCSSBorderImageRenderer> bir =
nsCSSBorderImageRenderer::CreateBorderImageRenderer(
aForFrame->PresContext(), aForFrame, aBorderArea, aStyleBorder,
aItem->GetPaintRect(), aForFrame->GetSkipSides(), flags, &result);
if (!bir) {
// We aren't ready. Try to fallback to the null border image if present but
// return the draw result for the border image renderer.
CreateWebRenderCommandsForNullBorder(
aItem, aForFrame, aBorderArea, aBuilder, aResources, aSc, aStyleBorder);
return result;
}
return bir->CreateWebRenderCommands(aItem, aForFrame, aBuilder, aResources,
aSc, aManager, aDisplayListBuilder);
}
static nsCSSBorderRenderer ConstructBorderRenderer(
nsPresContext* aPresContext, ComputedStyle* aStyle, DrawTarget* aDrawTarget,
nsIFrame* aForFrame, const nsRect& aDirtyRect, const nsRect& aBorderArea,
const nsStyleBorder& aStyleBorder, Sides aSkipSides, bool* aNeedsClip) {
nsMargin border = aStyleBorder.GetComputedBorder();
// Compute the outermost boundary of the area that might be painted.
// Same coordinate space as aBorderArea & aBGClipRect.
nsRect joinedBorderArea = nsCSSRendering::BoxDecorationRectForBorder(
aForFrame, aBorderArea, aSkipSides, &aStyleBorder);
RectCornerRadii bgRadii;
::GetRadii(aForFrame, aStyleBorder, aBorderArea, joinedBorderArea, &bgRadii);
PrintAsFormatString(" joinedBorderArea: %d %d %d %d\n", joinedBorderArea.x,
joinedBorderArea.y, joinedBorderArea.width,
joinedBorderArea.height);
// start drawing
if (nsCSSRendering::IsBoxDecorationSlice(aStyleBorder)) {
if (joinedBorderArea.IsEqualEdges(aBorderArea)) {
// No need for a clip, just skip the sides we don't want.
border.ApplySkipSides(aSkipSides);
} else {
// We're drawing borders around the joined continuation boxes so we need
// to clip that to the slice that we want for this frame.
*aNeedsClip = true;
}
} else {
MOZ_ASSERT(joinedBorderArea.IsEqualEdges(aBorderArea),
"Should use aBorderArea for box-decoration-break:clone");
MOZ_ASSERT(
aForFrame->GetSkipSides().IsEmpty() ||
IS_TRUE_OVERFLOW_CONTAINER(aForFrame) ||
aForFrame->IsColumnSetFrame(), // a little broader than column-rule
"Should not skip sides for box-decoration-break:clone except "
"::first-letter/line continuations or other frame types that "
"don't have borders but those shouldn't reach this point. "
"Overflow containers do reach this point though, as does "
"column-rule drawing (which always involves a columnset).");
border.ApplySkipSides(aSkipSides);
}
// Convert to dev pixels.
nscoord oneDevPixel = aPresContext->DevPixelsToAppUnits(1);
Rect joinedBorderAreaPx = NSRectToRect(joinedBorderArea, oneDevPixel);
Float borderWidths[4] = {
Float(border.top) / oneDevPixel, Float(border.right) / oneDevPixel,
Float(border.bottom) / oneDevPixel, Float(border.left) / oneDevPixel};
Rect dirtyRect = NSRectToRect(aDirtyRect, oneDevPixel);
StyleBorderStyle borderStyles[4];
nscolor borderColors[4];
// pull out styles, colors
for (const auto i : mozilla::AllPhysicalSides()) {
borderStyles[i] = aStyleBorder.GetBorderStyle(i);
borderColors[i] = aStyleBorder.BorderColorFor(i).CalcColor(*aStyle);
}
PrintAsFormatString(
" borderStyles: %d %d %d %d\n", static_cast<int>(borderStyles[0]),
static_cast<int>(borderStyles[1]), static_cast<int>(borderStyles[2]),
static_cast<int>(borderStyles[3]));
Document* document = nullptr;
nsIContent* content = aForFrame->GetContent();
if (content) {
document = content->OwnerDoc();
}
return nsCSSBorderRenderer(
aPresContext, document, aDrawTarget, dirtyRect, joinedBorderAreaPx,
borderStyles, borderWidths, bgRadii, borderColors,
!aForFrame->BackfaceIsHidden(),
*aNeedsClip ? Some(NSRectToRect(aBorderArea, oneDevPixel)) : Nothing());
}
ImgDrawResult nsCSSRendering::PaintBorderWithStyleBorder(
nsPresContext* aPresContext, gfxContext& aRenderingContext,
nsIFrame* aForFrame, const nsRect& aDirtyRect, const nsRect& aBorderArea,
const nsStyleBorder& aStyleBorder, ComputedStyle* aStyle,
PaintBorderFlags aFlags, Sides aSkipSides) {
DrawTarget& aDrawTarget = *aRenderingContext.GetDrawTarget();
PrintAsStringNewline("++ PaintBorder");
// Check to see if we have an appearance defined. If so, we let the theme
// renderer draw the border. DO not get the data from aForFrame, since the
// passed in ComputedStyle may be different! Always use |aStyle|!
const nsStyleDisplay* displayData = aStyle->StyleDisplay();
if (displayData->HasAppearance()) {
nsITheme* theme = aPresContext->GetTheme();
if (theme && theme->ThemeSupportsWidget(aPresContext, aForFrame,
displayData->mAppearance)) {
return ImgDrawResult::SUCCESS; // Let the theme handle it.
}
}
if (!aStyleBorder.mBorderImageSource.IsNone()) {
ImgDrawResult result = ImgDrawResult::SUCCESS;
uint32_t irFlags = 0;
if (aFlags & PaintBorderFlags::SyncDecodeImages) {
irFlags |= nsImageRenderer::FLAG_SYNC_DECODE_IMAGES;
}
// Creating the border image renderer will request a decode, and we rely on
// that happening.
Maybe<nsCSSBorderImageRenderer> renderer =
nsCSSBorderImageRenderer::CreateBorderImageRenderer(
aPresContext, aForFrame, aBorderArea, aStyleBorder, aDirtyRect,
aSkipSides, irFlags, &result);
// renderer was created successfully, which means border image is ready to
// be used.
if (renderer) {
MOZ_ASSERT(result == ImgDrawResult::SUCCESS);
return renderer->DrawBorderImage(aPresContext, aRenderingContext,
aForFrame, aDirtyRect);
}
}
ImgDrawResult result = ImgDrawResult::SUCCESS;
// If we had a border-image, but it wasn't loaded, then we should return
// ImgDrawResult::NOT_READY; we'll want to try again if we do a paint with
// sync decoding enabled.
if (!aStyleBorder.mBorderImageSource.IsNone()) {
result = ImgDrawResult::NOT_READY;
}
nsMargin border = aStyleBorder.GetComputedBorder();
if (0 == border.left && 0 == border.right && 0 == border.top &&
0 == border.bottom) {
// Empty border area
return result;
}
bool needsClip = false;
nsCSSBorderRenderer br = ConstructBorderRenderer(
aPresContext, aStyle, &aDrawTarget, aForFrame, aDirtyRect, aBorderArea,
aStyleBorder, aSkipSides, &needsClip);
if (needsClip) {
aDrawTarget.PushClipRect(NSRectToSnappedRect(
aBorderArea, aForFrame->PresContext()->AppUnitsPerDevPixel(),
aDrawTarget));
}
br.DrawBorders();
if (needsClip) {
aDrawTarget.PopClip();
}
PrintAsStringNewline();
return result;
}
Maybe<nsCSSBorderRenderer> nsCSSRendering::CreateBorderRendererWithStyleBorder(
nsPresContext* aPresContext, DrawTarget* aDrawTarget, nsIFrame* aForFrame,
const nsRect& aDirtyRect, const nsRect& aBorderArea,
const nsStyleBorder& aStyleBorder, ComputedStyle* aStyle,
bool* aOutBorderIsEmpty, Sides aSkipSides) {
if (!aStyleBorder.mBorderImageSource.IsNone()) {
return Nothing();
}
return CreateNullBorderRendererWithStyleBorder(
aPresContext, aDrawTarget, aForFrame, aDirtyRect, aBorderArea,
aStyleBorder, aStyle, aOutBorderIsEmpty, aSkipSides);
}
Maybe<nsCSSBorderRenderer>
nsCSSRendering::CreateNullBorderRendererWithStyleBorder(
nsPresContext* aPresContext, DrawTarget* aDrawTarget, nsIFrame* aForFrame,
const nsRect& aDirtyRect, const nsRect& aBorderArea,
const nsStyleBorder& aStyleBorder, ComputedStyle* aStyle,
bool* aOutBorderIsEmpty, Sides aSkipSides) {
const nsStyleDisplay* displayData = aStyle->StyleDisplay();
if (displayData->HasAppearance()) {
nsITheme* theme = aPresContext->GetTheme();
if (theme && theme->ThemeSupportsWidget(aPresContext, aForFrame,
displayData->mAppearance)) {
return Nothing();
}
}
nsMargin border = aStyleBorder.GetComputedBorder();
if (0 == border.left && 0 == border.right && 0 == border.top &&
0 == border.bottom) {
// Empty border area
if (aOutBorderIsEmpty) {
*aOutBorderIsEmpty = true;
}
return Nothing();
}
bool needsClip = false;
nsCSSBorderRenderer br = ConstructBorderRenderer(
aPresContext, aStyle, aDrawTarget, aForFrame, aDirtyRect, aBorderArea,
aStyleBorder, aSkipSides, &needsClip);
return Some(br);
}
static nsRect GetOutlineInnerRect(nsIFrame* aFrame) {
nsRect* savedOutlineInnerRect =
aFrame->GetProperty(nsIFrame::OutlineInnerRectProperty());
if (savedOutlineInnerRect) {
return *savedOutlineInnerRect;
}
// FIXME bug 1221888
NS_ERROR("we should have saved a frame property");
return nsRect(nsPoint(0, 0), aFrame->GetSize());
}
Maybe<nsCSSBorderRenderer> nsCSSRendering::CreateBorderRendererForOutline(
nsPresContext* aPresContext, gfxContext* aRenderingContext,
nsIFrame* aForFrame, const nsRect& aDirtyRect, const nsRect& aBorderArea,
ComputedStyle* aStyle) {
nscoord twipsRadii[8];
// Get our ComputedStyle's color struct.
const nsStyleOutline* ourOutline = aStyle->StyleOutline();
if (!ourOutline->ShouldPaintOutline()) {
// Empty outline
return Nothing();
}
nsRect innerRect;
if (
#ifdef MOZ_XUL
aStyle->GetPseudoType() == PseudoStyleType::XULTree
#else
false
#endif
) {
innerRect = aBorderArea;
} else {
innerRect = GetOutlineInnerRect(aForFrame) + aBorderArea.TopLeft();
}
nscoord offset = ourOutline->mOutlineOffset.ToAppUnits();
innerRect.Inflate(offset);
// If the dirty rect is completely inside the border area (e.g., only the
// content is being painted), then we can skip out now
// XXX this isn't exactly true for rounded borders, where the inside curves
// may encroach into the content area. A safer calculation would be to
// shorten insideRect by the radius one each side before performing this test.
if (innerRect.Contains(aDirtyRect)) return Nothing();
nscoord width = ourOutline->GetOutlineWidth();
nsRect outerRect = innerRect;
outerRect.Inflate(width);
// get the radius for our outline
nsIFrame::ComputeBorderRadii(ourOutline->mOutlineRadius, aBorderArea.Size(),
outerRect.Size(), Sides(), twipsRadii);
// Get our conversion values
nscoord oneDevPixel = aPresContext->DevPixelsToAppUnits(1);
// get the outer rectangles
Rect oRect(NSRectToRect(outerRect, oneDevPixel));
// convert the radii
nsMargin outlineMargin(width, width, width, width);
RectCornerRadii outlineRadii;
ComputePixelRadii(twipsRadii, oneDevPixel, &outlineRadii);
StyleBorderStyle outlineStyle;
if (ourOutline->mOutlineStyle.IsAuto()) {
if (StaticPrefs::layout_css_outline_style_auto_enabled()) {
nsITheme* theme = aPresContext->GetTheme();
if (theme && theme->ThemeSupportsWidget(aPresContext, aForFrame,
StyleAppearance::FocusOutline)) {
theme->DrawWidgetBackground(aRenderingContext, aForFrame,
StyleAppearance::FocusOutline, innerRect,
aDirtyRect);
return Nothing();
}
}
if (width == 0) {
return Nothing(); // empty outline
}
// http://dev.w3.org/csswg/css-ui/#outline
// "User agents may treat 'auto' as 'solid'."
outlineStyle = StyleBorderStyle::Solid;
} else {
outlineStyle = ourOutline->mOutlineStyle.AsBorderStyle();
}
StyleBorderStyle outlineStyles[4] = {outlineStyle, outlineStyle, outlineStyle,
outlineStyle};
// This handles treating the initial color as 'currentColor'; if we
// ever want 'invert' back we'll need to do a bit of work here too.
nscolor outlineColor =
aStyle->GetVisitedDependentColor(&nsStyleOutline::mOutlineColor);
nscolor outlineColors[4] = {outlineColor, outlineColor, outlineColor,
outlineColor};
// convert the border widths
Float outlineWidths[4] = {
Float(width) / oneDevPixel, Float(width) / oneDevPixel,
Float(width) / oneDevPixel, Float(width) / oneDevPixel};
Rect dirtyRect = NSRectToRect(aDirtyRect, oneDevPixel);
Document* document = nullptr;
nsIContent* content = aForFrame->GetContent();
if (content) {
document = content->OwnerDoc();
}
DrawTarget* dt =
aRenderingContext ? aRenderingContext->GetDrawTarget() : nullptr;
nsCSSBorderRenderer br(aPresContext, document, dt, dirtyRect, oRect,
outlineStyles, outlineWidths, outlineRadii,
outlineColors, !aForFrame->BackfaceIsHidden(),
Nothing());
return Some(br);
}
void nsCSSRendering::PaintOutline(nsPresContext* aPresContext,
gfxContext& aRenderingContext,
nsIFrame* aForFrame, const nsRect& aDirtyRect,
const nsRect& aBorderArea,
ComputedStyle* aStyle) {
Maybe<nsCSSBorderRenderer> br = CreateBorderRendererForOutline(
aPresContext, &aRenderingContext, aForFrame, aDirtyRect, aBorderArea,
aStyle);
if (!br) {
return;
}
// start drawing
br->DrawBorders();
PrintAsStringNewline();
}
void nsCSSRendering::PaintFocus(nsPresContext* aPresContext,
DrawTarget* aDrawTarget,
const nsRect& aFocusRect, nscolor aColor) {
nscoord oneCSSPixel = nsPresContext::CSSPixelsToAppUnits(1);
nscoord oneDevPixel = aPresContext->DevPixelsToAppUnits(1);
Rect focusRect(NSRectToRect(aFocusRect, oneDevPixel));
RectCornerRadii focusRadii;
{
nscoord twipsRadii[8] = {0, 0, 0, 0, 0, 0, 0, 0};
ComputePixelRadii(twipsRadii, oneDevPixel, &focusRadii);
}
Float focusWidths[4] = {
Float(oneCSSPixel) / oneDevPixel, Float(oneCSSPixel) / oneDevPixel,
Float(oneCSSPixel) / oneDevPixel, Float(oneCSSPixel) / oneDevPixel};
StyleBorderStyle focusStyles[4] = {
StyleBorderStyle::Dotted, StyleBorderStyle::Dotted,
StyleBorderStyle::Dotted, StyleBorderStyle::Dotted};
nscolor focusColors[4] = {aColor, aColor, aColor, aColor};
// Because this renders a dotted border, the background color
// should not be used. Therefore, we provide a value that will
// be blatantly wrong if it ever does get used. (If this becomes
// something that CSS can style, this function will then have access
// to a ComputedStyle and can use the same logic that PaintBorder
// and PaintOutline do.)
//
// WebRender layers-free mode don't use PaintFocus function. Just assign
// the backface-visibility to true for this case.
nsCSSBorderRenderer br(aPresContext, nullptr, aDrawTarget, focusRect,
focusRect, focusStyles, focusWidths, focusRadii,
focusColors, true, Nothing());
br.DrawBorders();
PrintAsStringNewline();
}
// Thebes Border Rendering Code End
//----------------------------------------------------------------------
//----------------------------------------------------------------------
/**
* Helper for ComputeObjectAnchorPoint; parameters are the same as for
* that function, except they're for a single coordinate / a single size
* dimension. (so, x/width vs. y/height)
*/
static void ComputeObjectAnchorCoord(const LengthPercentage& aCoord,
const nscoord aOriginBounds,
const nscoord aImageSize,
nscoord* aTopLeftCoord,
nscoord* aAnchorPointCoord) {
nscoord extraSpace = aOriginBounds - aImageSize;
// The anchor-point doesn't care about our image's size; just the size
// of the region we're rendering into.
*aAnchorPointCoord = aCoord.Resolve(aOriginBounds, NSToCoordRoundWithClamp);
// Adjust aTopLeftCoord by the specified % of the extra space.
*aTopLeftCoord = aCoord.Resolve(extraSpace, NSToCoordRoundWithClamp);
}
void nsImageRenderer::ComputeObjectAnchorPoint(const Position& aPos,
const nsSize& aOriginBounds,
const nsSize& aImageSize,
nsPoint* aTopLeft,
nsPoint* aAnchorPoint) {
ComputeObjectAnchorCoord(aPos.horizontal, aOriginBounds.width,
aImageSize.width, &aTopLeft->x, &aAnchorPoint->x);
ComputeObjectAnchorCoord(aPos.vertical, aOriginBounds.height,
aImageSize.height, &aTopLeft->y, &aAnchorPoint->y);
}
nsIFrame* nsCSSRendering::FindNonTransparentBackgroundFrame(
nsIFrame* aFrame, bool aStartAtParent /*= false*/) {
NS_ASSERTION(aFrame,
"Cannot find NonTransparentBackgroundFrame in a null frame");
nsIFrame* frame = nullptr;
if (aStartAtParent) {
frame = nsLayoutUtils::GetParentOrPlaceholderFor(aFrame);
}
if (!frame) {
frame = aFrame;
}
while (frame) {
// No need to call GetVisitedDependentColor because it always uses
// this alpha component anyway.
if (NS_GET_A(frame->StyleBackground()->BackgroundColor(frame)) > 0) {
break;
}
if (frame->IsThemed()) {
break;
}
nsIFrame* parent = nsLayoutUtils::GetParentOrPlaceholderFor(frame);
if (!parent) {
break;
}
frame = parent;
}
return frame;
}
// Returns true if aFrame is a canvas frame.
// We need to treat the viewport as canvas because, even though
// it does not actually paint a background, we need to get the right
// background style so we correctly detect transparent documents.
bool nsCSSRendering::IsCanvasFrame(nsIFrame* aFrame) {
LayoutFrameType frameType = aFrame->Type();
return frameType == LayoutFrameType::Canvas ||
frameType == LayoutFrameType::Root ||
frameType == LayoutFrameType::PageContent ||
frameType == LayoutFrameType::Viewport;
}
nsIFrame* nsCSSRendering::FindBackgroundStyleFrame(nsIFrame* aForFrame) {
const nsStyleBackground* result = aForFrame->StyleBackground();
// Check if we need to do propagation from BODY rather than HTML.
if (!result->IsTransparent(aForFrame)) {
return aForFrame;
}
nsIContent* content = aForFrame->GetContent();
// The root element content can't be null. We wouldn't know what
// frame to create for aFrame.
// Use |OwnerDoc| so it works during destruction.
if (!content) {
return aForFrame;
}
Document* document = content->OwnerDoc();
dom::Element* bodyContent = document->GetBodyElement();
// We need to null check the body node (bug 118829) since
// there are cases, thanks to the fix for bug 5569, where we
// will reflow a document with no body. In particular, if a
// SCRIPT element in the head blocks the parser and then has a
// SCRIPT that does "document.location.href = 'foo'", then
// nsParser::Terminate will call |DidBuildModel| methods
// through to the content sink, which will call |StartLayout|
// and thus |Initialize| on the pres shell. See bug 119351
// for the ugly details.
if (!bodyContent) {
return aForFrame;
}
nsIFrame* bodyFrame = bodyContent->GetPrimaryFrame();
if (!bodyFrame) {
return aForFrame;
}
return nsLayoutUtils::GetStyleFrame(bodyFrame);
}
/**
* |FindBackground| finds the correct style data to use to paint the
* background. It is responsible for handling the following two
* statements in section 14.2 of CSS2:
*
* The background of the box generated by the root element covers the
* entire canvas.
*
* For HTML documents, however, we recommend that authors specify the
* background for the BODY element rather than the HTML element. User
* agents should observe the following precedence rules to fill in the
* background: if the value of the 'background' property for the HTML
* element is different from 'transparent' then use it, else use the
* value of the 'background' property for the BODY element. If the
* resulting value is 'transparent', the rendering is undefined.
*
* Thus, in our implementation, it is responsible for ensuring that:
* + we paint the correct background on the |nsCanvasFrame|,
* |nsRootBoxFrame|, or |nsPageFrame|,
* + we don't paint the background on the root element, and
* + we don't paint the background on the BODY element in *some* cases,
* and for SGML-based HTML documents only.
*
* |FindBackground| returns true if a background should be painted, and
* the resulting ComputedStyle to use for the background information
* will be filled in to |aBackground|.
*/
ComputedStyle* nsCSSRendering::FindRootFrameBackground(nsIFrame* aForFrame) {
return FindBackgroundStyleFrame(aForFrame)->Style();
}
inline bool FindElementBackground(nsIFrame* aForFrame,
nsIFrame* aRootElementFrame) {
if (aForFrame == aRootElementFrame) {
// We must have propagated our background to the viewport or canvas. Abort.
return false;
}
// Return true unless the frame is for a BODY element whose background
// was propagated to the viewport.
nsIContent* content = aForFrame->GetContent();
if (!content || content->NodeInfo()->NameAtom() != nsGkAtoms::body)
return true; // not frame for a "body" element
// It could be a non-HTML "body" element but that's OK, we'd fail the
// bodyContent check below
if (aForFrame->Style()->GetPseudoType() != PseudoStyleType::NotPseudo) {
return true; // A pseudo-element frame.
}
// We should only look at the <html> background if we're in an HTML document
Document* document = content->OwnerDoc();
dom::Element* bodyContent = document->GetBodyElement();
if (bodyContent != content)
return true; // this wasn't the background that was propagated
// This can be called even when there's no root element yet, during frame
// construction, via nsLayoutUtils::FrameHasTransparency and
// nsContainerFrame::SyncFrameViewProperties.
if (!aRootElementFrame) {
return true;
}
const nsStyleBackground* htmlBG = aRootElementFrame->StyleBackground();
return !htmlBG->IsTransparent(aRootElementFrame);
}
bool nsCSSRendering::FindBackgroundFrame(nsIFrame* aForFrame,
nsIFrame** aBackgroundFrame) {
nsIFrame* rootElementFrame =
aForFrame->PresShell()->FrameConstructor()->GetRootElementStyleFrame();
if (IsCanvasFrame(aForFrame)) {
*aBackgroundFrame = FindCanvasBackgroundFrame(aForFrame, rootElementFrame);
return true;
}
*aBackgroundFrame = aForFrame;
return FindElementBackground(aForFrame, rootElementFrame);
}
bool nsCSSRendering::FindBackground(nsIFrame* aForFrame,
ComputedStyle** aBackgroundSC) {
nsIFrame* backgroundFrame = nullptr;
if (FindBackgroundFrame(aForFrame, &backgroundFrame)) {
*aBackgroundSC = backgroundFrame->Style();
return true;
}
return false;
}
void nsCSSRendering::BeginFrameTreesLocked() { ++gFrameTreeLockCount; }
void nsCSSRendering::EndFrameTreesLocked() {
NS_ASSERTION(gFrameTreeLockCount > 0, "Unbalanced EndFrameTreeLocked");
--gFrameTreeLockCount;
if (gFrameTreeLockCount == 0) {
gInlineBGData->Reset();
}
}
bool nsCSSRendering::HasBoxShadowNativeTheme(nsIFrame* aFrame,
bool& aMaybeHasBorderRadius) {
const nsStyleDisplay* styleDisplay = aFrame->StyleDisplay();
nsITheme::Transparency transparency;
if (aFrame->IsThemed(styleDisplay, &transparency)) {
aMaybeHasBorderRadius = false;
// For opaque (rectangular) theme widgets we can take the generic
// border-box path with border-radius disabled.
return transparency != nsITheme::eOpaque;
}
aMaybeHasBorderRadius = true;
return false;
}
gfx::Color nsCSSRendering::GetShadowColor(const StyleSimpleShadow& aShadow,
nsIFrame* aFrame, float aOpacity) {
// Get the shadow color; if not specified, use the foreground color
nscolor shadowColor = aShadow.color.CalcColor(aFrame);
Color color = Color::FromABGR(shadowColor);
color.a *= aOpacity;
return color;
}
nsRect nsCSSRendering::GetShadowRect(const nsRect& aFrameArea,
bool aNativeTheme, nsIFrame* aForFrame) {
nsRect frameRect = aNativeTheme
? aForFrame->GetVisualOverflowRectRelativeToSelf() +
aFrameArea.TopLeft()
: aFrameArea;
Sides skipSides = aForFrame->GetSkipSides();
frameRect = BoxDecorationRectForBorder(aForFrame, frameRect, skipSides);
// Explicitly do not need to account for the spread radius here
// Webrender does it for us or PaintBoxShadow will for non-WR
return frameRect;
}
bool nsCSSRendering::GetBorderRadii(const nsRect& aFrameRect,
const nsRect& aBorderRect, nsIFrame* aFrame,
RectCornerRadii& aOutRadii) {
const nscoord oneDevPixel = aFrame->PresContext()->DevPixelsToAppUnits(1);
nscoord twipsRadii[8];
NS_ASSERTION(
aBorderRect.Size() == aFrame->VisualBorderRectRelativeToSelf().Size(),
"unexpected size");
nsSize sz = aFrameRect.Size();
bool hasBorderRadius = aFrame->GetBorderRadii(sz, sz, Sides(), twipsRadii);
if (hasBorderRadius) {
ComputePixelRadii(twipsRadii, oneDevPixel, &aOutRadii);
}
return hasBorderRadius;
}
void nsCSSRendering::PaintBoxShadowOuter(nsPresContext* aPresContext,
gfxContext& aRenderingContext,
nsIFrame* aForFrame,
const nsRect& aFrameArea,
const nsRect& aDirtyRect,
float aOpacity) {
DrawTarget& aDrawTarget = *aRenderingContext.GetDrawTarget();
auto shadows = aForFrame->StyleEffects()->mBoxShadow.AsSpan();
if (shadows.IsEmpty()) {
return;
}
bool hasBorderRadius;
// mutually exclusive with hasBorderRadius
bool nativeTheme = HasBoxShadowNativeTheme(aForFrame, hasBorderRadius);
const nsStyleDisplay* styleDisplay = aForFrame->StyleDisplay();
nsRect frameRect = GetShadowRect(aFrameArea, nativeTheme, aForFrame);
// Get any border radius, since box-shadow must also have rounded corners if
// the frame does.
RectCornerRadii borderRadii;
const nscoord oneDevPixel = aPresContext->DevPixelsToAppUnits(1);
if (hasBorderRadius) {
nscoord twipsRadii[8];
NS_ASSERTION(
aFrameArea.Size() == aForFrame->VisualBorderRectRelativeToSelf().Size(),
"unexpected size");
nsSize sz = frameRect.Size();
hasBorderRadius = aForFrame->GetBorderRadii(sz, sz, Sides(), twipsRadii);
if (hasBorderRadius) {
ComputePixelRadii(twipsRadii, oneDevPixel, &borderRadii);
}
}
// We don't show anything that intersects with the frame we're blurring on. So
// tell the blurrer not to do unnecessary work there.
gfxRect skipGfxRect = ThebesRect(NSRectToRect(frameRect, oneDevPixel));
skipGfxRect.Round();
bool useSkipGfxRect = true;
if (nativeTheme) {
// Optimize non-leaf native-themed frames by skipping computing pixels
// in the padding-box. We assume the padding-box is going to be painted
// opaquely for non-leaf frames.
// XXX this may not be a safe assumption; we should make this go away
// by optimizing box-shadow drawing more for the cases where we don't have a
// skip-rect.
useSkipGfxRect = !aForFrame->IsLeaf();
nsRect paddingRect =
aForFrame->GetPaddingRectRelativeToSelf() + aFrameArea.TopLeft();
skipGfxRect = nsLayoutUtils::RectToGfxRect(paddingRect, oneDevPixel);
} else if (hasBorderRadius) {
skipGfxRect.Deflate(gfxMargin(
std::max(borderRadii[C_TL].height, borderRadii[C_TR].height), 0,
std::max(borderRadii[C_BL].height, borderRadii[C_BR].height), 0));
}
for (const StyleBoxShadow& shadow : Reversed(shadows)) {
if (shadow.inset) {
continue;
}
nsRect shadowRect = frameRect;
nsPoint shadowOffset(shadow.base.horizontal.ToAppUnits(),
shadow.base.vertical.ToAppUnits());
shadowRect.MoveBy(shadowOffset);
nscoord shadowSpread = shadow.spread.ToAppUnits();
if (!nativeTheme) {
shadowRect.Inflate(shadowSpread);
}
// shadowRect won't include the blur, so make an extra rect here that
// includes the blur for use in the even-odd rule below.
nsRect shadowRectPlusBlur = shadowRect;
nscoord blurRadius = shadow.base.blur.ToAppUnits();
shadowRectPlusBlur.Inflate(
nsContextBoxBlur::GetBlurRadiusMargin(blurRadius, oneDevPixel));
Rect shadowGfxRectPlusBlur = NSRectToRect(shadowRectPlusBlur, oneDevPixel);
shadowGfxRectPlusBlur.RoundOut();
MaybeSnapToDevicePixels(shadowGfxRectPlusBlur, aDrawTarget, true);
Color gfxShadowColor = GetShadowColor(shadow.base, aForFrame, aOpacity);
if (nativeTheme) {
nsContextBoxBlur blurringArea;
// When getting the widget shape from the native theme, we're going
// to draw the widget into the shadow surface to create a mask.
// We need to ensure that there actually *is* a shadow surface
// and that we're not going to draw directly into aRenderingContext.
gfxContext* shadowContext = blurringArea.Init(
shadowRect, shadowSpread, blurRadius, oneDevPixel, &aRenderingContext,
aDirtyRect, useSkipGfxRect ? &skipGfxRect : nullptr,
nsContextBoxBlur::FORCE_MASK);
if (!shadowContext) continue;
MOZ_ASSERT(shadowContext == blurringArea.GetContext());
aRenderingContext.Save();
aRenderingContext.SetColor(gfxShadowColor);
// Draw the shape of the frame so it can be blurred. Recall how
// nsContextBoxBlur doesn't make any temporary surfaces if blur is 0 and
// it just returns the original surface? If we have no blur, we're
// painting this fill on the actual content surface (aRenderingContext ==
// shadowContext) which is why we set up the color and clip before doing
// this.
// We don't clip the border-box from the shadow, nor any other box.
// We assume that the native theme is going to paint over the shadow.
// Draw the widget shape
gfxContextMatrixAutoSaveRestore save(shadowContext);
gfxPoint devPixelOffset = nsLayoutUtils::PointToGfxPoint(
shadowOffset, aPresContext->AppUnitsPerDevPixel());
shadowContext->SetMatrixDouble(
shadowContext->CurrentMatrixDouble().PreTranslate(devPixelOffset));
nsRect nativeRect = aDirtyRect;
nativeRect.MoveBy(-shadowOffset);
nativeRect.IntersectRect(frameRect, nativeRect);
aPresContext->GetTheme()->DrawWidgetBackground(shadowContext, aForFrame,
styleDisplay->mAppearance,
aFrameArea, nativeRect);
blurringArea.DoPaint();
aRenderingContext.Restore();
} else {
aRenderingContext.Save();
{
Rect innerClipRect = NSRectToRect(frameRect, oneDevPixel);
if (!MaybeSnapToDevicePixels(innerClipRect, aDrawTarget, true)) {
innerClipRect.Round();
}
// Clip out the interior of the frame's border edge so that the shadow
// is only painted outside that area.
RefPtr<PathBuilder> builder =
aDrawTarget.CreatePathBuilder(FillRule::FILL_EVEN_ODD);
AppendRectToPath(builder, shadowGfxRectPlusBlur);
if (hasBorderRadius) {
AppendRoundedRectToPath(builder, innerClipRect, borderRadii);
} else {
AppendRectToPath(builder, innerClipRect);
}
RefPtr<Path> path = builder->Finish();
aRenderingContext.Clip(path);
}
// Clip the shadow so that we only get the part that applies to aForFrame.
nsRect fragmentClip = shadowRectPlusBlur;
Sides skipSides = aForFrame->GetSkipSides();
if (!skipSides.IsEmpty()) {
if (skipSides.Left()) {
nscoord xmost = fragmentClip.XMost();
fragmentClip.x = aFrameArea.x;
fragmentClip.width = xmost - fragmentClip.x;
}
if (skipSides.Right()) {
nscoord xmost = fragmentClip.XMost();
nscoord overflow = xmost - aFrameArea.XMost();
if (overflow > 0) {
fragmentClip.width -= overflow;
}
}
if (skipSides.Top()) {
nscoord ymost = fragmentClip.YMost();
fragmentClip.y = aFrameArea.y;
fragmentClip.height = ymost - fragmentClip.y;
}
if (skipSides.Bottom()) {
nscoord ymost = fragmentClip.YMost();
nscoord overflow = ymost - aFrameArea.YMost();
if (overflow > 0) {
fragmentClip.height -= overflow;
}
}
}
fragmentClip = fragmentClip.Intersect(aDirtyRect);
aRenderingContext.Clip(NSRectToSnappedRect(
fragmentClip, aForFrame->PresContext()->AppUnitsPerDevPixel(),
aDrawTarget));
RectCornerRadii clipRectRadii;
if (hasBorderRadius) {
Float spreadDistance = Float(shadowSpread / oneDevPixel);
Float borderSizes[4];
borderSizes[eSideLeft] = spreadDistance;
borderSizes[eSideTop] = spreadDistance;
borderSizes[eSideRight] = spreadDistance;
borderSizes[eSideBottom] = spreadDistance;
nsCSSBorderRenderer::ComputeOuterRadii(borderRadii, borderSizes,
&clipRectRadii);
}
nsContextBoxBlur::BlurRectangle(
&aRenderingContext, shadowRect, oneDevPixel,
hasBorderRadius ? &clipRectRadii : nullptr, blurRadius,
gfxShadowColor, aDirtyRect, skipGfxRect);
aRenderingContext.Restore();
}
}
}
nsRect nsCSSRendering::GetBoxShadowInnerPaddingRect(nsIFrame* aFrame,
const nsRect& aFrameArea) {
Sides skipSides = aFrame->GetSkipSides();
nsRect frameRect = BoxDecorationRectForBorder(aFrame, aFrameArea, skipSides);
nsRect paddingRect = frameRect;
nsMargin border = aFrame->GetUsedBorder();
paddingRect.Deflate(border);
return paddingRect;
}
bool nsCSSRendering::ShouldPaintBoxShadowInner(nsIFrame* aFrame) {
const Span<const StyleBoxShadow> shadows =
aFrame->StyleEffects()->mBoxShadow.AsSpan();
if (shadows.IsEmpty()) {
return false;
}
if (aFrame->IsThemed() && aFrame->GetContent() &&
!nsContentUtils::IsChromeDoc(aFrame->GetContent()->GetComposedDoc())) {
// There's no way of getting hold of a shape corresponding to a
// "padding-box" for native-themed widgets, so just don't draw
// inner box-shadows for them. But we allow chrome to paint inner
// box shadows since chrome can be aware of the platform theme.
return false;
}
return true;
}
bool nsCSSRendering::GetShadowInnerRadii(nsIFrame* aFrame,
const nsRect& aFrameArea,
RectCornerRadii& aOutInnerRadii) {
// Get any border radius, since box-shadow must also have rounded corners
// if the frame does.
nscoord twipsRadii[8];
nsRect frameRect =
BoxDecorationRectForBorder(aFrame, aFrameArea, aFrame->GetSkipSides());
nsSize sz = frameRect.Size();
nsMargin border = aFrame->GetUsedBorder();
aFrame->GetBorderRadii(sz, sz, Sides(), twipsRadii);
const nscoord oneDevPixel = aFrame->PresContext()->DevPixelsToAppUnits(1);
RectCornerRadii borderRadii;
const bool hasBorderRadius =
GetBorderRadii(frameRect, aFrameArea, aFrame, borderRadii);
if (hasBorderRadius) {
ComputePixelRadii(twipsRadii, oneDevPixel, &borderRadii);
Float borderSizes[4] = {
Float(border.top) / oneDevPixel, Float(border.right) / oneDevPixel,
Float(border.bottom) / oneDevPixel, Float(border.left) / oneDevPixel};
nsCSSBorderRenderer::ComputeInnerRadii(borderRadii, borderSizes,
&aOutInnerRadii);
}
return hasBorderRadius;
}
void nsCSSRendering::PaintBoxShadowInner(nsPresContext* aPresContext,
gfxContext& aRenderingContext,
nsIFrame* aForFrame,
const nsRect& aFrameArea) {
if (!ShouldPaintBoxShadowInner(aForFrame)) {
return;
}
const Span<const StyleBoxShadow> shadows =
aForFrame->StyleEffects()->mBoxShadow.AsSpan();
NS_ASSERTION(
aForFrame->IsFieldSetFrame() || aFrameArea.Size() == aForFrame->GetSize(),
"unexpected size");
nsRect paddingRect = GetBoxShadowInnerPaddingRect(aForFrame, aFrameArea);
RectCornerRadii innerRadii;
bool hasBorderRadius = GetShadowInnerRadii(aForFrame, aFrameArea, innerRadii);
const nscoord oneDevPixel = aPresContext->DevPixelsToAppUnits(1);
for (const StyleBoxShadow& shadow : Reversed(shadows)) {
if (!shadow.inset) {
continue;
}
// shadowPaintRect: the area to paint on the temp surface
// shadowClipRect: the area on the temporary surface within shadowPaintRect
// that we will NOT paint in
nscoord blurRadius = shadow.base.blur.ToAppUnits();
nsMargin blurMargin =
nsContextBoxBlur::GetBlurRadiusMargin(blurRadius, oneDevPixel);
nsRect shadowPaintRect = paddingRect;
shadowPaintRect.Inflate(blurMargin);
// Round the spread radius to device pixels (by truncation).
// This mostly matches what we do for borders, except that we don't round
// up values between zero and one device pixels to one device pixel.
// This way of rounding is symmetric around zero, which makes sense for
// the spread radius.
int32_t spreadDistance = shadow.spread.ToAppUnits() / oneDevPixel;
nscoord spreadDistanceAppUnits =
aPresContext->DevPixelsToAppUnits(spreadDistance);
nsRect shadowClipRect = paddingRect;
shadowClipRect.MoveBy(shadow.base.horizontal.ToAppUnits(),
shadow.base.vertical.ToAppUnits());
shadowClipRect.Deflate(spreadDistanceAppUnits, spreadDistanceAppUnits);
Rect shadowClipGfxRect = NSRectToRect(shadowClipRect, oneDevPixel);
shadowClipGfxRect.Round();
RectCornerRadii clipRectRadii;
if (hasBorderRadius) {
// Calculate the radii the inner clipping rect will have
Float borderSizes[4] = {0, 0, 0, 0};
// See PaintBoxShadowOuter and bug 514670
if (innerRadii[C_TL].width > 0 || innerRadii[C_BL].width > 0) {
borderSizes[eSideLeft] = spreadDistance;
}
if (innerRadii[C_TL].height > 0 || innerRadii[C_TR].height > 0) {
borderSizes[eSideTop] = spreadDistance;
}
if (innerRadii[C_TR].width > 0 || innerRadii[C_BR].width > 0) {
borderSizes[eSideRight] = spreadDistance;
}
if (innerRadii[C_BL].height > 0 || innerRadii[C_BR].height > 0) {
borderSizes[eSideBottom] = spreadDistance;
}
nsCSSBorderRenderer::ComputeInnerRadii(innerRadii, borderSizes,
&clipRectRadii);
}
// Set the "skip rect" to the area within the frame that we don't paint in,
// including after blurring.
nsRect skipRect = shadowClipRect;
skipRect.Deflate(blurMargin);
gfxRect skipGfxRect = nsLayoutUtils::RectToGfxRect(skipRect, oneDevPixel);
if (hasBorderRadius) {
skipGfxRect.Deflate(gfxMargin(
std::max(clipRectRadii[C_TL].height, clipRectRadii[C_TR].height), 0,
std::max(clipRectRadii[C_BL].height, clipRectRadii[C_BR].height), 0));
}
// When there's a blur radius, gfxAlphaBoxBlur leaves the skiprect area
// unchanged. And by construction the gfxSkipRect is not touched by the
// rendered shadow (even after blurring), so those pixels must be completely
// transparent in the shadow, so drawing them changes nothing.
DrawTarget* drawTarget = aRenderingContext.GetDrawTarget();
// Clip the context to the area of the frame's padding rect, so no part of
// the shadow is painted outside. Also cut out anything beyond where the
// inset shadow will be.
Rect shadowGfxRect = NSRectToRect(paddingRect, oneDevPixel);
shadowGfxRect.Round();
Color shadowColor = GetShadowColor(shadow.base, aForFrame, 1.0);
aRenderingContext.Save();
// This clips the outside border radius.
// clipRectRadii is the border radius inside the inset shadow.
if (hasBorderRadius) {
RefPtr<Path> roundedRect =
MakePathForRoundedRect(*drawTarget, shadowGfxRect, innerRadii);
aRenderingContext.Clip(roundedRect);
} else {
aRenderingContext.Clip(shadowGfxRect);
}
nsContextBoxBlur insetBoxBlur;
gfxRect destRect =
nsLayoutUtils::RectToGfxRect(shadowPaintRect, oneDevPixel);
Point shadowOffset(shadow.base.horizontal.ToAppUnits() / oneDevPixel,
shadow.base.vertical.ToAppUnits() / oneDevPixel);
insetBoxBlur.InsetBoxBlur(
&aRenderingContext, ToRect(destRect), shadowClipGfxRect, shadowColor,
blurRadius, spreadDistanceAppUnits, oneDevPixel, hasBorderRadius,
clipRectRadii, ToRect(skipGfxRect), shadowOffset);
aRenderingContext.Restore();
}
}
/* static */
nsCSSRendering::PaintBGParams nsCSSRendering::PaintBGParams::ForAllLayers(
nsPresContext& aPresCtx, const nsRect& aDirtyRect,
const nsRect& aBorderArea, nsIFrame* aFrame, uint32_t aPaintFlags,
float aOpacity) {
MOZ_ASSERT(aFrame);
PaintBGParams result(aPresCtx, aDirtyRect, aBorderArea, aFrame, aPaintFlags,
-1, CompositionOp::OP_OVER, aOpacity);
return result;
}
/* static */
nsCSSRendering::PaintBGParams nsCSSRendering::PaintBGParams::ForSingleLayer(
nsPresContext& aPresCtx, const nsRect& aDirtyRect,
const nsRect& aBorderArea, nsIFrame* aFrame, uint32_t aPaintFlags,
int32_t aLayer, CompositionOp aCompositionOp, float aOpacity) {
MOZ_ASSERT(aFrame && (aLayer != -1));
PaintBGParams result(aPresCtx, aDirtyRect, aBorderArea, aFrame, aPaintFlags,
aLayer, aCompositionOp, aOpacity);
return result;
}
ImgDrawResult nsCSSRendering::PaintStyleImageLayer(const PaintBGParams& aParams,
gfxContext& aRenderingCtx) {
AUTO_PROFILER_LABEL("nsCSSRendering::PaintStyleImageLayer", GRAPHICS);
MOZ_ASSERT(aParams.frame,
"Frame is expected to be provided to PaintStyleImageLayer");
ComputedStyle* sc;
if (!FindBackground(aParams.frame, &sc)) {
// We don't want to bail out if moz-appearance is set on a root
// node. If it has a parent content node, bail because it's not
// a root, otherwise keep going in order to let the theme stuff
// draw the background. The canvas really should be drawing the
// bg, but there's no way to hook that up via css.
if (!aParams.frame->StyleDisplay()->HasAppearance()) {
return ImgDrawResult::SUCCESS;
}
nsIContent* content = aParams.frame->GetContent();
if (!content || content->GetParent()) {
return ImgDrawResult::SUCCESS;
}
sc = aParams.frame->Style();
}
return PaintStyleImageLayerWithSC(aParams, aRenderingCtx, sc,
*aParams.frame->StyleBorder());
}
bool nsCSSRendering::CanBuildWebRenderDisplayItemsForStyleImageLayer(
LayerManager* aManager, nsPresContext& aPresCtx, nsIFrame* aFrame,
const nsStyleBackground* aBackgroundStyle, int32_t aLayer,
uint32_t aPaintFlags) {
if (!aBackgroundStyle) {
return false;
}
MOZ_ASSERT(aFrame && aLayer >= 0 &&
(uint32_t)aLayer < aBackgroundStyle->mImage.mLayers.Length());
// We cannot draw native themed backgrounds
const nsStyleDisplay* displayData = aFrame->StyleDisplay();
if (displayData->HasAppearance()) {
nsITheme* theme = aPresCtx.GetTheme();
if (theme && theme->ThemeSupportsWidget(&aPresCtx, aFrame,
displayData->mAppearance)) {
return false;
}
}
// We only support painting gradients and image for a single style image
// layer, and we don't support crop-rects.
const auto& styleImage = aBackgroundStyle->mImage.mLayers[aLayer].mImage;
if (styleImage.IsImageRequestType()) {
if (styleImage.IsRect()) {
return false;
}
imgRequestProxy* requestProxy = styleImage.GetImageRequest();
if (!requestProxy) {
return false;
}
uint32_t imageFlags = imgIContainer::FLAG_NONE;
if (aPaintFlags & nsCSSRendering::PAINTBG_SYNC_DECODE_IMAGES) {
imageFlags |= imgIContainer::FLAG_SYNC_DECODE;
}
nsCOMPtr<imgIContainer> srcImage;
requestProxy->GetImage(getter_AddRefs(srcImage));
if (!srcImage ||
!srcImage->IsImageContainerAvailable(aManager, imageFlags)) {
return false;
}
return true;
}
if (styleImage.IsGradient()) {
return true;
}
return false;
}
ImgDrawResult nsCSSRendering::BuildWebRenderDisplayItemsForStyleImageLayer(
const PaintBGParams& aParams, mozilla::wr::DisplayListBuilder& aBuilder,
mozilla::wr::IpcResourceUpdateQueue& aResources,
const mozilla::layers::StackingContextHelper& aSc,
mozilla::layers::RenderRootStateManager* aManager, nsDisplayItem* aItem) {
MOZ_ASSERT(aParams.frame,
"Frame is expected to be provided to "
"BuildWebRenderDisplayItemsForStyleImageLayer");
ComputedStyle* sc;
if (!FindBackground(aParams.frame, &sc)) {
// We don't want to bail out if moz-appearance is set on a root
// node. If it has a parent content node, bail because it's not
// a root, otherwise keep going in order to let the theme stuff
// draw the background. The canvas really should be drawing the
// bg, but there's no way to hook that up via css.
if (!aParams.frame->StyleDisplay()->HasAppearance()) {
return ImgDrawResult::SUCCESS;
}
nsIContent* content = aParams.frame->GetContent();
if (!content || content->GetParent()) {
return ImgDrawResult::SUCCESS;
}
sc = aParams.frame->Style();
}
return BuildWebRenderDisplayItemsForStyleImageLayerWithSC(
aParams, aBuilder, aResources, aSc, aManager, aItem, sc,
*aParams.frame->StyleBorder());
}
static bool IsOpaqueBorderEdge(const nsStyleBorder& aBorder,
mozilla::Side aSide) {
if (aBorder.GetComputedBorder().Side(aSide) == 0) return true;
switch (aBorder.GetBorderStyle(aSide)) {
case StyleBorderStyle::Solid:
case StyleBorderStyle::Groove:
case StyleBorderStyle::Ridge:
case StyleBorderStyle::Inset:
case StyleBorderStyle::Outset:
break;
default:
return false;
}
// If we're using a border image, assume it's not fully opaque,
// because we may not even have the image loaded at this point, and
// even if we did, checking whether the relevant tile is fully
// opaque would be too much work.
if (!aBorder.mBorderImageSource.IsNone()) {
return false;
}
StyleColor color = aBorder.BorderColorFor(aSide);
// We don't know the foreground color here, so if it's being used
// we must assume it might be transparent.
return !color.MaybeTransparent();
}
/**
* Returns true if all border edges are either missing or opaque.
*/
static bool IsOpaqueBorder(const nsStyleBorder& aBorder) {
for (const auto i : mozilla::AllPhysicalSides()) {
if (!IsOpaqueBorderEdge(aBorder, i)) {
return false;
}
}
return true;
}
static inline void SetupDirtyRects(const nsRect& aBGClipArea,
const nsRect& aCallerDirtyRect,
nscoord aAppUnitsPerPixel,
/* OUT: */
nsRect* aDirtyRect, gfxRect* aDirtyRectGfx) {
aDirtyRect->IntersectRect(aBGClipArea, aCallerDirtyRect);
// Compute the Thebes equivalent of the dirtyRect.
*aDirtyRectGfx = nsLayoutUtils::RectToGfxRect(*aDirtyRect, aAppUnitsPerPixel);
NS_WARNING_ASSERTION(aDirtyRect->IsEmpty() || !aDirtyRectGfx->IsEmpty(),
"converted dirty rect should not be empty");
MOZ_ASSERT(!aDirtyRect->IsEmpty() || aDirtyRectGfx->IsEmpty(),
"second should be empty if first is");
}
static bool IsSVGStyleGeometryBox(StyleGeometryBox aBox) {
return (aBox == StyleGeometryBox::FillBox ||
aBox == StyleGeometryBox::StrokeBox ||
aBox == StyleGeometryBox::ViewBox);
}
static bool IsHTMLStyleGeometryBox(StyleGeometryBox aBox) {
return (aBox == StyleGeometryBox::ContentBox ||
aBox == StyleGeometryBox::PaddingBox ||
aBox == StyleGeometryBox::BorderBox ||
aBox == StyleGeometryBox::MarginBox);
}
static StyleGeometryBox ComputeBoxValue(nsIFrame* aForFrame,
StyleGeometryBox aBox) {
if (!(aForFrame->GetStateBits() & NS_FRAME_SVG_LAYOUT)) {
// For elements with associated CSS layout box, the values fill-box,
// stroke-box and view-box compute to the initial value of mask-clip.
if (IsSVGStyleGeometryBox(aBox)) {
return StyleGeometryBox::BorderBox;
}
} else {
// For SVG elements without associated CSS layout box, the values
// content-box, padding-box, border-box and margin-box compute to fill-box.
if (IsHTMLStyleGeometryBox(aBox)) {
return StyleGeometryBox::FillBox;
}
}
return aBox;
}
bool nsCSSRendering::ImageLayerClipState::IsValid() const {
// mDirtyRectInDevPx comes from mDirtyRectInAppUnits. mDirtyRectInAppUnits
// can not be empty if mDirtyRectInDevPx is not.
if (!mDirtyRectInDevPx.IsEmpty() && mDirtyRectInAppUnits.IsEmpty()) {
return false;
}
if (mHasRoundedCorners == mClippedRadii.IsEmpty()) {
return false;
}
return true;
}
/* static */
void nsCSSRendering::GetImageLayerClip(
const nsStyleImageLayers::Layer& aLayer, nsIFrame* aForFrame,
const nsStyleBorder& aBorder, const nsRect& aBorderArea,
const nsRect& aCallerDirtyRect, bool aWillPaintBorder,
nscoord aAppUnitsPerPixel,
/* out */ ImageLayerClipState* aClipState) {
StyleGeometryBox layerClip = ComputeBoxValue(aForFrame, aLayer.mClip);
if (IsSVGStyleGeometryBox(layerClip)) {
MOZ_ASSERT(aForFrame->IsFrameOfType(nsIFrame::eSVG) &&
!aForFrame->IsSVGOuterSVGFrame());
// The coordinate space of clipArea is svg user space.
nsRect clipArea = nsLayoutUtils::ComputeGeometryBox(aForFrame, layerClip);
nsRect strokeBox = (layerClip == StyleGeometryBox::StrokeBox)
? clipArea
: nsLayoutUtils::ComputeGeometryBox(
aForFrame, StyleGeometryBox::StrokeBox);
nsRect clipAreaRelativeToStrokeBox = clipArea - strokeBox.TopLeft();
// aBorderArea is the stroke-box area in a coordinate space defined by
// the caller. This coordinate space can be svg user space of aForFrame,
// the space of aForFrame's reference-frame, or anything else.
//
// Which coordinate space chosen for aBorderArea is not matter. What
// matter is to ensure returning aClipState->mBGClipArea in the consistent
// coordiante space with aBorderArea. So we evaluate the position of clip
// area base on the position of aBorderArea here.
aClipState->mBGClipArea =
clipAreaRelativeToStrokeBox + aBorderArea.TopLeft();
SetupDirtyRects(aClipState->mBGClipArea, aCallerDirtyRect,
aAppUnitsPerPixel, &aClipState->mDirtyRectInAppUnits,
&aClipState->mDirtyRectInDevPx);
MOZ_ASSERT(aClipState->IsValid());
return;
}
if (layerClip == StyleGeometryBox::NoClip) {
aClipState->mBGClipArea = aCallerDirtyRect;
SetupDirtyRects(aClipState->mBGClipArea, aCallerDirtyRect,
aAppUnitsPerPixel, &aClipState->mDirtyRectInAppUnits,
&aClipState->mDirtyRectInDevPx);
MOZ_ASSERT(aClipState->IsValid());
return;
}
MOZ_ASSERT(!aForFrame->IsFrameOfType(nsIFrame::eSVG) ||
aForFrame->IsSVGOuterSVGFrame());
// Compute the outermost boundary of the area that might be painted.
// Same coordinate space as aBorderArea.
Sides skipSides = aForFrame->GetSkipSides();
nsRect clipBorderArea =
BoxDecorationRectForBorder(aForFrame, aBorderArea, skipSides, &aBorder);
bool haveRoundedCorners = false;
LayoutFrameType fType = aForFrame->Type();
if (fType != LayoutFrameType::TableColGroup &&
fType != LayoutFrameType::TableCol &&
fType != LayoutFrameType::TableRow &&
fType != LayoutFrameType::TableRowGroup) {
haveRoundedCorners = GetRadii(aForFrame, aBorder, aBorderArea,
clipBorderArea, aClipState->mRadii);
}
bool isSolidBorder = aWillPaintBorder && IsOpaqueBorder(aBorder);
if (isSolidBorder && layerClip == StyleGeometryBox::BorderBox) {
// If we have rounded corners, we need to inflate the background
// drawing area a bit to avoid seams between the border and
// background.
layerClip = haveRoundedCorners ? StyleGeometryBox::MozAlmostPadding
: StyleGeometryBox::PaddingBox;
}
aClipState->mBGClipArea = clipBorderArea;
if (aForFrame->IsScrollFrame() &&
StyleImageLayerAttachment::Local == aLayer.mAttachment) {
// As of this writing, this is still in discussion in the CSS Working Group
// http://lists.w3.org/Archives/Public/www-style/2013Jul/0250.html
// The rectangle for 'background-clip' scrolls with the content,
// but the background is also clipped at a non-scrolling 'padding-box'
// like the content. (See below.)
// Therefore, only 'content-box' makes a difference here.
if (layerClip == StyleGeometryBox::ContentBox) {
nsIScrollableFrame* scrollableFrame = do_QueryFrame(aForFrame);
// Clip at a rectangle attached to the scrolled content.
aClipState->mHasAdditionalBGClipArea = true;
aClipState->mAdditionalBGClipArea =
nsRect(aClipState->mBGClipArea.TopLeft() +
scrollableFrame->GetScrolledFrame()->GetPosition()
// For the dir=rtl case:
+ scrollableFrame->GetScrollRange().TopLeft(),
scrollableFrame->GetScrolledRect().Size());
nsMargin padding = aForFrame->GetUsedPadding();
// padding-bottom is ignored on scrollable frames:
// https://bugzilla.mozilla.org/show_bug.cgi?id=748518
padding.bottom = 0;
padding.ApplySkipSides(skipSides);
aClipState->mAdditionalBGClipArea.Deflate(padding);
}
// Also clip at a non-scrolling, rounded-corner 'padding-box',
// same as the scrolled content because of the 'overflow' property.
layerClip = StyleGeometryBox::PaddingBox;
}
// See the comment of StyleGeometryBox::Margin.
// Hitting this assertion means we decide to turn on margin-box support for
// positioned mask from CSS parser and style system. In this case, you
// should *inflate* mBGClipArea by the margin returning from
// aForFrame->GetUsedMargin() in the code chunk bellow.
MOZ_ASSERT(layerClip != StyleGeometryBox::MarginBox,
"StyleGeometryBox::MarginBox rendering is not supported yet.\n");
if (layerClip != StyleGeometryBox::BorderBox &&
layerClip != StyleGeometryBox::Text) {
nsMargin border = aForFrame->GetUsedBorder();
if (layerClip == StyleGeometryBox::MozAlmostPadding) {
// Reduce |border| by 1px (device pixels) on all sides, if
// possible, so that we don't get antialiasing seams between the
// {background|mask} and border.
border.top = std::max(0, border.top - aAppUnitsPerPixel);
border.right = std::max(0, border.right - aAppUnitsPerPixel);
border.bottom = std::max(0, border.bottom - aAppUnitsPerPixel);
border.left = std::max(0, border.left - aAppUnitsPerPixel);
} else if (layerClip != StyleGeometryBox::PaddingBox) {
NS_ASSERTION(layerClip == StyleGeometryBox::ContentBox,
"unexpected background-clip");
border += aForFrame->GetUsedPadding();
}
border.ApplySkipSides(skipSides);
aClipState->mBGClipArea.Deflate(border);
if (haveRoundedCorners) {
nsIFrame::InsetBorderRadii(aClipState->mRadii, border);
}
}
if (haveRoundedCorners) {
auto d2a = aForFrame->PresContext()->AppUnitsPerDevPixel();
nsCSSRendering::ComputePixelRadii(aClipState->mRadii, d2a,
&aClipState->mClippedRadii);
aClipState->mHasRoundedCorners = !aClipState->mClippedRadii.IsEmpty();
}
if (!haveRoundedCorners && aClipState->mHasAdditionalBGClipArea) {
// Do the intersection here to account for the fast path(?) below.
aClipState->mBGClipArea =
aClipState->mBGClipArea.Intersect(aClipState->mAdditionalBGClipArea);
aClipState->mHasAdditionalBGClipArea = false;
}
SetupDirtyRects(aClipState->mBGClipArea, aCallerDirtyRect, aAppUnitsPerPixel,
&aClipState->mDirtyRectInAppUnits,
&aClipState->mDirtyRectInDevPx);
MOZ_ASSERT(aClipState->IsValid());
}
static void SetupImageLayerClip(nsCSSRendering::ImageLayerClipState& aClipState,
gfxContext* aCtx, nscoord aAppUnitsPerPixel,
gfxContextAutoSaveRestore* aAutoSR) {
if (aClipState.mDirtyRectInDevPx.IsEmpty()) {
// Our caller won't draw anything under this condition, so no need
// to set more up.
return;
}
if (aClipState.mCustomClip) {
// We don't support custom clips and rounded corners, arguably a bug, but
// table painting seems to depend on it.
return;
}
// If we have rounded corners, clip all subsequent drawing to the
// rounded rectangle defined by bgArea and bgRadii (we don't know
// whether the rounded corners intrude on the dirtyRect or not).
// Do not do this if we have a caller-provided clip rect --
// as above with bgArea, arguably a bug, but table painting seems
// to depend on it.
if (aClipState.mHasAdditionalBGClipArea) {
gfxRect bgAreaGfx = nsLayoutUtils::RectToGfxRect(
aClipState.mAdditionalBGClipArea, aAppUnitsPerPixel);
bgAreaGfx.Round();
gfxUtils::ConditionRect(bgAreaGfx);
aAutoSR->EnsureSaved(aCtx);
aCtx->SnappedClip(bgAreaGfx);
}
if (aClipState.mHasRoundedCorners) {
Rect bgAreaGfx = NSRectToRect(aClipState.mBGClipArea, aAppUnitsPerPixel);
bgAreaGfx.Round();
if (bgAreaGfx.IsEmpty()) {
// I think it's become possible to hit this since
// https://hg.mozilla.org/mozilla-central/rev/50e934e4979b landed.
NS_WARNING("converted background area should not be empty");
// Make our caller not do anything.
aClipState.mDirtyRectInDevPx.SizeTo(gfxSize(0.0, 0.0));
return;
}
aAutoSR->EnsureSaved(aCtx);
RefPtr<Path> roundedRect = MakePathForRoundedRect(
*aCtx->GetDrawTarget(), bgAreaGfx, aClipState.mClippedRadii);
aCtx->Clip(roundedRect);
}
}
static void DrawBackgroundColor(nsCSSRendering::ImageLayerClipState& aClipState,
gfxContext* aCtx, nscoord aAppUnitsPerPixel) {
if (aClipState.mDirtyRectInDevPx.IsEmpty()) {
// Our caller won't draw anything under this condition, so no need
// to set more up.
return;
}
DrawTarget* drawTarget = aCtx->GetDrawTarget();
// We don't support custom clips and rounded corners, arguably a bug, but
// table painting seems to depend on it.
if (!aClipState.mHasRoundedCorners || aClipState.mCustomClip) {
aCtx->NewPath();
aCtx->SnappedRectangle(aClipState.mDirtyRectInDevPx);
aCtx->Fill();
return;
}
Rect bgAreaGfx = NSRectToRect(aClipState.mBGClipArea, aAppUnitsPerPixel);
bgAreaGfx.Round();
if (bgAreaGfx.IsEmpty()) {
// I think it's become possible to hit this since
// https://hg.mozilla.org/mozilla-central/rev/50e934e4979b landed.
NS_WARNING("converted background area should not be empty");
// Make our caller not do anything.
aClipState.mDirtyRectInDevPx.SizeTo(gfxSize(0.0, 0.0));
return;
}
aCtx->Save();
gfxRect dirty = ThebesRect(bgAreaGfx).Intersect(aClipState.mDirtyRectInDevPx);
aCtx->SnappedClip(dirty);
if (aClipState.mHasAdditionalBGClipArea) {
gfxRect bgAdditionalAreaGfx = nsLayoutUtils::RectToGfxRect(
aClipState.mAdditionalBGClipArea, aAppUnitsPerPixel);
bgAdditionalAreaGfx.Round();
gfxUtils::ConditionRect(bgAdditionalAreaGfx);
aCtx->SnappedClip(bgAdditionalAreaGfx);
}
RefPtr<Path> roundedRect =
MakePathForRoundedRect(*drawTarget, bgAreaGfx, aClipState.mClippedRadii);
aCtx->SetPath(roundedRect);
aCtx->Fill();
aCtx->Restore();
}
enum class ScrollbarColorKind {
Thumb,
Track,
};
static Maybe<nscolor> CalcScrollbarColor(nsIFrame* aFrame,
ScrollbarColorKind aKind) {
ComputedStyle* scrollbarStyle = nsLayoutUtils::StyleForScrollbar(aFrame);
const auto& colors = scrollbarStyle->StyleUI()->mScrollbarColor;
if (colors.IsAuto()) {
return Nothing();
}
const auto& color = aKind == ScrollbarColorKind::Thumb
? colors.AsColors().thumb
: colors.AsColors().track;
return Some(color.CalcColor(*scrollbarStyle));
}
static nscolor GetBackgroundColor(nsIFrame* aFrame, ComputedStyle* aStyle) {
switch (aStyle->StyleDisplay()->mAppearance) {
case StyleAppearance::ScrollbarthumbVertical:
case StyleAppearance::ScrollbarthumbHorizontal: {
if (Maybe<nscolor> overrideColor =
CalcScrollbarColor(aFrame, ScrollbarColorKind::Thumb)) {
return *overrideColor;
}
break;
}
case StyleAppearance::ScrollbarVertical:
case StyleAppearance::ScrollbarHorizontal:
case StyleAppearance::Scrollcorner: {
if (Maybe<nscolor> overrideColor =
CalcScrollbarColor(aFrame, ScrollbarColorKind::Track)) {
return *overrideColor;
}
break;
}
default:
break;
}
return aStyle->GetVisitedDependentColor(&nsStyleBackground::mBackgroundColor);
}
nscolor nsCSSRendering::DetermineBackgroundColor(nsPresContext* aPresContext,
ComputedStyle* aStyle,
nsIFrame* aFrame,
bool& aDrawBackgroundImage,
bool& aDrawBackgroundColor) {
aDrawBackgroundImage = true;
aDrawBackgroundColor = true;
const nsStyleVisibility* visibility = aStyle->StyleVisibility();
if (visibility->mColorAdjust != StyleColorAdjust::Exact &&
aFrame->HonorPrintBackgroundSettings()) {
aDrawBackgroundImage = aPresContext->GetBackgroundImageDraw();
aDrawBackgroundColor = aPresContext->GetBackgroundColorDraw();
}
const nsStyleBackground* bg = aStyle->StyleBackground();
nscolor bgColor;
if (aDrawBackgroundColor) {
bgColor = GetBackgroundColor(aFrame, aStyle);
if (NS_GET_A(bgColor) == 0) {
aDrawBackgroundColor = false;
}
} else {
// If GetBackgroundColorDraw() is false, we are still expected to
// draw color in the background of any frame that's not completely
// transparent, but we are expected to use white instead of whatever
// color was specified.
bgColor = NS_RGB(255, 255, 255);
if (aDrawBackgroundImage || !bg->IsTransparent(aStyle)) {
aDrawBackgroundColor = true;
} else {
bgColor = NS_RGBA(0, 0, 0, 0);
}
}
// We can skip painting the background color if a background image is opaque.
nsStyleImageLayers::Repeat repeat = bg->BottomLayer().mRepeat;
bool xFullRepeat = repeat.mXRepeat == StyleImageLayerRepeat::Repeat ||
repeat.mXRepeat == StyleImageLayerRepeat::Round;
bool yFullRepeat = repeat.mYRepeat == StyleImageLayerRepeat::Repeat ||
repeat.mYRepeat == StyleImageLayerRepeat::Round;
if (aDrawBackgroundColor && xFullRepeat && yFullRepeat &&
bg->BottomLayer().mImage.IsOpaque() &&
bg->BottomLayer().mBlendMode == NS_STYLE_BLEND_NORMAL) {
aDrawBackgroundColor = false;
}
return bgColor;
}
static CompositionOp DetermineCompositionOp(
const nsCSSRendering::PaintBGParams& aParams,
const nsStyleImageLayers& aLayers, uint32_t aLayerIndex) {
if (aParams.layer >= 0) {
// When drawing a single layer, use the specified composition op.
return aParams.compositionOp;
}
const nsStyleImageLayers::Layer& layer = aLayers.mLayers[aLayerIndex];
// When drawing all layers, get the compositon op from each image layer.
if (aParams.paintFlags & nsCSSRendering::PAINTBG_MASK_IMAGE) {
// Always using OP_OVER mode while drawing the bottom mask layer.
if (aLayerIndex == (aLayers.mImageCount - 1)) {
return CompositionOp::OP_OVER;
}
return nsCSSRendering::GetGFXCompositeMode(layer.mComposite);
}
return nsCSSRendering::GetGFXBlendMode(layer.mBlendMode);
}
ImgDrawResult nsCSSRendering::PaintStyleImageLayerWithSC(
const PaintBGParams& aParams, gfxContext& aRenderingCtx,
ComputedStyle* aBackgroundSC, const nsStyleBorder& aBorder) {
MOZ_ASSERT(aParams.frame,
"Frame is expected to be provided to PaintStyleImageLayerWithSC");
// If we're drawing all layers, aCompositonOp is ignored, so make sure that
// it was left at its default value.
MOZ_ASSERT(aParams.layer != -1 ||
aParams.compositionOp == CompositionOp::OP_OVER);
// Check to see if we have an appearance defined. If so, we let the theme
// renderer draw the background and bail out.
// XXXzw this ignores aParams.bgClipRect.
const nsStyleDisplay* displayData = aParams.frame->StyleDisplay();
if (displayData->HasAppearance()) {
nsITheme* theme = aParams.presCtx.GetTheme();
if (theme && theme->ThemeSupportsWidget(&aParams.presCtx, aParams.frame,
displayData->mAppearance)) {
nsRect drawing(aParams.borderArea);
theme->GetWidgetOverflow(aParams.presCtx.DeviceContext(), aParams.frame,
displayData->mAppearance, &drawing);
drawing.IntersectRect(drawing, aParams.dirtyRect);
theme->DrawWidgetBackground(&aRenderingCtx, aParams.frame,
displayData->mAppearance, aParams.borderArea,
drawing);
return ImgDrawResult::SUCCESS;
}
}
// For canvas frames (in the CSS sense) we draw the background color using
// a solid color item that gets added in nsLayoutUtils::PaintFrame,
// or nsSubDocumentFrame::BuildDisplayList (bug 488242). (The solid
// color may be moved into nsDisplayCanvasBackground by
// PresShell::AddCanvasBackgroundColorItem(), and painted by
// nsDisplayCanvasBackground directly.) Either way we don't need to
// paint the background color here.
bool isCanvasFrame = IsCanvasFrame(aParams.frame);
// Determine whether we are drawing background images and/or
// background colors.
bool drawBackgroundImage;
bool drawBackgroundColor;
nscolor bgColor =
DetermineBackgroundColor(&aParams.presCtx, aBackgroundSC, aParams.frame,
drawBackgroundImage, drawBackgroundColor);
bool paintMask = (aParams.paintFlags & PAINTBG_MASK_IMAGE);
const nsStyleImageLayers& layers =
paintMask ? aBackgroundSC->StyleSVGReset()->mMask
: aBackgroundSC->StyleBackground()->mImage;
// If we're drawing a specific layer, we don't want to draw the
// background color.
if ((drawBackgroundColor && aParams.layer >= 0) || paintMask) {
drawBackgroundColor = false;
}
// At this point, drawBackgroundImage and drawBackgroundColor are
// true if and only if we are actually supposed to paint an image or
// color into aDirtyRect, respectively.
if (!drawBackgroundImage && !drawBackgroundColor) {
return ImgDrawResult::SUCCESS;
}
// The 'bgClipArea' (used only by the image tiling logic, far below)
// is the caller-provided aParams.bgClipRect if any, or else the area
// determined by the value of 'background-clip' in
// SetupCurrentBackgroundClip. (Arguably it should be the
// intersection, but that breaks the table painter -- in particular,
// taking the intersection breaks reftests/bugs/403249-1[ab].)
nscoord appUnitsPerPixel = aParams.presCtx.AppUnitsPerDevPixel();
ImageLayerClipState clipState;
if (aParams.bgClipRect) {
clipState.mBGClipArea = *aParams.bgClipRect;
clipState.mCustomClip = true;
clipState.mHasRoundedCorners = false;
SetupDirtyRects(clipState.mBGClipArea, aParams.dirtyRect, appUnitsPerPixel,
&clipState.mDirtyRectInAppUnits,
&clipState.mDirtyRectInDevPx);
} else {
GetImageLayerClip(layers.BottomLayer(), aParams.frame, aBorder,
aParams.borderArea, aParams.dirtyRect,
(aParams.paintFlags & PAINTBG_WILL_PAINT_BORDER),
appUnitsPerPixel, &clipState);
}
// If we might be using a background color, go ahead and set it now.
if (drawBackgroundColor && !isCanvasFrame) {
aRenderingCtx.SetColor(Color::FromABGR(bgColor));
}
// If there is no background image, draw a color. (If there is
// neither a background image nor a color, we wouldn't have gotten
// this far.)
if (!drawBackgroundImage) {
if (!isCanvasFrame) {
DrawBackgroundColor(clipState, &aRenderingCtx, appUnitsPerPixel);
}
return ImgDrawResult::SUCCESS;
}
if (layers.mImageCount < 1) {
// Return if there are no background layers, all work from this point
// onwards happens iteratively on these.
return ImgDrawResult::SUCCESS;
}
MOZ_ASSERT((aParams.layer < 0) ||
(layers.mImageCount > uint32_t(aParams.layer)));
// The background color is rendered over the entire dirty area,
// even if the image isn't.
if (drawBackgroundColor && !isCanvasFrame) {
DrawBackgroundColor(clipState, &aRenderingCtx, appUnitsPerPixel);
}
// Compute the outermost boundary of the area that might be painted.
// Same coordinate space as aParams.borderArea & aParams.bgClipRect.
Sides skipSides = aParams.frame->GetSkipSides();
nsRect paintBorderArea = BoxDecorationRectForBackground(
aParams.frame, aParams.borderArea, skipSides, &aBorder);
nsRect clipBorderArea = BoxDecorationRectForBorder(
aParams.frame, aParams.borderArea, skipSides, &aBorder);
ImgDrawResult result = ImgDrawResult::SUCCESS;
StyleGeometryBox currentBackgroundClip = StyleGeometryBox::BorderBox;
const bool drawAllLayers = (aParams.layer < 0);
uint32_t count = drawAllLayers
? layers.mImageCount // iterate all image layers.
: layers.mImageCount -
aParams.layer; // iterate from the bottom layer to
// the 'aParams.layer-th' layer.
NS_FOR_VISIBLE_IMAGE_LAYERS_BACK_TO_FRONT_WITH_RANGE(
i, layers, layers.mImageCount - 1, count) {
// NOTE: no Save() yet, we do that later by calling autoSR.EnsureSaved(ctx)
// in the cases we need it.
gfxContextAutoSaveRestore autoSR;
const nsStyleImageLayers::Layer& layer = layers.mLayers[i];
if (!aParams.bgClipRect) {
bool isBottomLayer = (i == layers.mImageCount - 1);
if (currentBackgroundClip != layer.mClip || isBottomLayer) {
currentBackgroundClip = layer.mClip;
ImageLayerClipState currentLayerClipState;
if (isBottomLayer) {
currentLayerClipState = clipState;
} else {
// For the bottom layer, we already called GetImageLayerClip above
// and it stored its results in clipState.
GetImageLayerClip(layer, aParams.frame, aBorder, aParams.borderArea,
aParams.dirtyRect,
(aParams.paintFlags & PAINTBG_WILL_PAINT_BORDER),
appUnitsPerPixel, &currentLayerClipState);
}
SetupImageLayerClip(currentLayerClipState, &aRenderingCtx,
appUnitsPerPixel, &autoSR);
if (!clipBorderArea.IsEqualEdges(aParams.borderArea)) {
// We're drawing the background for the joined continuation boxes
// so we need to clip that to the slice that we want for this
// frame.
gfxRect clip = nsLayoutUtils::RectToGfxRect(aParams.borderArea,
appUnitsPerPixel);
autoSR.EnsureSaved(&aRenderingCtx);
aRenderingCtx.SnappedClip(clip);
}
}
}
// Skip the following layer preparing and painting code if the current
// layer is not selected for drawing.
if (aParams.layer >= 0 && i != (uint32_t)aParams.layer) {
continue;
}
nsBackgroundLayerState state = PrepareImageLayer(
&aParams.presCtx, aParams.frame, aParams.paintFlags, paintBorderArea,
clipState.mBGClipArea, layer, nullptr);
result &= state.mImageRenderer.PrepareResult();
// Skip the layer painting code if we found the dirty region is empty.
if (clipState.mDirtyRectInDevPx.IsEmpty()) {
continue;
}
if (!state.mFillArea.IsEmpty()) {
CompositionOp co = DetermineCompositionOp(aParams, layers, i);
if (co != CompositionOp::OP_OVER) {
NS_ASSERTION(aRenderingCtx.CurrentOp() == CompositionOp::OP_OVER,
"It is assumed the initial op is OP_OVER, when it is "
"restored later");
aRenderingCtx.SetOp(co);
}
result &= state.mImageRenderer.DrawLayer(
&aParams.presCtx, aRenderingCtx, state.mDestArea, state.mFillArea,
state.mAnchor + paintBorderArea.TopLeft(),
clipState.mDirtyRectInAppUnits, state.mRepeatSize, aParams.opacity);
if (co != CompositionOp::OP_OVER) {
aRenderingCtx.SetOp(CompositionOp::OP_OVER);
}
}
}
return result;
}
ImgDrawResult
nsCSSRendering::BuildWebRenderDisplayItemsForStyleImageLayerWithSC(
const PaintBGParams& aParams, mozilla::wr::DisplayListBuilder& aBuilder,
mozilla::wr::IpcResourceUpdateQueue& aResources,
const mozilla::layers::StackingContextHelper& aSc,
mozilla::layers::RenderRootStateManager* aManager, nsDisplayItem* aItem,
ComputedStyle* aBackgroundSC, const nsStyleBorder& aBorder) {
MOZ_ASSERT(!(aParams.paintFlags & PAINTBG_MASK_IMAGE));
nscoord appUnitsPerPixel = aParams.presCtx.AppUnitsPerDevPixel();
ImageLayerClipState clipState;
clipState.mBGClipArea = *aParams.bgClipRect;
clipState.mCustomClip = true;
clipState.mHasRoundedCorners = false;
SetupDirtyRects(clipState.mBGClipArea, aParams.dirtyRect, appUnitsPerPixel,
&clipState.mDirtyRectInAppUnits,
&clipState.mDirtyRectInDevPx);
// Compute the outermost boundary of the area that might be painted.
// Same coordinate space as aParams.borderArea & aParams.bgClipRect.
Sides skipSides = aParams.frame->GetSkipSides();
nsRect paintBorderArea = BoxDecorationRectForBackground(
aParams.frame, aParams.borderArea, skipSides, &aBorder);
const nsStyleImageLayers& layers = aBackgroundSC->StyleBackground()->mImage;
const nsStyleImageLayers::Layer& layer = layers.mLayers[aParams.layer];
// Skip the following layer painting code if we found the dirty region is
// empty or the current layer is not selected for drawing.
if (clipState.mDirtyRectInDevPx.IsEmpty()) {
return ImgDrawResult::SUCCESS;
}
ImgDrawResult result = ImgDrawResult::SUCCESS;
nsBackgroundLayerState state =
PrepareImageLayer(&aParams.presCtx, aParams.frame, aParams.paintFlags,
paintBorderArea, clipState.mBGClipArea, layer, nullptr);
result &= state.mImageRenderer.PrepareResult();
if (!state.mFillArea.IsEmpty()) {
return state.mImageRenderer.BuildWebRenderDisplayItemsForLayer(
&aParams.presCtx, aBuilder, aResources, aSc, aManager, aItem,
state.mDestArea, state.mFillArea,
state.mAnchor + paintBorderArea.TopLeft(),
clipState.mDirtyRectInAppUnits, state.mRepeatSize, aParams.opacity);
}
return result;
}
nsRect nsCSSRendering::ComputeImageLayerPositioningArea(
nsPresContext* aPresContext, nsIFrame* aForFrame, const nsRect& aBorderArea,
const nsStyleImageLayers::Layer& aLayer, nsIFrame** aAttachedToFrame,
bool* aOutIsTransformedFixed) {
// Compute {background|mask} origin area relative to aBorderArea now as we
// may need it to compute the effective image size for a CSS gradient.
nsRect positionArea;
StyleGeometryBox layerOrigin = ComputeBoxValue(aForFrame, aLayer.mOrigin);
if (IsSVGStyleGeometryBox(layerOrigin)) {
MOZ_ASSERT(aForFrame->IsFrameOfType(nsIFrame::eSVG) &&
!aForFrame->IsSVGOuterSVGFrame());
*aAttachedToFrame = aForFrame;
positionArea = nsLayoutUtils::ComputeGeometryBox(aForFrame, layerOrigin);
nsPoint toStrokeBoxOffset = nsPoint(0, 0);
if (layerOrigin != StyleGeometryBox::StrokeBox) {
nsRect strokeBox = nsLayoutUtils::ComputeGeometryBox(
aForFrame, StyleGeometryBox::StrokeBox);
toStrokeBoxOffset = positionArea.TopLeft() - strokeBox.TopLeft();
}
// For SVG frames, the return value is relative to the stroke box
return nsRect(toStrokeBoxOffset, positionArea.Size());
}
MOZ_ASSERT(!aForFrame->IsFrameOfType(nsIFrame::eSVG) ||
aForFrame->IsSVGOuterSVGFrame());
LayoutFrameType frameType = aForFrame->Type();
nsIFrame* geometryFrame = aForFrame;
if (MOZ_UNLIKELY(frameType == LayoutFrameType::Scroll &&
StyleImageLayerAttachment::Local == aLayer.mAttachment)) {
nsIScrollableFrame* scrollableFrame = do_QueryFrame(aForFrame);
positionArea = nsRect(scrollableFrame->GetScrolledFrame()->GetPosition()
// For the dir=rtl case:
+ scrollableFrame->GetScrollRange().TopLeft(),
scrollableFrame->GetScrolledRect().Size());
// The ScrolledRects size does not include the borders or scrollbars,
// reverse the handling of background-origin
// compared to the common case below.
if (layerOrigin == StyleGeometryBox::BorderBox) {
nsMargin border = geometryFrame->GetUsedBorder();
border.ApplySkipSides(geometryFrame->GetSkipSides());
positionArea.Inflate(border);
positionArea.Inflate(scrollableFrame->GetActualScrollbarSizes());
} else if (layerOrigin != StyleGeometryBox::PaddingBox) {
nsMargin padding = geometryFrame->GetUsedPadding();
padding.ApplySkipSides(geometryFrame->GetSkipSides());
positionArea.Deflate(padding);
NS_ASSERTION(layerOrigin == StyleGeometryBox::ContentBox,
"unknown background-origin value");
}
*aAttachedToFrame = aForFrame;
return positionArea;
}
if (MOZ_UNLIKELY(frameType == LayoutFrameType::Canvas)) {
geometryFrame = aForFrame->PrincipalChildList().FirstChild();
// geometryFrame might be null if this canvas is a page created
// as an overflow container (e.g. the in-flow content has already
// finished and this page only displays the continuations of
// absolutely positioned content).
if (geometryFrame) {
positionArea = geometryFrame->GetRect();
}
} else {
positionArea = nsRect(nsPoint(0, 0), aBorderArea.Size());
}
// See the comment of StyleGeometryBox::MarginBox.
// Hitting this assertion means we decide to turn on margin-box support for
// positioned mask from CSS parser and style system. In this case, you
// should *inflate* positionArea by the margin returning from
// geometryFrame->GetUsedMargin() in the code chunk bellow.
MOZ_ASSERT(aLayer.mOrigin != StyleGeometryBox::MarginBox,
"StyleGeometryBox::MarginBox rendering is not supported yet.\n");
// {background|mask} images are tiled over the '{background|mask}-clip' area
// but the origin of the tiling is based on the '{background|mask}-origin'
// area.
if (layerOrigin != StyleGeometryBox::BorderBox && geometryFrame) {
nsMargin border = geometryFrame->GetUsedBorder();
if (layerOrigin != StyleGeometryBox::PaddingBox) {
border += geometryFrame->GetUsedPadding();
NS_ASSERTION(layerOrigin == StyleGeometryBox::ContentBox,
"unknown background-origin value");
}
positionArea.Deflate(border);
}
nsIFrame* attachedToFrame = aForFrame;
if (StyleImageLayerAttachment::Fixed == aLayer.mAttachment) {
// If it's a fixed background attachment, then the image is placed
// relative to the viewport, which is the area of the root frame
// in a screen context or the page content frame in a print context.
attachedToFrame = aPresContext->PresShell()->GetRootFrame();
NS_ASSERTION(attachedToFrame, "no root frame");
nsIFrame* pageContentFrame = nullptr;
if (aPresContext->IsPaginated()) {
pageContentFrame = nsLayoutUtils::GetClosestFrameOfType(
aForFrame, LayoutFrameType::PageContent);
if (pageContentFrame) {
attachedToFrame = pageContentFrame;
}
// else this is an embedded shell and its root frame is what we want
}
// If the background is affected by a transform, treat is as if it
// wasn't fixed.
if (nsLayoutUtils::IsTransformed(aForFrame, attachedToFrame)) {
attachedToFrame = aForFrame;
*aOutIsTransformedFixed = true;
} else {
// Set the background positioning area to the viewport's area
// (relative to aForFrame)
positionArea = nsRect(-aForFrame->GetOffsetTo(attachedToFrame),
attachedToFrame->GetSize());
if (!pageContentFrame) {
// Subtract the size of scrollbars.
nsIScrollableFrame* scrollableFrame =
aPresContext->PresShell()->GetRootScrollFrameAsScrollable();
if (scrollableFrame) {
nsMargin scrollbars = scrollableFrame->GetActualScrollbarSizes();
positionArea.Deflate(scrollbars);
}
}
}
}
*aAttachedToFrame = attachedToFrame;
return positionArea;
}
/* static */
nscoord nsCSSRendering::ComputeRoundedSize(nscoord aCurrentSize,
nscoord aPositioningSize) {
float repeatCount = NS_roundf(float(aPositioningSize) / float(aCurrentSize));
if (repeatCount < 1.0f) {
return aPositioningSize;
}
return nscoord(NS_lround(float(aPositioningSize) / repeatCount));
}
// Apply the CSS image sizing algorithm as it applies to background images.
// See http://www.w3.org/TR/css3-background/#the-background-size .
// aIntrinsicSize is the size that the background image 'would like to be'.
// It can be found by calling nsImageRenderer::ComputeIntrinsicSize.
static nsSize ComputeDrawnSizeForBackground(
const CSSSizeOrRatio& aIntrinsicSize, const nsSize& aBgPositioningArea,
const StyleBackgroundSize& aLayerSize, StyleImageLayerRepeat aXRepeat,
StyleImageLayerRepeat aYRepeat) {
nsSize imageSize;
// Size is dictated by cover or contain rules.
if (aLayerSize.IsContain() || aLayerSize.IsCover()) {
nsImageRenderer::FitType fitType = aLayerSize.IsCover()
? nsImageRenderer::COVER
: nsImageRenderer::CONTAIN;
imageSize = nsImageRenderer::ComputeConstrainedSize(
aBgPositioningArea, aIntrinsicSize.mRatio, fitType);
} else {
MOZ_ASSERT(aLayerSize.IsExplicitSize());
const auto& width = aLayerSize.explicit_size.width;
const auto& height = aLayerSize.explicit_size.height;
// No cover/contain constraint, use default algorithm.
CSSSizeOrRatio specifiedSize;
if (width.IsLengthPercentage()) {
specifiedSize.SetWidth(
width.AsLengthPercentage().Resolve(aBgPositioningArea.width));
}
if (height.IsLengthPercentage()) {
specifiedSize.SetHeight(
height.AsLengthPercentage().Resolve(aBgPositioningArea.height));
}
imageSize = nsImageRenderer::ComputeConcreteSize(
specifiedSize, aIntrinsicSize, aBgPositioningArea);
}
// See https://www.w3.org/TR/css3-background/#background-size .
// "If 'background-repeat' is 'round' for one (or both) dimensions, there is a
// second
// step. The UA must scale the image in that dimension (or both dimensions)
// so that it fits a whole number of times in the background positioning
// area."
// "If 'background-repeat' is 'round' for one dimension only and if
// 'background-size'
// is 'auto' for the other dimension, then there is a third step: that other
// dimension is scaled so that the original aspect ratio is restored."
bool isRepeatRoundInBothDimensions =
aXRepeat == StyleImageLayerRepeat::Round &&
aYRepeat == StyleImageLayerRepeat::Round;
// Calculate the rounded size only if the background-size computation
// returned a correct size for the image.
if (imageSize.width && aXRepeat == StyleImageLayerRepeat::Round) {
imageSize.width = nsCSSRendering::ComputeRoundedSize(
imageSize.width, aBgPositioningArea.width);
if (!isRepeatRoundInBothDimensions && aLayerSize.IsExplicitSize() &&
aLayerSize.explicit_size.height.IsAuto()) {
// Restore intrinsic ratio
if (aIntrinsicSize.mRatio) {
imageSize.height =
aIntrinsicSize.mRatio.Inverted().ApplyTo(imageSize.width);
}
}
}
// Calculate the rounded size only if the background-size computation
// returned a correct size for the image.
if (imageSize.height && aYRepeat == StyleImageLayerRepeat::Round) {
imageSize.height = nsCSSRendering::ComputeRoundedSize(
imageSize.height, aBgPositioningArea.height);
if (!isRepeatRoundInBothDimensions && aLayerSize.IsExplicitSize() &&
aLayerSize.explicit_size.width.IsAuto()) {
// Restore intrinsic ratio
if (aIntrinsicSize.mRatio) {
imageSize.width = aIntrinsicSize.mRatio.ApplyTo(imageSize.height);
}
}
}
return imageSize;
}
/* ComputeSpacedRepeatSize
* aImageDimension: the image width/height
* aAvailableSpace: the background positioning area width/height
* aRepeat: determine whether the image is repeated
* Returns the image size plus gap size of app units for use as spacing
*/
static nscoord ComputeSpacedRepeatSize(nscoord aImageDimension,
nscoord aAvailableSpace, bool& aRepeat) {
float ratio = static_cast<float>(aAvailableSpace) / aImageDimension;
if (ratio < 2.0f) { // If you can't repeat at least twice, then don't repeat.
aRepeat = false;
return aImageDimension;
}
aRepeat = true;
return (aAvailableSpace - aImageDimension) / (NSToIntFloor(ratio) - 1);
}
/* static */
nscoord nsCSSRendering::ComputeBorderSpacedRepeatSize(nscoord aImageDimension,
nscoord aAvailableSpace,
nscoord& aSpace) {
int32_t count = aImageDimension ? (aAvailableSpace / aImageDimension) : 0;
aSpace = (aAvailableSpace - aImageDimension * count) / (count + 1);
return aSpace + aImageDimension;
}
nsBackgroundLayerState nsCSSRendering::PrepareImageLayer(
nsPresContext* aPresContext, nsIFrame* aForFrame, uint32_t aFlags,
const nsRect& aBorderArea, const nsRect& aBGClipRect,
const nsStyleImageLayers::Layer& aLayer, bool* aOutIsTransformedFixed) {
/*
* The properties we need to keep in mind when drawing style image
* layers are:
*
* background-image/ mask-image
* background-repeat/ mask-repeat
* background-attachment
* background-position/ mask-position
* background-clip/ mask-clip
* background-origin/ mask-origin
* background-size/ mask-size
* background-blend-mode
* box-decoration-break
* mask-mode
* mask-composite
*
* (background-color applies to the entire element and not to individual
* layers, so it is irrelevant to this method.)
*
* These properties have the following dependencies upon each other when
* determining rendering:
*
* background-image/ mask-image
* no dependencies
* background-repeat/ mask-repeat
* no dependencies
* background-attachment
* no dependencies
* background-position/ mask-position
* depends upon background-size/mask-size (for the image's scaled size)
* and background-break (for the background positioning area)
* background-clip/ mask-clip
* no dependencies
* background-origin/ mask-origin
* depends upon background-attachment (only in the case where that value
* is 'fixed')
* background-size/ mask-size
* depends upon box-decoration-break (for the background positioning area
* for resolving percentages), background-image (for the image's intrinsic
* size), background-repeat (if that value is 'round'), and
* background-origin (for the background painting area, when
* background-repeat is 'round')
* background-blend-mode
* no dependencies
* mask-mode
* no dependencies
* mask-composite
* no dependencies
* box-decoration-break
* no dependencies
*
* As a result of only-if dependencies we don't strictly do a topological
* sort of the above properties when processing, but it's pretty close to one:
*
* background-clip/mask-clip (by caller)
* background-image/ mask-image
* box-decoration-break, background-origin/ mask origin
* background-attachment (postfix for background-origin if 'fixed')
* background-size/ mask-size
* background-position/ mask-position
* background-repeat/ mask-repeat
*/
uint32_t irFlags = 0;
if (aFlags & nsCSSRendering::PAINTBG_SYNC_DECODE_IMAGES) {
irFlags |= nsImageRenderer::FLAG_SYNC_DECODE_IMAGES;
}
if (aFlags & nsCSSRendering::PAINTBG_TO_WINDOW) {
irFlags |= nsImageRenderer::FLAG_PAINTING_TO_WINDOW;
}
nsBackgroundLayerState state(aForFrame, &aLayer.mImage, irFlags);
if (!state.mImageRenderer.PrepareImage()) {
// There's no image or it's not ready to be painted.
if (aOutIsTransformedFixed &&
StyleImageLayerAttachment::Fixed == aLayer.mAttachment) {
nsIFrame* attachedToFrame = aPresContext->PresShell()->GetRootFrame();
NS_ASSERTION(attachedToFrame, "no root frame");
nsIFrame* pageContentFrame = nullptr;
if (aPresContext->IsPaginated()) {
pageContentFrame = nsLayoutUtils::GetClosestFrameOfType(
aForFrame, LayoutFrameType::PageContent);
if (pageContentFrame) {
attachedToFrame = pageContentFrame;
}
// else this is an embedded shell and its root frame is what we want
}
*aOutIsTransformedFixed =
nsLayoutUtils::IsTransformed(aForFrame, attachedToFrame);
}
return state;
}
// The frame to which the background is attached
nsIFrame* attachedToFrame = aForFrame;
// Is the background marked 'fixed', but affected by a transform?
bool transformedFixed = false;
// Compute background origin area relative to aBorderArea now as we may need
// it to compute the effective image size for a CSS gradient.
nsRect positionArea = ComputeImageLayerPositioningArea(
aPresContext, aForFrame, aBorderArea, aLayer, &attachedToFrame,
&transformedFixed);
if (aOutIsTransformedFixed) {
*aOutIsTransformedFixed = transformedFixed;
}
// For background-attachment:fixed backgrounds, we'll override the area
// where the background can be drawn to the viewport.
nsRect bgClipRect = aBGClipRect;
if (StyleImageLayerAttachment::Fixed == aLayer.mAttachment &&
!transformedFixed && (aFlags & nsCSSRendering::PAINTBG_TO_WINDOW)) {
bgClipRect = positionArea + aBorderArea.TopLeft();
}
StyleImageLayerRepeat repeatX = aLayer.mRepeat.mXRepeat;
StyleImageLayerRepeat repeatY = aLayer.mRepeat.mYRepeat;
// Scale the image as specified for background-size and background-repeat.
// Also as required for proper background positioning when background-position
// is defined with percentages.
CSSSizeOrRatio intrinsicSize = state.mImageRenderer.ComputeIntrinsicSize();
nsSize bgPositionSize = positionArea.Size();
nsSize imageSize = ComputeDrawnSizeForBackground(
intrinsicSize, bgPositionSize, aLayer.mSize, repeatX, repeatY);
if (imageSize.width <= 0 || imageSize.height <= 0) return state;
state.mImageRenderer.SetPreferredSize(intrinsicSize, imageSize);
// Compute the anchor point.
//
// relative to aBorderArea.TopLeft() (which is where the top-left
// of aForFrame's border-box will be rendered)
nsPoint imageTopLeft;
// Compute the position of the background now that the background's size is
// determined.
nsImageRenderer::ComputeObjectAnchorPoint(aLayer.mPosition, bgPositionSize,
imageSize, &imageTopLeft,
&state.mAnchor);
state.mRepeatSize = imageSize;
if (repeatX == StyleImageLayerRepeat::Space) {
bool isRepeat;
state.mRepeatSize.width = ComputeSpacedRepeatSize(
imageSize.width, bgPositionSize.width, isRepeat);
if (isRepeat) {
imageTopLeft.x = 0;
state.mAnchor.x = 0;
} else {
repeatX = StyleImageLayerRepeat::NoRepeat;
}
}
if (repeatY == StyleImageLayerRepeat::Space) {
bool isRepeat;
state.mRepeatSize.height = ComputeSpacedRepeatSize(
imageSize.height, bgPositionSize.height, isRepeat);
if (isRepeat) {
imageTopLeft.y = 0;
state.mAnchor.y = 0;
} else {
repeatY = StyleImageLayerRepeat::NoRepeat;
}
}
imageTopLeft += positionArea.TopLeft();
state.mAnchor += positionArea.TopLeft();
state.mDestArea = nsRect(imageTopLeft + aBorderArea.TopLeft(), imageSize);
state.mFillArea = state.mDestArea;
ExtendMode repeatMode = ExtendMode::CLAMP;
if (repeatX == StyleImageLayerRepeat::Repeat ||
repeatX == StyleImageLayerRepeat::Round ||
repeatX == StyleImageLayerRepeat::Space) {
state.mFillArea.x = bgClipRect.x;
state.mFillArea.width = bgClipRect.width;
repeatMode = ExtendMode::REPEAT_X;
}
if (repeatY == StyleImageLayerRepeat::Repeat ||
repeatY == StyleImageLayerRepeat::Round ||
repeatY == StyleImageLayerRepeat::Space) {
state.mFillArea.y = bgClipRect.y;
state.mFillArea.height = bgClipRect.height;
/***
* We're repeating on the X axis already,
* so if we have to repeat in the Y axis,
* we really need to repeat in both directions.
*/
if (repeatMode == ExtendMode::REPEAT_X) {
repeatMode = ExtendMode::REPEAT;
} else {
repeatMode = ExtendMode::REPEAT_Y;
}
}
state.mImageRenderer.SetExtendMode(repeatMode);
state.mImageRenderer.SetMaskOp(aLayer.mMaskMode);
state.mFillArea.IntersectRect(state.mFillArea, bgClipRect);
return state;
}
nsRect nsCSSRendering::GetBackgroundLayerRect(
nsPresContext* aPresContext, nsIFrame* aForFrame, const nsRect& aBorderArea,
const nsRect& aClipRect, const nsStyleImageLayers::Layer& aLayer,
uint32_t aFlags) {
Sides skipSides = aForFrame->GetSkipSides();
nsRect borderArea =
BoxDecorationRectForBackground(aForFrame, aBorderArea, skipSides);
nsBackgroundLayerState state = PrepareImageLayer(
aPresContext, aForFrame, aFlags, borderArea, aClipRect, aLayer);
return state.mFillArea;
}
// Begin table border-collapsing section
// These functions were written to not disrupt the normal ones and yet satisfy
// some additional requirements At some point, all functions should be unified
// to include the additional functionality that these provide
static nscoord RoundIntToPixel(nscoord aValue, nscoord aOneDevPixel,
bool aRoundDown = false) {
if (aOneDevPixel <= 0) {
// We must be rendering to a device that has a resolution greater than
// one device pixel!
// In that case, aValue is as accurate as it's going to get.
return aValue;
}
nscoord halfPixel = NSToCoordRound(aOneDevPixel / 2.0f);
nscoord extra = aValue % aOneDevPixel;
nscoord finalValue = (!aRoundDown && (extra >= halfPixel))
? aValue + (aOneDevPixel - extra)
: aValue - extra;
return finalValue;
}
static nscoord RoundFloatToPixel(float aValue, nscoord aOneDevPixel,
bool aRoundDown = false) {
return RoundIntToPixel(NSToCoordRound(aValue), aOneDevPixel, aRoundDown);
}
static void SetPoly(const Rect& aRect, Point* poly) {
poly[0].x = aRect.x;
poly[0].y = aRect.y;
poly[1].x = aRect.x + aRect.width;
poly[1].y = aRect.y;
poly[2].x = aRect.x + aRect.width;
poly[2].y = aRect.y + aRect.height;
poly[3].x = aRect.x;
poly[3].y = aRect.y + aRect.height;
}
static void DrawDashedSegment(DrawTarget& aDrawTarget, nsRect aRect,
nscoord aDashLength, nscolor aColor,
int32_t aAppUnitsPerDevPixel, bool aHorizontal) {
ColorPattern color(ToDeviceColor(aColor));
DrawOptions drawOptions(1.f, CompositionOp::OP_OVER, AntialiasMode::NONE);
StrokeOptions strokeOptions;
Float dash[2];
dash[0] = Float(aDashLength) / aAppUnitsPerDevPixel;
dash[1] = dash[0];
strokeOptions.mDashPattern = dash;
strokeOptions.mDashLength = MOZ_ARRAY_LENGTH(dash);
if (aHorizontal) {
nsPoint left = (aRect.TopLeft() + aRect.BottomLeft()) / 2;
nsPoint right = (aRect.TopRight() + aRect.BottomRight()) / 2;
strokeOptions.mLineWidth = Float(aRect.height) / aAppUnitsPerDevPixel;
StrokeLineWithSnapping(left, right, aAppUnitsPerDevPixel, aDrawTarget,
color, strokeOptions, drawOptions);
} else {
nsPoint top = (aRect.TopLeft() + aRect.TopRight()) / 2;
nsPoint bottom = (aRect.BottomLeft() + aRect.BottomRight()) / 2;
strokeOptions.mLineWidth = Float(aRect.width) / aAppUnitsPerDevPixel;
StrokeLineWithSnapping(top, bottom, aAppUnitsPerDevPixel, aDrawTarget,
color, strokeOptions, drawOptions);
}
}
static void DrawSolidBorderSegment(
DrawTarget& aDrawTarget, nsRect aRect, nscolor aColor,
int32_t aAppUnitsPerDevPixel,
mozilla::Side aStartBevelSide = mozilla::eSideTop,
nscoord aStartBevelOffset = 0,
mozilla::Side aEndBevelSide = mozilla::eSideTop,
nscoord aEndBevelOffset = 0) {
ColorPattern color(ToDeviceColor(aColor));
DrawOptions drawOptions(1.f, CompositionOp::OP_OVER, AntialiasMode::NONE);
nscoord oneDevPixel = NSIntPixelsToAppUnits(1, aAppUnitsPerDevPixel);
// We don't need to bevel single pixel borders
if ((aRect.width == oneDevPixel) || (aRect.height == oneDevPixel) ||
((0 == aStartBevelOffset) && (0 == aEndBevelOffset))) {
// simple rectangle
aDrawTarget.FillRect(
NSRectToSnappedRect(aRect, aAppUnitsPerDevPixel, aDrawTarget), color,
drawOptions);
} else {
// polygon with beveling
Point poly[4];
SetPoly(NSRectToSnappedRect(aRect, aAppUnitsPerDevPixel, aDrawTarget),
poly);
Float startBevelOffset =
NSAppUnitsToFloatPixels(aStartBevelOffset, aAppUnitsPerDevPixel);
switch (aStartBevelSide) {
case eSideTop:
poly[0].x += startBevelOffset;
break;
case eSideBottom:
poly[3].x += startBevelOffset;
break;
case eSideRight:
poly[1].y += startBevelOffset;
break;
case eSideLeft:
poly[0].y += startBevelOffset;
}
Float endBevelOffset =
NSAppUnitsToFloatPixels(aEndBevelOffset, aAppUnitsPerDevPixel);
switch (aEndBevelSide) {
case eSideTop:
poly[1].x -= endBevelOffset;
break;
case eSideBottom:
poly[2].x -= endBevelOffset;
break;
case eSideRight:
poly[2].y -= endBevelOffset;
break;
case eSideLeft:
poly[3].y -= endBevelOffset;
}
RefPtr<PathBuilder> builder = aDrawTarget.CreatePathBuilder();
builder->MoveTo(poly[0]);
builder->LineTo(poly[1]);
builder->LineTo(poly[2]);
builder->LineTo(poly[3]);
builder->Close();
RefPtr<Path> path = builder->Finish();
aDrawTarget.Fill(path, color, drawOptions);
}
}
static void GetDashInfo(nscoord aBorderLength, nscoord aDashLength,
nscoord aOneDevPixel, int32_t& aNumDashSpaces,
nscoord& aStartDashLength, nscoord& aEndDashLength) {
aNumDashSpaces = 0;
if (aStartDashLength + aDashLength + aEndDashLength >= aBorderLength) {
aStartDashLength = aBorderLength;
aEndDashLength = 0;
} else {
aNumDashSpaces =
(aBorderLength - aDashLength) / (2 * aDashLength); // round down
nscoord extra = aBorderLength - aStartDashLength - aEndDashLength -
(((2 * aNumDashSpaces) - 1) * aDashLength);
if (extra > 0) {
nscoord half = RoundIntToPixel(extra / 2, aOneDevPixel);
aStartDashLength += half;
aEndDashLength += (extra - half);
}
}
}
void nsCSSRendering::DrawTableBorderSegment(
DrawTarget& aDrawTarget, StyleBorderStyle aBorderStyle,
nscolor aBorderColor, const nsRect& aBorder, int32_t aAppUnitsPerDevPixel,
mozilla::Side aStartBevelSide, nscoord aStartBevelOffset,
mozilla::Side aEndBevelSide, nscoord aEndBevelOffset) {
bool horizontal =
((eSideTop == aStartBevelSide) || (eSideBottom == aStartBevelSide));
nscoord oneDevPixel = NSIntPixelsToAppUnits(1, aAppUnitsPerDevPixel);
if ((oneDevPixel >= aBorder.width) || (oneDevPixel >= aBorder.height) ||
(StyleBorderStyle::Dashed == aBorderStyle) ||
(StyleBorderStyle::Dotted == aBorderStyle)) {
// no beveling for 1 pixel border, dash or dot
aStartBevelOffset = 0;
aEndBevelOffset = 0;
}
switch (aBorderStyle) {
case StyleBorderStyle::None:
case StyleBorderStyle::Hidden:
// NS_ASSERTION(false, "style of none or hidden");
break;
case StyleBorderStyle::Dotted:
case StyleBorderStyle::Dashed: {
nscoord dashLength =
(StyleBorderStyle::Dashed == aBorderStyle) ? DASH_LENGTH : DOT_LENGTH;
// make the dash length proportional to the border thickness
dashLength *= (horizontal) ? aBorder.height : aBorder.width;
// make the min dash length for the ends 1/2 the dash length
nscoord minDashLength =
(StyleBorderStyle::Dashed == aBorderStyle)
? RoundFloatToPixel(((float)dashLength) / 2.0f,
aAppUnitsPerDevPixel)
: dashLength;
minDashLength = std::max(minDashLength, oneDevPixel);
nscoord numDashSpaces = 0;
nscoord startDashLength = minDashLength;
nscoord endDashLength = minDashLength;
if (horizontal) {
GetDashInfo(aBorder.width, dashLength, aAppUnitsPerDevPixel,
numDashSpaces, startDashLength, endDashLength);
nsRect rect(aBorder.x, aBorder.y, startDashLength, aBorder.height);
DrawSolidBorderSegment(aDrawTarget, rect, aBorderColor,
aAppUnitsPerDevPixel);
rect.x += startDashLength + dashLength;
rect.width =
aBorder.width - (startDashLength + endDashLength + dashLength);
DrawDashedSegment(aDrawTarget, rect, dashLength, aBorderColor,
aAppUnitsPerDevPixel, horizontal);
rect.x += rect.width;
rect.width = endDashLength;
DrawSolidBorderSegment(aDrawTarget, rect, aBorderColor,
aAppUnitsPerDevPixel);
} else {
GetDashInfo(aBorder.height, dashLength, aAppUnitsPerDevPixel,
numDashSpaces, startDashLength, endDashLength);
nsRect rect(aBorder.x, aBorder.y, aBorder.width, startDashLength);
DrawSolidBorderSegment(aDrawTarget, rect, aBorderColor,
aAppUnitsPerDevPixel);
rect.y += rect.height + dashLength;
rect.height =
aBorder.height - (startDashLength + endDashLength + dashLength);
DrawDashedSegment(aDrawTarget, rect, dashLength, aBorderColor,
aAppUnitsPerDevPixel, horizontal);
rect.y += rect.height;
rect.height = endDashLength;
DrawSolidBorderSegment(aDrawTarget, rect, aBorderColor,
aAppUnitsPerDevPixel);
}
} break;
default:
AutoTArray<SolidBeveledBorderSegment, 3> segments;
GetTableBorderSolidSegments(
segments, aBorderStyle, aBorderColor, aBorder, aAppUnitsPerDevPixel,
aStartBevelSide, aStartBevelOffset, aEndBevelSide, aEndBevelOffset);
for (const auto& segment : segments) {
DrawSolidBorderSegment(
aDrawTarget, segment.mRect, segment.mColor, aAppUnitsPerDevPixel,
segment.mStartBevel.mSide, segment.mStartBevel.mOffset,
segment.mEndBevel.mSide, segment.mEndBevel.mOffset);
}
break;
}
}
void nsCSSRendering::GetTableBorderSolidSegments(
nsTArray<SolidBeveledBorderSegment>& aSegments,
StyleBorderStyle aBorderStyle, nscolor aBorderColor, const nsRect& aBorder,
int32_t aAppUnitsPerDevPixel, mozilla::Side aStartBevelSide,
nscoord aStartBevelOffset, mozilla::Side aEndBevelSide,
nscoord aEndBevelOffset) {
const bool horizontal =
eSideTop == aStartBevelSide || eSideBottom == aStartBevelSide;
const nscoord oneDevPixel = NSIntPixelsToAppUnits(1, aAppUnitsPerDevPixel);
switch (aBorderStyle) {
case StyleBorderStyle::None:
case StyleBorderStyle::Hidden:
return;
case StyleBorderStyle::Dotted:
case StyleBorderStyle::Dashed:
MOZ_ASSERT_UNREACHABLE("Caller should have checked");
return;
case StyleBorderStyle::Groove:
case StyleBorderStyle::Ridge:
if ((horizontal && (oneDevPixel >= aBorder.height)) ||
(!horizontal && (oneDevPixel >= aBorder.width))) {
aSegments.AppendElement(
SolidBeveledBorderSegment{aBorder,
aBorderColor,
{aStartBevelSide, aStartBevelOffset},
{aEndBevelSide, aEndBevelOffset}});
} else {
nscoord startBevel =
(aStartBevelOffset > 0)
? RoundFloatToPixel(0.5f * (float)aStartBevelOffset,
aAppUnitsPerDevPixel, true)
: 0;
nscoord endBevel =
(aEndBevelOffset > 0)
? RoundFloatToPixel(0.5f * (float)aEndBevelOffset,
aAppUnitsPerDevPixel, true)
: 0;
mozilla::Side ridgeGrooveSide = (horizontal) ? eSideTop : eSideLeft;
// FIXME: In theory, this should use the visited-dependent
// background color, but I don't care.
nscolor bevelColor =
MakeBevelColor(ridgeGrooveSide, aBorderStyle, aBorderColor);
nsRect rect(aBorder);
nscoord half;
if (horizontal) { // top, bottom
half = RoundFloatToPixel(0.5f * (float)aBorder.height,
aAppUnitsPerDevPixel);
rect.height = half;
if (eSideTop == aStartBevelSide) {
rect.x += startBevel;
rect.width -= startBevel;
}
if (eSideTop == aEndBevelSide) {
rect.width -= endBevel;
}
aSegments.AppendElement(
SolidBeveledBorderSegment{rect,
bevelColor,
{aStartBevelSide, startBevel},
{aEndBevelSide, endBevel}});
} else { // left, right
half = RoundFloatToPixel(0.5f * (float)aBorder.width,
aAppUnitsPerDevPixel);
rect.width = half;
if (eSideLeft == aStartBevelSide) {
rect.y += startBevel;
rect.height -= startBevel;
}
if (eSideLeft == aEndBevelSide) {
rect.height -= endBevel;
}
aSegments.AppendElement(
SolidBeveledBorderSegment{rect,
bevelColor,
{aStartBevelSide, startBevel},
{aEndBevelSide, endBevel}});
}
rect = aBorder;
ridgeGrooveSide =
(eSideTop == ridgeGrooveSide) ? eSideBottom : eSideRight;
// FIXME: In theory, this should use the visited-dependent
// background color, but I don't care.
bevelColor =
MakeBevelColor(ridgeGrooveSide, aBorderStyle, aBorderColor);
if (horizontal) {
rect.y = rect.y + half;
rect.height = aBorder.height - half;
if (eSideBottom == aStartBevelSide) {
rect.x += startBevel;
rect.width -= startBevel;
}
if (eSideBottom == aEndBevelSide) {
rect.width -= endBevel;
}
aSegments.AppendElement(
SolidBeveledBorderSegment{rect,
bevelColor,
{aStartBevelSide, startBevel},
{aEndBevelSide, endBevel}});
} else {
rect.x = rect.x + half;
rect.width = aBorder.width - half;
if (eSideRight == aStartBevelSide) {
rect.y += aStartBevelOffset - startBevel;
rect.height -= startBevel;
}
if (eSideRight == aEndBevelSide) {
rect.height -= endBevel;
}
aSegments.AppendElement(
SolidBeveledBorderSegment{rect,
bevelColor,
{aStartBevelSide, startBevel},
{aEndBevelSide, endBevel}});
}
}
break;
case StyleBorderStyle::Double:
// We can only do "double" borders if the thickness of the border
// is more than 2px. Otherwise, we fall through to painting a
// solid border.
if ((aBorder.width > 2 * oneDevPixel || horizontal) &&
(aBorder.height > 2 * oneDevPixel || !horizontal)) {
nscoord startBevel =
(aStartBevelOffset > 0)
? RoundFloatToPixel(0.333333f * (float)aStartBevelOffset,
aAppUnitsPerDevPixel)
: 0;
nscoord endBevel =
(aEndBevelOffset > 0)
? RoundFloatToPixel(0.333333f * (float)aEndBevelOffset,
aAppUnitsPerDevPixel)
: 0;
if (horizontal) { // top, bottom
nscoord thirdHeight = RoundFloatToPixel(
0.333333f * (float)aBorder.height, aAppUnitsPerDevPixel);
// draw the top line or rect
nsRect topRect(aBorder.x, aBorder.y, aBorder.width, thirdHeight);
if (eSideTop == aStartBevelSide) {
topRect.x += aStartBevelOffset - startBevel;
topRect.width -= aStartBevelOffset - startBevel;
}
if (eSideTop == aEndBevelSide) {
topRect.width -= aEndBevelOffset - endBevel;
}
aSegments.AppendElement(
SolidBeveledBorderSegment{topRect,
aBorderColor,
{aStartBevelSide, startBevel},
{aEndBevelSide, endBevel}});
// draw the botom line or rect
nscoord heightOffset = aBorder.height - thirdHeight;
nsRect bottomRect(aBorder.x, aBorder.y + heightOffset, aBorder.width,
aBorder.height - heightOffset);
if (eSideBottom == aStartBevelSide) {
bottomRect.x += aStartBevelOffset - startBevel;
bottomRect.width -= aStartBevelOffset - startBevel;
}
if (eSideBottom == aEndBevelSide) {
bottomRect.width -= aEndBevelOffset - endBevel;
}
aSegments.AppendElement(
SolidBeveledBorderSegment{bottomRect,
aBorderColor,
{aStartBevelSide, startBevel},
{aEndBevelSide, endBevel}});
} else { // left, right
nscoord thirdWidth = RoundFloatToPixel(
0.333333f * (float)aBorder.width, aAppUnitsPerDevPixel);
nsRect leftRect(aBorder.x, aBorder.y, thirdWidth, aBorder.height);
if (eSideLeft == aStartBevelSide) {
leftRect.y += aStartBevelOffset - startBevel;
leftRect.height -= aStartBevelOffset - startBevel;
}
if (eSideLeft == aEndBevelSide) {
leftRect.height -= aEndBevelOffset - endBevel;
}
aSegments.AppendElement(
SolidBeveledBorderSegment{leftRect,
aBorderColor,
{aStartBevelSide, startBevel},
{aEndBevelSide, endBevel}});
nscoord widthOffset = aBorder.width - thirdWidth;
nsRect rightRect(aBorder.x + widthOffset, aBorder.y,
aBorder.width - widthOffset, aBorder.height);
if (eSideRight == aStartBevelSide) {
rightRect.y += aStartBevelOffset - startBevel;
rightRect.height -= aStartBevelOffset - startBevel;
}
if (eSideRight == aEndBevelSide) {
rightRect.height -= aEndBevelOffset - endBevel;
}
aSegments.AppendElement(
SolidBeveledBorderSegment{rightRect,
aBorderColor,
{aStartBevelSide, startBevel},
{aEndBevelSide, endBevel}});
}
break;
}
// else fall through to solid
[[fallthrough]];
case StyleBorderStyle::Solid:
aSegments.AppendElement(
SolidBeveledBorderSegment{aBorder,
aBorderColor,
{aStartBevelSide, aStartBevelOffset},
{aEndBevelSide, aEndBevelOffset}});
break;
case StyleBorderStyle::Outset:
case StyleBorderStyle::Inset:
MOZ_ASSERT_UNREACHABLE(
"inset, outset should have been converted to groove, ridge");
break;
}
}
// End table border-collapsing section
Rect nsCSSRendering::ExpandPaintingRectForDecorationLine(
nsIFrame* aFrame, const uint8_t aStyle, const Rect& aClippedRect,
const Float aICoordInFrame, const Float aCycleLength, bool aVertical) {
switch (aStyle) {
case NS_STYLE_TEXT_DECORATION_STYLE_DOTTED:
case NS_STYLE_TEXT_DECORATION_STYLE_DASHED:
case NS_STYLE_TEXT_DECORATION_STYLE_WAVY:
break;
default:
NS_ERROR("Invalid style was specified");
return aClippedRect;
}
nsBlockFrame* block = nullptr;
// Note that when we paint the decoration lines in relative positioned
// box, we should paint them like all of the boxes are positioned as static.
nscoord framePosInBlockAppUnits = 0;
for (nsIFrame* f = aFrame; f; f = f->GetParent()) {
block = do_QueryFrame(f);
if (block) {
break;
}
framePosInBlockAppUnits +=
aVertical ? f->GetNormalPosition().y : f->GetNormalPosition().x;
}
NS_ENSURE_TRUE(block, aClippedRect);
nsPresContext* pc = aFrame->PresContext();
Float framePosInBlock =
Float(pc->AppUnitsToGfxUnits(framePosInBlockAppUnits));
int32_t rectPosInBlock = int32_t(NS_round(framePosInBlock + aICoordInFrame));
int32_t extraStartEdge =
rectPosInBlock - (rectPosInBlock / int32_t(aCycleLength) * aCycleLength);
Rect rect(aClippedRect);
if (aVertical) {
rect.y -= extraStartEdge;
rect.height += extraStartEdge;
} else {
rect.x -= extraStartEdge;
rect.width += extraStartEdge;
}
return rect;
}
// Converts a GfxFont to an SkFont
// Either returns true if it was successful, or false if something went wrong
static bool GetSkFontFromGfxFont(DrawTarget& aDrawTarget, gfxFont* aFont,
SkFont& aSkFont) {
RefPtr<ScaledFont> scaledFont = aFont->GetScaledFont(&aDrawTarget);
if (!scaledFont) {
return false;
}
ScaledFontBase* fontBase = static_cast<ScaledFontBase*>(scaledFont.get());
SkTypeface* typeface = fontBase->GetSkTypeface();
if (!typeface) {
return false;
}
aSkFont = SkFont(sk_ref_sp(typeface), SkFloatToScalar(fontBase->GetSize()));
return true;
}
// Computes data used to position the decoration line within a
// SkTextBlob, data is returned through aBounds
static void GetPositioning(
const nsCSSRendering::PaintDecorationLineParams& aParams, const Rect& aRect,
Float aOneCSSPixel, Float aCenterBaselineOffset, SkScalar aBounds[]) {
/**
* How Positioning in Skia Works
* Take the letter "n" for example
* We set textPos as 0, 0
* This is represented in Skia like so (not to scale)
* ^
* -10px | _ __
* | | '_ \
* -5px | | | | |
* y-axis | |_| |_|
* (0,0) ----------------------->
* | 5px 10px
* 5px |
* |
* 10px |
* v
* 0 on the x axis is a line that touches the bottom of the n
* (0,0) is the bottom left-hand corner of the n character
* Moving "up" from the n is going in a negative y direction
* Moving "down" from the n is going in a positive y direction
*
* The intercepts that are returned in this arrangement will be
* offset by the original point it starts at. (This happens in
* the SkipInk function below).
*
* In Skia, text MUST be laid out such that the next character
* in the RunBuffer is further along the x-axis than the previous
* character, otherwise there is undefined/strange behavior.
*/
Float rectThickness = aParams.vertical ? aRect.Width() : aRect.Height();
// the upper and lower lines/edges of the under or over line
SkScalar upperLine, lowerLine;
if (aParams.decoration == mozilla::StyleTextDecorationLine::OVERLINE) {
lowerLine =
-aParams.offset + aParams.defaultLineThickness - aCenterBaselineOffset;
upperLine = lowerLine - rectThickness;
} else {
// underlines in vertical text are offset from the center of
// the text, and not the baseline
// Skia sets the text at it's baseline so we have to offset it
// for text in vertical-* writing modes
upperLine = -aParams.offset - aCenterBaselineOffset;
lowerLine = upperLine + rectThickness;
}
// set up the bounds, add in a little padding to the thickness of the line
// (unless the line is <= 1 CSS pixel thick)
Float lineThicknessPadding = aParams.lineSize.height > aOneCSSPixel
? 0.25f * aParams.lineSize.height
: 0;
// don't allow padding greater than 0.75 CSS pixel
lineThicknessPadding = std::min(lineThicknessPadding, 0.75f * aOneCSSPixel);
aBounds[0] = upperLine - lineThicknessPadding;
aBounds[1] = lowerLine + lineThicknessPadding;
}
// positions an individual glyph according to the given offset
static SkPoint GlyphPosition(const gfxTextRun::DetailedGlyph& aGlyph,
const SkPoint& aTextPos,
int32_t aAppUnitsPerDevPixel) {
SkPoint point = {aGlyph.mOffset.x, aGlyph.mOffset.y};
// convert to device pixels
point.fX /= (float)aAppUnitsPerDevPixel;
point.fY /= (float)aAppUnitsPerDevPixel;
// add offsets
point.fX += aTextPos.fX;
point.fY += aTextPos.fY;
return point;
}
// returns a count of all the glyphs that will be rendered
// excludes ligature continuations, includes the number of individual
// glyph records. This includes the number of DetailedGlyphs that a single
// CompressedGlyph record points to. This function is necessary because Skia
// needs the total length of glyphs to add to it's run buffer before it creates
// the RunBuffer object, and this cannot be resized later.
static uint32_t CountAllGlyphs(
const gfxTextRun* aTextRun,
const gfxTextRun::CompressedGlyph* aCompressedGlyph, uint32_t aStringStart,
uint32_t aStringEnd) {
uint32_t totalGlyphCount = 0;
for (const gfxTextRun::CompressedGlyph* cg = aCompressedGlyph + aStringStart;
cg < aCompressedGlyph + aStringEnd; ++cg) {
totalGlyphCount += cg->IsSimpleGlyph() ? 1 : cg->GetGlyphCount();
}
return totalGlyphCount;
}
static void AddDetailedGlyph(const SkTextBlobBuilder::RunBuffer& aRunBuffer,
const gfxTextRun::DetailedGlyph& aGlyph,
int aIndex, float aAppUnitsPerDevPixel,
SkPoint& aTextPos) {
// add glyph ID to the run buffer at i
aRunBuffer.glyphs[aIndex] = aGlyph.mGlyphID;
// position the glyph correctly using the detailed offsets
SkPoint position = GlyphPosition(aGlyph, aTextPos, aAppUnitsPerDevPixel);
aRunBuffer.pos[2 * aIndex] = position.fX;
aRunBuffer.pos[(2 * aIndex) + 1] = position.fY;
// increase aTextPos.fx by the advance
aTextPos.fX += ((float)aGlyph.mAdvance / aAppUnitsPerDevPixel);
}
static void AddSimpleGlyph(const SkTextBlobBuilder::RunBuffer& aRunBuffer,
const gfxTextRun::CompressedGlyph& aGlyph,
int aIndex, float aAppUnitsPerDevPixel,
SkPoint& aTextPos) {
aRunBuffer.glyphs[aIndex] = aGlyph.GetSimpleGlyph();
// simple glyphs are offset from 0, so we'll just use textPos
aRunBuffer.pos[2 * aIndex] = aTextPos.fX;
aRunBuffer.pos[(2 * aIndex) + 1] = aTextPos.fY;
// increase aTextPos.fX by the advance
aTextPos.fX += ((float)aGlyph.GetSimpleAdvance() / aAppUnitsPerDevPixel);
}
// Sets up a Skia TextBlob of the specified font, text position, and made up of
// the glyphs between aStringStart and aStringEnd. Handles RTL and LTR text
// and positions each glyph within the text blob
static sk_sp<const SkTextBlob> CreateTextBlob(
const gfxTextRun* aTextRun,
const gfxTextRun::CompressedGlyph* aCompressedGlyph, const SkFont& aFont,
const gfxTextRun::PropertyProvider::Spacing* aSpacing,
uint32_t aStringStart, uint32_t aStringEnd, float aAppUnitsPerDevPixel,
SkPoint& aTextPos, int32_t& aSpacingOffset) {
// allocate space for the run buffer, then fill it with the glyphs
uint32_t len =
CountAllGlyphs(aTextRun, aCompressedGlyph, aStringStart, aStringEnd);
if (len <= 0) {
return nullptr;
}
SkTextBlobBuilder builder;
const SkTextBlobBuilder::RunBuffer& run = builder.allocRunPos(aFont, len);
// RTL text should be read in by glyph starting at aStringEnd - 1 down until
// aStringStart.
bool isRTL = aTextRun->IsRightToLeft();
uint32_t currIndex = isRTL ? aStringEnd - 1 : aStringStart; // textRun index
// currIndex will be advanced by |step| until it reaches |limit|, which is the
// final index to be handled (NOT one beyond the final index)
int step = isRTL ? -1 : 1;
uint32_t limit = isRTL ? aStringStart : aStringEnd - 1;
uint32_t i = 0; // index into the SkTextBlob we're building
while (true) {
// Loop exit test is below, just before we update currIndex.
aTextPos.fX +=
isRTL ? aSpacing[aSpacingOffset].mAfter / aAppUnitsPerDevPixel
: aSpacing[aSpacingOffset].mBefore / aAppUnitsPerDevPixel;
if (aCompressedGlyph[currIndex].IsSimpleGlyph()) {
MOZ_ASSERT(i < len, "glyph count error!");
AddSimpleGlyph(run, aCompressedGlyph[currIndex], i, aAppUnitsPerDevPixel,
aTextPos);
i++;
} else {
// if it's detailed, potentially add multiple into run.glyphs
uint32_t count = aCompressedGlyph[currIndex].GetGlyphCount();
if (count > 0) {
gfxTextRun::DetailedGlyph* detailGlyph =
aTextRun->GetDetailedGlyphs(currIndex);
for (uint32_t d = isRTL ? count - 1 : 0; count; count--, d += step) {
MOZ_ASSERT(i < len, "glyph count error!");
AddDetailedGlyph(run, detailGlyph[d], i, aAppUnitsPerDevPixel,
aTextPos);
i++;
}
}
}
aTextPos.fX += isRTL
? aSpacing[aSpacingOffset].mBefore / aAppUnitsPerDevPixel
: aSpacing[aSpacingOffset].mAfter / aAppUnitsPerDevPixel;
aSpacingOffset += step;
if (currIndex == limit) {
break;
}
currIndex += step;
}
MOZ_ASSERT(i == len, "glyph count error!");
return builder.make();
}
// Given a TextBlob, the bounding lines, and the set of current intercepts this
// function adds the intercepts for the current TextBlob into the given set of
// previoulsy calculated intercepts. This set is either of length 0, or a
// multiple of 2 (since every intersection with a piece of text results in two
// intercepts: entering/exiting)
static void GetTextIntercepts(const sk_sp<const SkTextBlob>& aBlob,
const SkScalar aBounds[],
nsTArray<SkScalar>& aIntercepts) {
// https://skia.org/user/api/SkTextBlob_Reference#Text_Blob_Text_Intercepts
int count = aBlob->getIntercepts(aBounds, nullptr);
if (count < 2) {
return;
}
aBlob->getIntercepts(aBounds, aIntercepts.AppendElements(count));
}
// This function, given a set of intercepts that represent each intersection
// between an under/overline and text, makes a series of calls to
// PaintDecorationLineInternal that paints a series of clip rects which
// implement the text-decoration-skip-ink property
// Logic for where to place each clipped rect, and the length of each rect is
// included here
static void SkipInk(nsIFrame* aFrame, DrawTarget& aDrawTarget,
const nsCSSRendering::PaintDecorationLineParams& aParams,
const nsTArray<SkScalar>& aIntercepts, Float aPadding,
Rect& aRect) {
nsCSSRendering::PaintDecorationLineParams clipParams = aParams;
int length = aIntercepts.Length();
SkScalar startIntercept = 0;
SkScalar endIntercept = 0;
// keep track of the direction we are drawing the clipped rects in
// for sideways text, our intercepts from the first glyph are actually
// decreasing (towards the top edge of the page), so we use a negative
// direction
Float dir = 1.0f;
Float lineStart = aParams.vertical ? aParams.pt.y : aParams.pt.x;
Float lineEnd = lineStart + aParams.lineSize.width;
if (aParams.sidewaysLeft) {
dir = -1.0f;
std::swap(lineStart, lineEnd);
}
for (int i = 0; i <= length; i += 2) {
// handle start/end edge cases and set up general case
startIntercept = (i > 0) ? (dir * aIntercepts[i - 1]) + lineStart
: lineStart - (dir * aPadding);
endIntercept = (i < length) ? (dir * aIntercepts[i]) + lineStart
: lineEnd + (dir * aPadding);
// remove padding at both ends for width
// the start of the line is calculated so the padding removes just
// enough so that the line starts at its normal position
clipParams.lineSize.width =
(dir * (endIntercept - startIntercept)) - (2.0 * aPadding);
if (aParams.vertical) {
aRect.height = clipParams.lineSize.width;
} else {
aRect.width = clipParams.lineSize.width;
}
// Don't draw decoration lines that have a smaller width than 1, or half
// the line-end padding dimension.
if (clipParams.lineSize.width < std::max(aPadding * 0.5, 1.0)) {
continue;
}
// start the line right after the intercept's location plus room for
// padding
if (aParams.vertical) {
clipParams.pt.y = aParams.sidewaysLeft ? endIntercept + aPadding
: startIntercept + aPadding;
aRect.y = clipParams.pt.y;
} else {
clipParams.pt.x = startIntercept + aPadding;
aRect.x = clipParams.pt.x;
}
nsCSSRendering::PaintDecorationLineInternal(aFrame, aDrawTarget, clipParams,
aRect);
}
}
void nsCSSRendering::PaintDecorationLine(
nsIFrame* aFrame, DrawTarget& aDrawTarget,
const PaintDecorationLineParams& aParams) {
NS_ASSERTION(aParams.style != NS_STYLE_TEXT_DECORATION_STYLE_NONE,
"aStyle is none");
Rect rect = ToRect(GetTextDecorationRectInternal(aParams.pt, aParams));
if (rect.IsEmpty() || !rect.Intersects(aParams.dirtyRect)) {
return;
}
if (aParams.decoration != StyleTextDecorationLine::UNDERLINE &&
aParams.decoration != StyleTextDecorationLine::OVERLINE &&
aParams.decoration != StyleTextDecorationLine::LINE_THROUGH) {
MOZ_ASSERT_UNREACHABLE("Invalid text decoration value");
return;
}
// Check if decoration line will skip past ascenders/descenders
// text-decoration-skip-ink only applies to overlines/underlines
mozilla::StyleTextDecorationSkipInk skipInk =
aFrame->StyleText()->mTextDecorationSkipInk;
bool skipInkEnabled =
skipInk == mozilla::StyleTextDecorationSkipInk::Auto &&
aParams.decoration != StyleTextDecorationLine::LINE_THROUGH &&
StaticPrefs::layout_css_text_decoration_skip_ink_enabled();
if (!skipInkEnabled || aParams.glyphRange.Length() == 0) {
PaintDecorationLineInternal(aFrame, aDrawTarget, aParams, rect);
return;
}
// check if the frame is a text frame or not
nsTextFrame* textFrame = nullptr;
if (aFrame->IsTextFrame()) {
textFrame = static_cast<nsTextFrame*>(aFrame);
} else {
PaintDecorationLineInternal(aFrame, aDrawTarget, aParams, rect);
return;
}
// get text run and current text offset (for line wrapping)
gfxTextRun* textRun =
textFrame->GetTextRun(nsTextFrame::TextRunType::eInflated);
// used for conversions from app units to device pixels
int32_t appUnitsPerDevPixel = aFrame->PresContext()->AppUnitsPerDevPixel();
// pointer to the array of glyphs for this TextRun
gfxTextRun::CompressedGlyph* characterGlyphs = textRun->GetCharacterGlyphs();
// get positioning info
SkPoint textPos = {0, aParams.baselineOffset};
SkScalar bounds[] = {0, 0};
Float oneCSSPixel = aFrame->PresContext()->CSSPixelsToDevPixels(1.0f);
if (!textRun->UseCenterBaseline()) {
GetPositioning(aParams, rect, oneCSSPixel, 0, bounds);
}
// array for the text intercepts
AutoTArray<SkScalar, 256> intercepts;
// array for spacing data
AutoTArray<gfxTextRun::PropertyProvider::Spacing, 64> spacing;
spacing.SetLength(aParams.glyphRange.Length());
if (aParams.provider != nullptr) {
aParams.provider->GetSpacing(aParams.glyphRange, spacing.Elements());
}
// loop through each glyph run
// in most cases there will only be one
bool isRTL = textRun->IsRightToLeft();
int32_t spacingOffset = isRTL ? aParams.glyphRange.Length() - 1 : 0;
gfxTextRun::GlyphRunIterator iter(textRun, aParams.glyphRange, isRTL);
while (iter.NextRun()) {
if (iter.GetGlyphRun()->mOrientation ==
mozilla::gfx::ShapedTextFlags::TEXT_ORIENT_VERTICAL_UPRIGHT ||
iter.GetGlyphRun()->mIsCJK) {
// We don't support upright text in vertical modes currently
// (see https://bugzilla.mozilla.org/show_bug.cgi?id=1572294),
// but we do need to update textPos so that following runs will be
// correctly positioned.
// We also don't apply skip-ink to CJK text runs because many fonts
// have an underline that looks really bad if this is done
// (see https://bugzilla.mozilla.org/show_bug.cgi?id=1573249).
textPos.fX +=
textRun->GetAdvanceWidth(
gfxTextRun::Range(iter.GetStringStart(), iter.GetStringEnd()),
aParams.provider) /
appUnitsPerDevPixel;
continue;
}
gfxFont* font = iter.GetGlyphRun()->mFont;
// Don't try to apply skip-ink to 'sbix' fonts like Apple Color Emoji,
// because old macOS (10.9) may crash trying to retrieve glyph paths
// that don't exist.
if (font->GetFontEntry()->HasFontTable(TRUETYPE_TAG('s', 'b', 'i', 'x'))) {
continue;
}
// get a Skia version of the glyph run's font
SkFont skiafont;
if (!GetSkFontFromGfxFont(aDrawTarget, font, skiafont)) {
PaintDecorationLineInternal(aFrame, aDrawTarget, aParams, rect);
return;
}
// Create a text blob with correctly positioned glyphs. This also updates
// textPos.fX with the advance of the glyphs.
sk_sp<const SkTextBlob> textBlob =
CreateTextBlob(textRun, characterGlyphs, skiafont, spacing.Elements(),
iter.GetStringStart(), iter.GetStringEnd(),
(float)appUnitsPerDevPixel, textPos, spacingOffset);
if (!textBlob) {
continue;
}
if (textRun->UseCenterBaseline()) {
// writing modes that use a center baseline need to be adjusted on a
// font-by-font basis since Skia lines up the text on a alphabetic
// baseline, but for some vertical-* writing modes the offset is from the
// center.
gfxFont::Metrics metrics = font->GetMetrics(nsFontMetrics::eHorizontal);
Float centerToBaseline = (metrics.emAscent - metrics.emDescent) / 2.0f;
GetPositioning(aParams, rect, oneCSSPixel, centerToBaseline, bounds);
}
// compute the text intercepts that need to be skipped
GetTextIntercepts(textBlob, bounds, intercepts);
}
bool needsSkipInk = intercepts.Length() > 0;
if (needsSkipInk) {
// Padding between glyph intercepts and the decoration line: we use the
// decoration line thickness, clamped to a minimum of 1px and a maximum
// of 0.2em.
Float padding =
std::min(std::max(aParams.lineSize.height, oneCSSPixel),
Float(textRun->GetFontGroup()->GetStyle()->size / 5.0));
SkipInk(aFrame, aDrawTarget, aParams, intercepts, padding, rect);
} else {
PaintDecorationLineInternal(aFrame, aDrawTarget, aParams, rect);
}
}
void nsCSSRendering::PaintDecorationLineInternal(
nsIFrame* aFrame, DrawTarget& aDrawTarget,
const PaintDecorationLineParams& aParams, Rect aRect) {
Float lineThickness = std::max(NS_round(aParams.lineSize.height), 1.0);
Color color = ToDeviceColor(aParams.color);
ColorPattern colorPat(color);
StrokeOptions strokeOptions(lineThickness);
DrawOptions drawOptions;
Float dash[2];
AutoPopClips autoPopClips(&aDrawTarget);
mozilla::layout::TextDrawTarget* textDrawer = nullptr;
if (aDrawTarget.GetBackendType() == BackendType::WEBRENDER_TEXT) {
textDrawer = static_cast<mozilla::layout::TextDrawTarget*>(&aDrawTarget);
}
switch (aParams.style) {
case NS_STYLE_TEXT_DECORATION_STYLE_SOLID:
case NS_STYLE_TEXT_DECORATION_STYLE_DOUBLE:
break;
case NS_STYLE_TEXT_DECORATION_STYLE_DASHED: {
autoPopClips.PushClipRect(aRect);
Float dashWidth = lineThickness * DOT_LENGTH * DASH_LENGTH;
dash[0] = dashWidth;
dash[1] = dashWidth;
strokeOptions.mDashPattern = dash;
strokeOptions.mDashLength = MOZ_ARRAY_LENGTH(dash);
strokeOptions.mLineCap = CapStyle::BUTT;
aRect = ExpandPaintingRectForDecorationLine(
aFrame, aParams.style, aRect, aParams.icoordInFrame, dashWidth * 2,
aParams.vertical);
// We should continue to draw the last dash even if it is not in the rect.
aRect.width += dashWidth;
break;
}
case NS_STYLE_TEXT_DECORATION_STYLE_DOTTED: {
autoPopClips.PushClipRect(aRect);
Float dashWidth = lineThickness * DOT_LENGTH;
if (lineThickness > 2.0) {
dash[0] = 0.f;
dash[1] = dashWidth * 2.f;
strokeOptions.mLineCap = CapStyle::ROUND;
} else {
dash[0] = dashWidth;
dash[1] = dashWidth;
}
strokeOptions.mDashPattern = dash;
strokeOptions.mDashLength = MOZ_ARRAY_LENGTH(dash);
aRect = ExpandPaintingRectForDecorationLine(
aFrame, aParams.style, aRect, aParams.icoordInFrame, dashWidth * 2,
aParams.vertical);
// We should continue to draw the last dot even if it is not in the rect.
aRect.width += dashWidth;
break;
}
case NS_STYLE_TEXT_DECORATION_STYLE_WAVY:
autoPopClips.PushClipRect(aRect);
if (lineThickness > 2.0) {
drawOptions.mAntialiasMode = AntialiasMode::SUBPIXEL;
} else {
// Don't use anti-aliasing here. Because looks like lighter color wavy
// line at this case. And probably, users don't think the
// non-anti-aliased wavy line is not pretty.
drawOptions.mAntialiasMode = AntialiasMode::NONE;
}
break;
default:
NS_ERROR("Invalid style value!");
return;
}
// The block-direction position should be set to the middle of the line.
if (aParams.vertical) {
aRect.x += lineThickness / 2;
} else {
aRect.y += lineThickness / 2;
}
switch (aParams.style) {
case NS_STYLE_TEXT_DECORATION_STYLE_SOLID:
case NS_STYLE_TEXT_DECORATION_STYLE_DOTTED:
case NS_STYLE_TEXT_DECORATION_STYLE_DASHED: {
Point p1 = aRect.TopLeft();
Point p2 = aParams.vertical ? aRect.BottomLeft() : aRect.TopRight();
if (textDrawer) {
textDrawer->AppendDecoration(p1, p2, lineThickness, aParams.vertical,
color, aParams.style);
} else {
aDrawTarget.StrokeLine(p1, p2, colorPat, strokeOptions, drawOptions);
}
return;
}
case NS_STYLE_TEXT_DECORATION_STYLE_DOUBLE: {
/**
* We are drawing double line as:
*
* +-------------------------------------------+
* |XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX| ^
* |XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX| | lineThickness
* |XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX| v
* | |
* | |
* |XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX| ^
* |XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX| | lineThickness
* |XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX| v
* +-------------------------------------------+
*/
Point p1a = aRect.TopLeft();
Point p2a = aParams.vertical ? aRect.BottomLeft() : aRect.TopRight();
if (aParams.vertical) {
aRect.width -= lineThickness;
} else {
aRect.height -= lineThickness;
}
Point p1b = aParams.vertical ? aRect.TopRight() : aRect.BottomLeft();
Point p2b = aRect.BottomRight();
if (textDrawer) {
textDrawer->AppendDecoration(p1a, p2a, lineThickness, aParams.vertical,
color,
NS_STYLE_TEXT_DECORATION_STYLE_SOLID);
textDrawer->AppendDecoration(p1b, p2b, lineThickness, aParams.vertical,
color,
NS_STYLE_TEXT_DECORATION_STYLE_SOLID);
} else {
aDrawTarget.StrokeLine(p1a, p2a, colorPat, strokeOptions, drawOptions);
aDrawTarget.StrokeLine(p1b, p2b, colorPat, strokeOptions, drawOptions);
}
return;
}
case NS_STYLE_TEXT_DECORATION_STYLE_WAVY: {
/**
* We are drawing wavy line as:
*
* P: Path, X: Painted pixel
*
* +---------------------------------------+
* XX|X XXXXXX XXXXXX |
* PP|PX XPPPPPPX XPPPPPPX | ^
* XX|XPX XPXXXXXXPX XPXXXXXXPX| |
* | XPX XPX XPX XPX XP|X |adv
* | XPXXXXXXPX XPXXXXXXPX X|PX |
* | XPPPPPPX XPPPPPPX |XPX v
* | XXXXXX XXXXXX | XX
* +---------------------------------------+
* <---><---> ^
* adv flatLengthAtVertex rightMost
*
* 1. Always starts from top-left of the drawing area, however, we need
* to draw the line from outside of the rect. Because the start
* point of the line is not good style if we draw from inside it.
* 2. First, draw horizontal line from outside the rect to top-left of
* the rect;
* 3. Goes down to bottom of the area at 45 degrees.
* 4. Slides to right horizontaly, see |flatLengthAtVertex|.
* 5. Goes up to top of the area at 45 degrees.
* 6. Slides to right horizontaly.
* 7. Repeat from 2 until reached to right-most edge of the area.
*
* In the vertical case, swap horizontal and vertical coordinates and
* directions in the above description.
*/
Float& rectICoord = aParams.vertical ? aRect.y : aRect.x;
Float& rectISize = aParams.vertical ? aRect.height : aRect.width;
const Float rectBSize = aParams.vertical ? aRect.width : aRect.height;
const Float adv = rectBSize - lineThickness;
const Float flatLengthAtVertex =
std::max((lineThickness - 1.0) * 2.0, 1.0);
// Align the start of wavy lines to the nearest ancestor block.
const Float cycleLength = 2 * (adv + flatLengthAtVertex);
aRect = ExpandPaintingRectForDecorationLine(
aFrame, aParams.style, aRect, aParams.icoordInFrame, cycleLength,
aParams.vertical);
if (textDrawer) {
// Undo attempted centering
Float& rectBCoord = aParams.vertical ? aRect.x : aRect.y;
rectBCoord -= lineThickness / 2;
textDrawer->AppendWavyDecoration(aRect, lineThickness, aParams.vertical,
color);
return;
}
// figure out if we can trim whole cycles from the left and right edges
// of the line, to try and avoid creating an unnecessarily long and
// complex path (but don't do this for webrender, )
const Float dirtyRectICoord =
aParams.vertical ? aParams.dirtyRect.y : aParams.dirtyRect.x;
int32_t skipCycles = floor((dirtyRectICoord - rectICoord) / cycleLength);
if (skipCycles > 0) {
rectICoord += skipCycles * cycleLength;
rectISize -= skipCycles * cycleLength;
}
rectICoord += lineThickness / 2.0;
Point pt(aRect.TopLeft());
Float& ptICoord = aParams.vertical ? pt.y : pt.x;
Float& ptBCoord = aParams.vertical ? pt.x : pt.y;
if (aParams.vertical) {
ptBCoord += adv;
}
Float iCoordLimit = ptICoord + rectISize + lineThickness;
const Float dirtyRectIMost = aParams.vertical ? aParams.dirtyRect.YMost()
: aParams.dirtyRect.XMost();
skipCycles = floor((iCoordLimit - dirtyRectIMost) / cycleLength);
if (skipCycles > 0) {
iCoordLimit -= skipCycles * cycleLength;
}
RefPtr<PathBuilder> builder = aDrawTarget.CreatePathBuilder();
RefPtr<Path> path;
ptICoord -= lineThickness;
builder->MoveTo(pt); // 1
ptICoord = rectICoord;
builder->LineTo(pt); // 2
// In vertical mode, to go "down" relative to the text we need to
// decrease the block coordinate, whereas in horizontal we increase
// it. So the sense of this flag is effectively inverted.
bool goDown = !aParams.vertical;
uint32_t iter = 0;
while (ptICoord < iCoordLimit) {
if (++iter > 1000) {
// stroke the current path and start again, to avoid pathological
// behavior in cairo with huge numbers of path segments
path = builder->Finish();
aDrawTarget.Stroke(path, colorPat, strokeOptions, drawOptions);
builder = aDrawTarget.CreatePathBuilder();
builder->MoveTo(pt);
iter = 0;
}
ptICoord += adv;
ptBCoord += goDown ? adv : -adv;
builder->LineTo(pt); // 3 and 5
ptICoord += flatLengthAtVertex;
builder->LineTo(pt); // 4 and 6
goDown = !goDown;
}
path = builder->Finish();
aDrawTarget.Stroke(path, colorPat, strokeOptions, drawOptions);
return;
}
default:
NS_ERROR("Invalid style value!");
}
}
Rect nsCSSRendering::DecorationLineToPath(
const PaintDecorationLineParams& aParams) {
NS_ASSERTION(aParams.style != NS_STYLE_TEXT_DECORATION_STYLE_NONE,
"aStyle is none");
Rect path; // To benefit from RVO, we return this from all return points
Rect rect = ToRect(GetTextDecorationRectInternal(aParams.pt, aParams));
if (rect.IsEmpty() || !rect.Intersects(aParams.dirtyRect)) {
return path;
}
if (aParams.decoration != StyleTextDecorationLine::UNDERLINE &&
aParams.decoration != StyleTextDecorationLine::OVERLINE &&
aParams.decoration != StyleTextDecorationLine::LINE_THROUGH) {
MOZ_ASSERT_UNREACHABLE("Invalid text decoration value");
return path;
}
if (aParams.style != NS_STYLE_TEXT_DECORATION_STYLE_SOLID) {
// For the moment, we support only solid text decorations.
return path;
}
Float lineThickness = std::max(NS_round(aParams.lineSize.height), 1.0);
// The block-direction position should be set to the middle of the line.
if (aParams.vertical) {
rect.x += lineThickness / 2;
path = Rect(rect.TopLeft() - Point(lineThickness / 2, 0.0),
Size(lineThickness, rect.Height()));
} else {
rect.y += lineThickness / 2;
path = Rect(rect.TopLeft() - Point(0.0, lineThickness / 2),
Size(rect.Width(), lineThickness));
}
return path;
}
nsRect nsCSSRendering::GetTextDecorationRect(
nsPresContext* aPresContext, const DecorationRectParams& aParams) {
NS_ASSERTION(aPresContext, "aPresContext is null");
NS_ASSERTION(aParams.style != NS_STYLE_TEXT_DECORATION_STYLE_NONE,
"aStyle is none");
gfxRect rect = GetTextDecorationRectInternal(Point(0, 0), aParams);
// The rect values are already rounded to nearest device pixels.
nsRect r;
r.x = aPresContext->GfxUnitsToAppUnits(rect.X());
r.y = aPresContext->GfxUnitsToAppUnits(rect.Y());
r.width = aPresContext->GfxUnitsToAppUnits(rect.Width());
r.height = aPresContext->GfxUnitsToAppUnits(rect.Height());
return r;
}
gfxRect nsCSSRendering::GetTextDecorationRectInternal(
const Point& aPt, const DecorationRectParams& aParams) {
NS_ASSERTION(aParams.style <= NS_STYLE_TEXT_DECORATION_STYLE_WAVY,
"Invalid aStyle value");
if (aParams.style == NS_STYLE_TEXT_DECORATION_STYLE_NONE)
return gfxRect(0, 0, 0, 0);
bool canLiftUnderline = aParams.descentLimit >= 0.0;
gfxFloat iCoord = aParams.vertical ? aPt.y : aPt.x;
gfxFloat bCoord = aParams.vertical ? aPt.x : aPt.y;
// 'left' and 'right' are relative to the line, so for vertical writing modes
// they will actually become top and bottom of the rendered line.
// Similarly, aLineSize.width and .height are actually length and thickness
// of the line, which runs horizontally or vertically according to aVertical.
const gfxFloat left = floor(iCoord + 0.5),
right = floor(iCoord + aParams.lineSize.width + 0.5);
// We compute |r| as if for a horizontal text run, and then swap vertical
// and horizontal coordinates at the end if vertical was requested.
gfxRect r(left, 0, right - left, 0);
gfxFloat lineThickness = NS_round(aParams.lineSize.height);
lineThickness = std::max(lineThickness, 1.0);
gfxFloat defaultLineThickness = NS_round(aParams.defaultLineThickness);
defaultLineThickness = std::max(defaultLineThickness, 1.0);
gfxFloat ascent = NS_round(aParams.ascent);
gfxFloat descentLimit = floor(aParams.descentLimit);
gfxFloat suggestedMaxRectHeight =
std::max(std::min(ascent, descentLimit), 1.0);
r.height = lineThickness;
if (aParams.style == NS_STYLE_TEXT_DECORATION_STYLE_DOUBLE) {
/**
* We will draw double line as:
*
* +-------------------------------------------+
* |XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX| ^
* |XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX| | lineThickness
* |XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX| v
* | | ^
* | | | gap
* | | v
* |XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX| ^
* |XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX| | lineThickness
* |XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX| v
* +-------------------------------------------+
*/
gfxFloat gap = NS_round(lineThickness / 2.0);
gap = std::max(gap, 1.0);
r.height = lineThickness * 2.0 + gap;
if (canLiftUnderline) {
if (r.Height() > suggestedMaxRectHeight) {
// Don't shrink the line height, because the thickness has some meaning.
// We can just shrink the gap at this time.
r.height = std::max(suggestedMaxRectHeight, lineThickness * 2.0 + 1.0);
}
}
} else if (aParams.style == NS_STYLE_TEXT_DECORATION_STYLE_WAVY) {
/**
* We will draw wavy line as:
*
* +-------------------------------------------+
* |XXXXX XXXXXX XXXXXX | ^
* |XXXXXX XXXXXXXX XXXXXXXX | | lineThickness
* |XXXXXXX XXXXXXXXXX XXXXXXXXXX| v
* | XXX XXX XXX XXX XX|
* | XXXXXXXXXX XXXXXXXXXX X|
* | XXXXXXXX XXXXXXXX |
* | XXXXXX XXXXXX |
* +-------------------------------------------+
*/
r.height = lineThickness > 2.0 ? lineThickness * 4.0 : lineThickness * 3.0;
if (canLiftUnderline) {
if (r.Height() > suggestedMaxRectHeight) {
// Don't shrink the line height even if there is not enough space,
// because the thickness has some meaning. E.g., the 1px wavy line and
// 2px wavy line can be used for different meaning in IME selections
// at same time.
r.height = std::max(suggestedMaxRectHeight, lineThickness * 2.0);
}
}
}
gfxFloat baseline = floor(bCoord + aParams.ascent + 0.5);
// Calculate adjusted offset based on writing-mode/orientation and thickness
// of decoration line. The input value aParams.offset is the nominal position
// (offset from baseline) where we would draw a single, infinitely-thin line;
// but for a wavy or double line, we'll need to move the bounding rect of the
// decoration outwards from the baseline so that an underline remains below
// the glyphs, and an overline above them, despite the increased block-dir
// extent of the decoration.
//
// So adjustments by r.Height() are used to make the wider line styles (wavy
// and double) "grow" in the appropriate direction compared to the basic
// single line.
//
// Note that at this point, the decoration rect is being calculated in line-
// relative coordinates, where 'x' is line-rightwards, and 'y' is line-
// upwards. We'll swap them to be physical coords at the end.
gfxFloat offset = 0.0;
if (aParams.decoration == StyleTextDecorationLine::UNDERLINE) {
offset = aParams.offset;
if (canLiftUnderline) {
if (descentLimit < -offset + r.Height()) {
// If we can ignore the offset and the decoration line is overflowing,
// we should align the bottom edge of the decoration line rect if it's
// possible. Otherwise, we should lift up the top edge of the rect as
// far as possible.
gfxFloat offsetBottomAligned = -descentLimit + r.Height();
gfxFloat offsetTopAligned = 0.0;
offset = std::min(offsetBottomAligned, offsetTopAligned);
}
}
} else if (aParams.decoration == StyleTextDecorationLine::OVERLINE) {
// For overline, we adjust the offset by defaultlineThickness (the default
// thickness of a single decoration line) because empirically it looks
// better to draw the overline just inside rather than outside the font's
// ascent, which is what nsTextFrame passes as aParams.offset (as fonts
// don't provide an explicit overline-offset).
offset = aParams.offset - defaultLineThickness + r.Height();
} else if (aParams.decoration == StyleTextDecorationLine::LINE_THROUGH) {
// To maintain a consistent mid-point for line-through decorations,
// we adjust the offset by half of the decoration rect's height.
gfxFloat extra = floor(r.Height() / 2.0 + 0.5);
extra = std::max(extra, lineThickness);
// computes offset for when user specifies a decoration width since
// aParams.offset is derived from the font metric's line height
gfxFloat decorationThicknessOffset =
(lineThickness - defaultLineThickness) / 2.0;
offset = aParams.offset - lineThickness + extra + decorationThicknessOffset;
} else {
MOZ_ASSERT_UNREACHABLE("Invalid text decoration value");
}
// Convert line-relative coordinate system (x = line-right, y = line-up)
// to physical coords, and move the decoration rect to the calculated
// offset from baseline.
if (aParams.vertical) {
std::swap(r.x, r.y);
std::swap(r.width, r.height);
// line-upwards in vertical mode = physical-right, so we /add/ offset
// to baseline. Except in sideways-lr mode, where line-upwards will be
// physical leftwards.
if (aParams.sidewaysLeft) {
r.x = baseline - floor(offset + 0.5);
} else {
r.x = baseline + floor(offset - r.Width() + 0.5);
}
} else {
// line-upwards in horizontal mode = physical-up, but our physical coord
// system works downwards, so we /subtract/ offset from baseline.
r.y = baseline - floor(offset + 0.5);
}
return r;
}
#define MAX_BLUR_RADIUS 300
#define MAX_SPREAD_RADIUS 50
static inline gfxPoint ComputeBlurStdDev(nscoord aBlurRadius,
int32_t aAppUnitsPerDevPixel,
gfxFloat aScaleX, gfxFloat aScaleY) {
// http://dev.w3.org/csswg/css3-background/#box-shadow says that the
// standard deviation of the blur should be half the given blur value.
gfxFloat blurStdDev = gfxFloat(aBlurRadius) / gfxFloat(aAppUnitsPerDevPixel);
return gfxPoint(
std::min((blurStdDev * aScaleX), gfxFloat(MAX_BLUR_RADIUS)) / 2.0,
std::min((blurStdDev * aScaleY), gfxFloat(MAX_BLUR_RADIUS)) / 2.0);
}
static inline IntSize ComputeBlurRadius(nscoord aBlurRadius,
int32_t aAppUnitsPerDevPixel,
gfxFloat aScaleX = 1.0,
gfxFloat aScaleY = 1.0) {
gfxPoint scaledBlurStdDev =
ComputeBlurStdDev(aBlurRadius, aAppUnitsPerDevPixel, aScaleX, aScaleY);
return gfxAlphaBoxBlur::CalculateBlurRadius(scaledBlurStdDev);
}
// -----
// nsContextBoxBlur
// -----
gfxContext* nsContextBoxBlur::Init(const nsRect& aRect, nscoord aSpreadRadius,
nscoord aBlurRadius,
int32_t aAppUnitsPerDevPixel,
gfxContext* aDestinationCtx,
const nsRect& aDirtyRect,
const gfxRect* aSkipRect, uint32_t aFlags) {
if (aRect.IsEmpty()) {
mContext = nullptr;
return nullptr;
}
IntSize blurRadius;
IntSize spreadRadius;
GetBlurAndSpreadRadius(aDestinationCtx->GetDrawTarget(), aAppUnitsPerDevPixel,
aBlurRadius, aSpreadRadius, blurRadius, spreadRadius);
mDestinationCtx = aDestinationCtx;
// If not blurring, draw directly onto the destination device
if (blurRadius.width <= 0 && blurRadius.height <= 0 &&
spreadRadius.width <= 0 && spreadRadius.height <= 0 &&
!(aFlags & FORCE_MASK)) {
mContext = aDestinationCtx;
return mContext;
}
// Convert from app units to device pixels
gfxRect rect = nsLayoutUtils::RectToGfxRect(aRect, aAppUnitsPerDevPixel);
gfxRect dirtyRect =
nsLayoutUtils::RectToGfxRect(aDirtyRect, aAppUnitsPerDevPixel);
dirtyRect.RoundOut();
gfxMatrix transform = aDestinationCtx->CurrentMatrixDouble();
rect = transform.TransformBounds(rect);
mPreTransformed = !transform.IsIdentity();
// Create the temporary surface for blurring
dirtyRect = transform.TransformBounds(dirtyRect);
bool useHardwareAccel = !(aFlags & DISABLE_HARDWARE_ACCELERATION_BLUR);
if (aSkipRect) {
gfxRect skipRect = transform.TransformBounds(*aSkipRect);
mContext =
mAlphaBoxBlur.Init(aDestinationCtx, rect, spreadRadius, blurRadius,
&dirtyRect, &skipRect, useHardwareAccel);
} else {
mContext =
mAlphaBoxBlur.Init(aDestinationCtx, rect, spreadRadius, blurRadius,
&dirtyRect, nullptr, useHardwareAccel);
}
if (mContext) {
// we don't need to blur if skipRect is equal to rect
// and mContext will be nullptr
mContext->Multiply(transform);
}
return mContext;
}
void nsContextBoxBlur::DoPaint() {
if (mContext == mDestinationCtx) {
return;
}
gfxContextMatrixAutoSaveRestore saveMatrix(mDestinationCtx);
if (mPreTransformed) {
mDestinationCtx->SetMatrix(Matrix());
}
mAlphaBoxBlur.Paint(mDestinationCtx);
}
gfxContext* nsContextBoxBlur::GetContext() { return mContext; }
/* static */
nsMargin nsContextBoxBlur::GetBlurRadiusMargin(nscoord aBlurRadius,
int32_t aAppUnitsPerDevPixel) {
IntSize blurRadius = ComputeBlurRadius(aBlurRadius, aAppUnitsPerDevPixel);
nsMargin result;
result.top = result.bottom = blurRadius.height * aAppUnitsPerDevPixel;
result.left = result.right = blurRadius.width * aAppUnitsPerDevPixel;
return result;
}
/* static */
void nsContextBoxBlur::BlurRectangle(
gfxContext* aDestinationCtx, const nsRect& aRect,
int32_t aAppUnitsPerDevPixel, RectCornerRadii* aCornerRadii,
nscoord aBlurRadius, const Color& aShadowColor, const nsRect& aDirtyRect,
const gfxRect& aSkipRect) {
DrawTarget& aDestDrawTarget = *aDestinationCtx->GetDrawTarget();
if (aRect.IsEmpty()) {
return;
}
Rect shadowGfxRect = NSRectToRect(aRect, aAppUnitsPerDevPixel);
if (aBlurRadius <= 0) {
ColorPattern color(ToDeviceColor(aShadowColor));
if (aCornerRadii) {
RefPtr<Path> roundedRect =
MakePathForRoundedRect(aDestDrawTarget, shadowGfxRect, *aCornerRadii);
aDestDrawTarget.Fill(roundedRect, color);
} else {
aDestDrawTarget.FillRect(shadowGfxRect, color);
}
return;
}
gfxFloat scaleX = 1;
gfxFloat scaleY = 1;
// Do blurs in device space when possible.
// Chrome/Skia always does the blurs in device space
// and will sometimes get incorrect results (e.g. rotated blurs)
gfxMatrix transform = aDestinationCtx->CurrentMatrixDouble();
// XXX: we could probably handle negative scales but for now it's easier just
// to fallback
if (!transform.HasNonAxisAlignedTransform() && transform._11 > 0.0 &&
transform._22 > 0.0) {
scaleX = transform._11;
scaleY = transform._22;
aDestinationCtx->SetMatrix(Matrix());
} else {
transform = gfxMatrix();
}
gfxPoint blurStdDev =
ComputeBlurStdDev(aBlurRadius, aAppUnitsPerDevPixel, scaleX, scaleY);
gfxRect dirtyRect =
nsLayoutUtils::RectToGfxRect(aDirtyRect, aAppUnitsPerDevPixel);
dirtyRect.RoundOut();
gfxRect shadowThebesRect =
transform.TransformBounds(ThebesRect(shadowGfxRect));
dirtyRect = transform.TransformBounds(dirtyRect);
gfxRect skipRect = transform.TransformBounds(aSkipRect);
if (aCornerRadii) {
aCornerRadii->Scale(scaleX, scaleY);
}
gfxAlphaBoxBlur::BlurRectangle(aDestinationCtx, shadowThebesRect,
aCornerRadii, blurStdDev, aShadowColor,
dirtyRect, skipRect);
}
/* static */
void nsContextBoxBlur::GetBlurAndSpreadRadius(
DrawTarget* aDestDrawTarget, int32_t aAppUnitsPerDevPixel,
nscoord aBlurRadius, nscoord aSpreadRadius, IntSize& aOutBlurRadius,
IntSize& aOutSpreadRadius, bool aConstrainSpreadRadius) {
// Do blurs in device space when possible.
// Chrome/Skia always does the blurs in device space
// and will sometimes get incorrect results (e.g. rotated blurs)
Matrix transform = aDestDrawTarget->GetTransform();
// XXX: we could probably handle negative scales but for now it's easier just
// to fallback
gfxFloat scaleX, scaleY;
if (transform.HasNonAxisAlignedTransform() || transform._11 <= 0.0 ||
transform._22 <= 0.0) {
scaleX = 1;
scaleY = 1;
} else {
scaleX = transform._11;
scaleY = transform._22;
}
// compute a large or smaller blur radius
aOutBlurRadius =
ComputeBlurRadius(aBlurRadius, aAppUnitsPerDevPixel, scaleX, scaleY);
aOutSpreadRadius =
IntSize(int32_t(aSpreadRadius * scaleX / aAppUnitsPerDevPixel),
int32_t(aSpreadRadius * scaleY / aAppUnitsPerDevPixel));
if (aConstrainSpreadRadius) {
aOutSpreadRadius.width =
std::min(aOutSpreadRadius.width, int32_t(MAX_SPREAD_RADIUS));
aOutSpreadRadius.height =
std::min(aOutSpreadRadius.height, int32_t(MAX_SPREAD_RADIUS));
}
}
/* static */
bool nsContextBoxBlur::InsetBoxBlur(
gfxContext* aDestinationCtx, Rect aDestinationRect, Rect aShadowClipRect,
Color& aShadowColor, nscoord aBlurRadiusAppUnits,
nscoord aSpreadDistanceAppUnits, int32_t aAppUnitsPerDevPixel,
bool aHasBorderRadius, RectCornerRadii& aInnerClipRectRadii, Rect aSkipRect,
Point aShadowOffset) {
if (aDestinationRect.IsEmpty()) {
mContext = nullptr;
return false;
}
gfxContextAutoSaveRestore autoRestore(aDestinationCtx);
IntSize blurRadius;
IntSize spreadRadius;
// Convert the blur and spread radius to device pixels
bool constrainSpreadRadius = false;
GetBlurAndSpreadRadius(aDestinationCtx->GetDrawTarget(), aAppUnitsPerDevPixel,
aBlurRadiusAppUnits, aSpreadDistanceAppUnits,
blurRadius, spreadRadius, constrainSpreadRadius);
// The blur and spread radius are scaled already, so scale all
// input data to the blur. This way, we don't have to scale the min
// inset blur to the invert of the dest context, then rescale it back
// when we draw to the destination surface.
gfx::Size scale = aDestinationCtx->CurrentMatrix().ScaleFactors(true);
Matrix transform = aDestinationCtx->CurrentMatrix();
// XXX: we could probably handle negative scales but for now it's easier just
// to fallback
if (!transform.HasNonAxisAlignedTransform() && transform._11 > 0.0 &&
transform._22 > 0.0) {
// If we don't have a rotation, we're pre-transforming all the rects.
aDestinationCtx->SetMatrix(Matrix());
} else {
// Don't touch anything, we have a rotation.
transform = Matrix();
}
Rect transformedDestRect = transform.TransformBounds(aDestinationRect);
Rect transformedShadowClipRect = transform.TransformBounds(aShadowClipRect);
Rect transformedSkipRect = transform.TransformBounds(aSkipRect);
transformedDestRect.Round();
transformedShadowClipRect.Round();
transformedSkipRect.RoundIn();
for (size_t i = 0; i < 4; i++) {
aInnerClipRectRadii[i].width =
std::floor(scale.width * aInnerClipRectRadii[i].width);
aInnerClipRectRadii[i].height =
std::floor(scale.height * aInnerClipRectRadii[i].height);
}
mAlphaBoxBlur.BlurInsetBox(aDestinationCtx, transformedDestRect,
transformedShadowClipRect, blurRadius,
aShadowColor,
aHasBorderRadius ? &aInnerClipRectRadii : nullptr,
transformedSkipRect, aShadowOffset);
return true;
}