зеркало из https://github.com/mozilla/gecko-dev.git
4897 строки
188 KiB
C++
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, ¤tLayerClipState);
|
||
}
|
||
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 ScrolledRect’s 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;
|
||
}
|