mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-25 05:41:12 +00:00
971b645fe3
Differential Revision: https://phabricator.services.mozilla.com/D87865
1379 lines
53 KiB
C++
1379 lines
53 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/. */
|
|
|
|
// Main header first:
|
|
#include "SVGIntegrationUtils.h"
|
|
|
|
// Keep others in (case-insensitive) order:
|
|
#include "gfxDrawable.h"
|
|
|
|
#include "Layers.h"
|
|
#include "nsCSSAnonBoxes.h"
|
|
#include "nsCSSRendering.h"
|
|
#include "nsDisplayList.h"
|
|
#include "nsLayoutUtils.h"
|
|
#include "gfxContext.h"
|
|
#include "SVGFilterPaintCallback.h"
|
|
#include "SVGPaintServerFrame.h"
|
|
#include "FrameLayerBuilder.h"
|
|
#include "BasicLayers.h"
|
|
#include "mozilla/gfx/Point.h"
|
|
#include "mozilla/gfx/gfxVars.h"
|
|
#include "mozilla/CSSClipPathInstance.h"
|
|
#include "mozilla/FilterInstance.h"
|
|
#include "mozilla/StaticPrefs_layers.h"
|
|
#include "mozilla/SVGClipPathFrame.h"
|
|
#include "mozilla/SVGObserverUtils.h"
|
|
#include "mozilla/SVGMaskFrame.h"
|
|
#include "mozilla/SVGUtils.h"
|
|
#include "mozilla/Unused.h"
|
|
#include "mozilla/dom/SVGElement.h"
|
|
|
|
using namespace mozilla::dom;
|
|
using namespace mozilla::layers;
|
|
using namespace mozilla::gfx;
|
|
using namespace mozilla::image;
|
|
|
|
namespace mozilla {
|
|
|
|
/**
|
|
* This class is used to get the pre-effects ink overflow rect of a frame,
|
|
* or, in the case of a frame with continuations, to collect the union of the
|
|
* pre-effects ink overflow rects of all the continuations. The result is
|
|
* relative to the origin (top left corner of the border box) of the frame, or,
|
|
* if the frame has continuations, the origin of the _first_ continuation.
|
|
*/
|
|
class PreEffectsInkOverflowCollector : public nsLayoutUtils::BoxCallback {
|
|
public:
|
|
/**
|
|
* If the pre-effects ink overflow rect of the frame being examined
|
|
* happens to be known, it can be passed in as aCurrentFrame and its
|
|
* pre-effects ink overflow rect can be passed in as
|
|
* aCurrentFrameOverflowArea. This is just an optimization to save a
|
|
* frame property lookup - these arguments are optional.
|
|
*/
|
|
PreEffectsInkOverflowCollector(nsIFrame* aFirstContinuation,
|
|
nsIFrame* aCurrentFrame,
|
|
const nsRect& aCurrentFrameOverflowArea,
|
|
bool aInReflow)
|
|
: mFirstContinuation(aFirstContinuation),
|
|
mCurrentFrame(aCurrentFrame),
|
|
mCurrentFrameOverflowArea(aCurrentFrameOverflowArea),
|
|
mInReflow(aInReflow) {
|
|
NS_ASSERTION(!mFirstContinuation->GetPrevContinuation(),
|
|
"We want the first continuation here");
|
|
}
|
|
|
|
virtual void AddBox(nsIFrame* aFrame) override {
|
|
nsRect overflow = (aFrame == mCurrentFrame)
|
|
? mCurrentFrameOverflowArea
|
|
: PreEffectsInkOverflowRect(aFrame, mInReflow);
|
|
mResult.UnionRect(mResult,
|
|
overflow + aFrame->GetOffsetTo(mFirstContinuation));
|
|
}
|
|
|
|
nsRect GetResult() const { return mResult; }
|
|
|
|
private:
|
|
static nsRect PreEffectsInkOverflowRect(nsIFrame* aFrame, bool aInReflow) {
|
|
nsRect* r = aFrame->GetProperty(nsIFrame::PreEffectsBBoxProperty());
|
|
if (r) {
|
|
return *r;
|
|
}
|
|
|
|
#ifdef DEBUG
|
|
// Having PreTransformOverflowAreasProperty cached means
|
|
// InkOverflowRect() will return post-effect rect, which is not what
|
|
// we want. This function intentional reports pre-effect rect. But it does
|
|
// not matter if there is no SVG effect on this frame, since no effect
|
|
// means post-effect rect matches pre-effect rect.
|
|
//
|
|
// This function may be called during reflow or painting. We should only
|
|
// do this check in painting process since the PreEffectsBBoxProperty of
|
|
// continuations are not set correctly while reflowing.
|
|
if (SVGIntegrationUtils::UsingOverflowAffectingEffects(aFrame) &&
|
|
!aInReflow) {
|
|
OverflowAreas* preTransformOverflows =
|
|
aFrame->GetProperty(nsIFrame::PreTransformOverflowAreasProperty());
|
|
|
|
MOZ_ASSERT(!preTransformOverflows,
|
|
"InkOverflowRect() won't return the pre-effects rect!");
|
|
}
|
|
#endif
|
|
return aFrame->InkOverflowRectRelativeToSelf();
|
|
}
|
|
|
|
nsIFrame* mFirstContinuation;
|
|
nsIFrame* mCurrentFrame;
|
|
const nsRect& mCurrentFrameOverflowArea;
|
|
nsRect mResult;
|
|
bool mInReflow;
|
|
};
|
|
|
|
/**
|
|
* Gets the union of the pre-effects ink overflow rects of all of a frame's
|
|
* continuations, in "user space".
|
|
*/
|
|
static nsRect GetPreEffectsInkOverflowUnion(
|
|
nsIFrame* aFirstContinuation, nsIFrame* aCurrentFrame,
|
|
const nsRect& aCurrentFramePreEffectsOverflow,
|
|
const nsPoint& aFirstContinuationToUserSpace, bool aInReflow) {
|
|
NS_ASSERTION(!aFirstContinuation->GetPrevContinuation(),
|
|
"Need first continuation here");
|
|
PreEffectsInkOverflowCollector collector(aFirstContinuation, aCurrentFrame,
|
|
aCurrentFramePreEffectsOverflow,
|
|
aInReflow);
|
|
// Compute union of all overflow areas relative to aFirstContinuation:
|
|
nsLayoutUtils::GetAllInFlowBoxes(aFirstContinuation, &collector);
|
|
// Return the result in user space:
|
|
return collector.GetResult() + aFirstContinuationToUserSpace;
|
|
}
|
|
|
|
/**
|
|
* Gets the pre-effects ink overflow rect of aCurrentFrame in "user space".
|
|
*/
|
|
static nsRect GetPreEffectsInkOverflow(
|
|
nsIFrame* aFirstContinuation, nsIFrame* aCurrentFrame,
|
|
const nsPoint& aFirstContinuationToUserSpace) {
|
|
NS_ASSERTION(!aFirstContinuation->GetPrevContinuation(),
|
|
"Need first continuation here");
|
|
PreEffectsInkOverflowCollector collector(aFirstContinuation, nullptr,
|
|
nsRect(), false);
|
|
// Compute overflow areas of current frame relative to aFirstContinuation:
|
|
nsLayoutUtils::AddBoxesForFrame(aCurrentFrame, &collector);
|
|
// Return the result in user space:
|
|
return collector.GetResult() + aFirstContinuationToUserSpace;
|
|
}
|
|
|
|
bool SVGIntegrationUtils::UsingOverflowAffectingEffects(
|
|
const nsIFrame* aFrame) {
|
|
// Currently overflow don't take account of SVG or other non-absolute
|
|
// positioned clipping, or masking.
|
|
return aFrame->StyleEffects()->HasFilters();
|
|
}
|
|
|
|
bool SVGIntegrationUtils::UsingEffectsForFrame(const nsIFrame* aFrame) {
|
|
// Even when SVG display lists are disabled, returning true for SVG frames
|
|
// does not adversely affect any of our callers. Therefore we don't bother
|
|
// checking the SDL prefs here, since we don't know if we're being called for
|
|
// painting or hit-testing anyway.
|
|
const nsStyleSVGReset* style = aFrame->StyleSVGReset();
|
|
const nsStyleEffects* effects = aFrame->StyleEffects();
|
|
// TODO(cbrewster): remove backdrop-filter from this list once it is supported
|
|
// in preserve-3d cases.
|
|
return effects->HasFilters() || effects->HasBackdropFilters() ||
|
|
style->HasClipPath() || style->HasMask();
|
|
}
|
|
|
|
bool SVGIntegrationUtils::UsingMaskOrClipPathForFrame(const nsIFrame* aFrame) {
|
|
const nsStyleSVGReset* style = aFrame->StyleSVGReset();
|
|
return style->HasClipPath() || style->HasMask();
|
|
}
|
|
|
|
bool SVGIntegrationUtils::UsingSimpleClipPathForFrame(const nsIFrame* aFrame) {
|
|
const nsStyleSVGReset* style = aFrame->StyleSVGReset();
|
|
if (!style->HasClipPath() || style->HasMask()) {
|
|
return false;
|
|
}
|
|
|
|
const auto& clipPath = style->mClipPath;
|
|
if (!clipPath.IsShape()) {
|
|
return false;
|
|
}
|
|
|
|
return !clipPath.AsShape()._0->IsPolygon();
|
|
}
|
|
|
|
nsPoint SVGIntegrationUtils::GetOffsetToBoundingBox(nsIFrame* aFrame) {
|
|
if (aFrame->HasAnyStateBits(NS_FRAME_SVG_LAYOUT)) {
|
|
// Do NOT call GetAllInFlowRectsUnion for SVG - it will get the
|
|
// covered region relative to the SVGOuterSVGFrame, which is absolutely
|
|
// not what we want. SVG frames are always in user space, so they have
|
|
// no offset adjustment to make.
|
|
return nsPoint();
|
|
}
|
|
|
|
// The GetAllInFlowRectsUnion() call gets the union of the frame border-box
|
|
// rects over all continuations, relative to the origin (top-left of the
|
|
// border box) of its second argument (here, aFrame, the first continuation).
|
|
return -nsLayoutUtils::GetAllInFlowRectsUnion(aFrame, aFrame).TopLeft();
|
|
}
|
|
|
|
/* static */
|
|
nsSize SVGIntegrationUtils::GetContinuationUnionSize(nsIFrame* aNonSVGFrame) {
|
|
NS_ASSERTION(!aNonSVGFrame->IsFrameOfType(nsIFrame::eSVG),
|
|
"SVG frames should not get here");
|
|
nsIFrame* firstFrame =
|
|
nsLayoutUtils::FirstContinuationOrIBSplitSibling(aNonSVGFrame);
|
|
return nsLayoutUtils::GetAllInFlowRectsUnion(firstFrame, firstFrame).Size();
|
|
}
|
|
|
|
/* static */ gfx::Size SVGIntegrationUtils::GetSVGCoordContextForNonSVGFrame(
|
|
nsIFrame* aNonSVGFrame) {
|
|
NS_ASSERTION(!aNonSVGFrame->IsFrameOfType(nsIFrame::eSVG),
|
|
"SVG frames should not get here");
|
|
nsIFrame* firstFrame =
|
|
nsLayoutUtils::FirstContinuationOrIBSplitSibling(aNonSVGFrame);
|
|
nsRect r = nsLayoutUtils::GetAllInFlowRectsUnion(firstFrame, firstFrame);
|
|
return gfx::Size(nsPresContext::AppUnitsToFloatCSSPixels(r.width),
|
|
nsPresContext::AppUnitsToFloatCSSPixels(r.height));
|
|
}
|
|
|
|
gfxRect SVGIntegrationUtils::GetSVGBBoxForNonSVGFrame(
|
|
nsIFrame* aNonSVGFrame, bool aUnionContinuations) {
|
|
// Except for SVGOuterSVGFrame, we shouldn't be getting here with SVG
|
|
// frames at all. This function is for elements that are laid out using the
|
|
// CSS box model rules.
|
|
NS_ASSERTION(!aNonSVGFrame->HasAnyStateBits(NS_FRAME_SVG_LAYOUT),
|
|
"Frames with SVG layout should not get here");
|
|
MOZ_ASSERT(!aNonSVGFrame->IsFrameOfType(nsIFrame::eSVG) ||
|
|
aNonSVGFrame->IsSVGOuterSVGFrame());
|
|
|
|
nsIFrame* firstFrame =
|
|
nsLayoutUtils::FirstContinuationOrIBSplitSibling(aNonSVGFrame);
|
|
// 'r' is in "user space":
|
|
nsRect r = (aUnionContinuations)
|
|
? GetPreEffectsInkOverflowUnion(
|
|
firstFrame, nullptr, nsRect(),
|
|
GetOffsetToBoundingBox(firstFrame), false)
|
|
: GetPreEffectsInkOverflow(firstFrame, aNonSVGFrame,
|
|
GetOffsetToBoundingBox(firstFrame));
|
|
|
|
return nsLayoutUtils::RectToGfxRect(r, AppUnitsPerCSSPixel());
|
|
}
|
|
|
|
// XXX Since we're called during reflow, this method is broken for frames with
|
|
// continuations. When we're called for a frame with continuations, we're
|
|
// called for each continuation in turn as it's reflowed. However, it isn't
|
|
// until the last continuation is reflowed that this method's
|
|
// GetOffsetToBoundingBox() and GetPreEffectsInkOverflowUnion() calls will
|
|
// obtain valid border boxes for all the continuations. As a result, we'll
|
|
// end up returning bogus post-filter ink overflow rects for all the prior
|
|
// continuations. Unfortunately, by the time the last continuation is
|
|
// reflowed, it's too late to go back and set and propagate the overflow
|
|
// rects on the previous continuations.
|
|
//
|
|
// The reason that we need to pass an override bbox to
|
|
// GetPreEffectsInkOverflowUnion rather than just letting it call into our
|
|
// GetSVGBBoxForNonSVGFrame method is because we get called by
|
|
// ComputeEffectsRect when it has been called with
|
|
// aStoreRectProperties set to false. In this case the pre-effects visual
|
|
// overflow rect that it has been passed may be different to that stored on
|
|
// aFrame, resulting in a different bbox.
|
|
//
|
|
// XXXjwatt The pre-effects ink overflow rect passed to
|
|
// ComputeEffectsRect won't include continuation overflows, so
|
|
// for frames with continuation the following filter analysis will likely end
|
|
// up being carried out with a bbox created as if the frame didn't have
|
|
// continuations.
|
|
//
|
|
// XXXjwatt Using aPreEffectsOverflowRect to create the bbox isn't really right
|
|
// for SVG frames, since for SVG frames the SVG spec defines the bbox to be
|
|
// something quite different to the pre-effects ink overflow rect. However,
|
|
// we're essentially calculating an invalidation area here, and using the
|
|
// pre-effects overflow rect will actually overestimate that area which, while
|
|
// being a bit wasteful, isn't otherwise a problem.
|
|
//
|
|
nsRect SVGIntegrationUtils::ComputePostEffectsInkOverflowRect(
|
|
nsIFrame* aFrame, const nsRect& aPreEffectsOverflowRect) {
|
|
MOZ_ASSERT(!aFrame->HasAnyStateBits(NS_FRAME_SVG_LAYOUT),
|
|
"Don't call this on SVG child frames");
|
|
|
|
MOZ_ASSERT(aFrame->StyleEffects()->HasFilters(),
|
|
"We should only be called if the frame is filtered, since filters "
|
|
"are the only effect that affects overflow.");
|
|
|
|
nsIFrame* firstFrame =
|
|
nsLayoutUtils::FirstContinuationOrIBSplitSibling(aFrame);
|
|
// Note: we do not return here for eHasNoRefs since we must still handle any
|
|
// CSS filter functions.
|
|
// TODO: We currently pass nullptr instead of an nsTArray* here, but we
|
|
// actually should get the filter frames and then pass them into
|
|
// GetPostFilterBounds below! See bug 1494263.
|
|
// TODO: we should really return an empty rect for eHasRefsSomeInvalid since
|
|
// in that case we disable painting of the element.
|
|
if (SVGObserverUtils::GetAndObserveFilters(firstFrame, nullptr) ==
|
|
SVGObserverUtils::eHasRefsSomeInvalid) {
|
|
return aPreEffectsOverflowRect;
|
|
}
|
|
|
|
// Create an override bbox - see comment above:
|
|
nsPoint firstFrameToBoundingBox = GetOffsetToBoundingBox(firstFrame);
|
|
// overrideBBox is in "user space", in _CSS_ pixels:
|
|
// XXX Why are we rounding out to pixel boundaries? We don't do that in
|
|
// GetSVGBBoxForNonSVGFrame, and it doesn't appear to be necessary.
|
|
gfxRect overrideBBox = nsLayoutUtils::RectToGfxRect(
|
|
GetPreEffectsInkOverflowUnion(firstFrame, aFrame, aPreEffectsOverflowRect,
|
|
firstFrameToBoundingBox, true),
|
|
AppUnitsPerCSSPixel());
|
|
overrideBBox.RoundOut();
|
|
|
|
nsRect overflowRect =
|
|
FilterInstance::GetPostFilterBounds(firstFrame, &overrideBBox);
|
|
|
|
// Return overflowRect relative to aFrame, rather than "user space":
|
|
return overflowRect -
|
|
(aFrame->GetOffsetTo(firstFrame) + firstFrameToBoundingBox);
|
|
}
|
|
|
|
nsIntRegion SVGIntegrationUtils::AdjustInvalidAreaForSVGEffects(
|
|
nsIFrame* aFrame, const nsPoint& aToReferenceFrame,
|
|
const nsIntRegion& aInvalidRegion) {
|
|
if (aInvalidRegion.IsEmpty()) {
|
|
return nsIntRect();
|
|
}
|
|
|
|
nsIFrame* firstFrame =
|
|
nsLayoutUtils::FirstContinuationOrIBSplitSibling(aFrame);
|
|
|
|
// If we have any filters to observe then we should have started doing that
|
|
// during reflow/ComputeFrameEffectsRect, so we use GetFiltersIfObserving
|
|
// here to avoid needless work (or masking bugs by setting up observers at
|
|
// the wrong time).
|
|
if (!aFrame->StyleEffects()->HasFilters() ||
|
|
SVGObserverUtils::GetFiltersIfObserving(firstFrame, nullptr) ==
|
|
SVGObserverUtils::eHasRefsSomeInvalid) {
|
|
return aInvalidRegion;
|
|
}
|
|
|
|
int32_t appUnitsPerDevPixel = aFrame->PresContext()->AppUnitsPerDevPixel();
|
|
|
|
// Convert aInvalidRegion into bounding box frame space in app units:
|
|
nsPoint toBoundingBox =
|
|
aFrame->GetOffsetTo(firstFrame) + GetOffsetToBoundingBox(firstFrame);
|
|
// The initial rect was relative to the reference frame, so we need to
|
|
// remove that offset to get a rect relative to the current frame.
|
|
toBoundingBox -= aToReferenceFrame;
|
|
nsRegion preEffectsRegion =
|
|
aInvalidRegion.ToAppUnits(appUnitsPerDevPixel).MovedBy(toBoundingBox);
|
|
|
|
// Adjust the dirty area for effects, and shift it back to being relative to
|
|
// the reference frame.
|
|
nsRegion result =
|
|
FilterInstance::GetPostFilterDirtyArea(firstFrame, preEffectsRegion)
|
|
.MovedBy(-toBoundingBox);
|
|
// Return the result, in pixels relative to the reference frame.
|
|
return result.ToOutsidePixels(appUnitsPerDevPixel);
|
|
}
|
|
|
|
nsRect SVGIntegrationUtils::GetRequiredSourceForInvalidArea(
|
|
nsIFrame* aFrame, const nsRect& aDirtyRect) {
|
|
nsIFrame* firstFrame =
|
|
nsLayoutUtils::FirstContinuationOrIBSplitSibling(aFrame);
|
|
|
|
// If we have any filters to observe then we should have started doing that
|
|
// during reflow/ComputeFrameEffectsRect, so we use GetFiltersIfObserving
|
|
// here to avoid needless work (or masking bugs by setting up observers at
|
|
// the wrong time).
|
|
if (!aFrame->StyleEffects()->HasFilters() ||
|
|
SVGObserverUtils::GetFiltersIfObserving(firstFrame, nullptr) ==
|
|
SVGObserverUtils::eHasRefsSomeInvalid) {
|
|
return aDirtyRect;
|
|
}
|
|
|
|
// Convert aDirtyRect into "user space" in app units:
|
|
nsPoint toUserSpace =
|
|
aFrame->GetOffsetTo(firstFrame) + GetOffsetToBoundingBox(firstFrame);
|
|
nsRect postEffectsRect = aDirtyRect + toUserSpace;
|
|
|
|
// Return ther result, relative to aFrame, not in user space:
|
|
return FilterInstance::GetPreFilterNeededArea(firstFrame, postEffectsRect)
|
|
.GetBounds() -
|
|
toUserSpace;
|
|
}
|
|
|
|
bool SVGIntegrationUtils::HitTestFrameForEffects(nsIFrame* aFrame,
|
|
const nsPoint& aPt) {
|
|
nsIFrame* firstFrame =
|
|
nsLayoutUtils::FirstContinuationOrIBSplitSibling(aFrame);
|
|
// Convert aPt to user space:
|
|
nsPoint toUserSpace;
|
|
if (aFrame->HasAnyStateBits(NS_FRAME_SVG_LAYOUT)) {
|
|
// XXXmstange Isn't this wrong for svg:use and innerSVG frames?
|
|
toUserSpace = aFrame->GetPosition();
|
|
} else {
|
|
toUserSpace =
|
|
aFrame->GetOffsetTo(firstFrame) + GetOffsetToBoundingBox(firstFrame);
|
|
}
|
|
nsPoint pt = aPt + toUserSpace;
|
|
gfxPoint userSpacePt = gfxPoint(pt.x, pt.y) / AppUnitsPerCSSPixel();
|
|
return SVGUtils::HitTestClip(firstFrame, userSpacePt);
|
|
}
|
|
|
|
class RegularFramePaintCallback : public SVGFilterPaintCallback {
|
|
public:
|
|
RegularFramePaintCallback(nsDisplayListBuilder* aBuilder,
|
|
LayerManager* aManager,
|
|
const gfxPoint& aUserSpaceToFrameSpaceOffset)
|
|
: mBuilder(aBuilder),
|
|
mLayerManager(aManager),
|
|
mUserSpaceToFrameSpaceOffset(aUserSpaceToFrameSpaceOffset) {}
|
|
|
|
virtual void Paint(gfxContext& aContext, nsIFrame* aTarget,
|
|
const gfxMatrix& aTransform, const nsIntRect* aDirtyRect,
|
|
imgDrawingParams& aImgParams) override {
|
|
BasicLayerManager* basic = mLayerManager->AsBasicLayerManager();
|
|
RefPtr<gfxContext> oldCtx = basic->GetTarget();
|
|
basic->SetTarget(&aContext);
|
|
|
|
gfxContextMatrixAutoSaveRestore autoSR(&aContext);
|
|
aContext.SetMatrixDouble(aContext.CurrentMatrixDouble().PreTranslate(
|
|
-mUserSpaceToFrameSpaceOffset));
|
|
|
|
mLayerManager->EndTransaction(FrameLayerBuilder::DrawPaintedLayer,
|
|
mBuilder);
|
|
basic->SetTarget(oldCtx);
|
|
}
|
|
|
|
private:
|
|
nsDisplayListBuilder* mBuilder;
|
|
LayerManager* mLayerManager;
|
|
gfxPoint mUserSpaceToFrameSpaceOffset;
|
|
};
|
|
|
|
using PaintFramesParams = SVGIntegrationUtils::PaintFramesParams;
|
|
|
|
/**
|
|
* Paint css-positioned-mask onto a given target(aMaskDT).
|
|
* Return value indicates if mask is complete.
|
|
*/
|
|
static bool PaintMaskSurface(const PaintFramesParams& aParams,
|
|
DrawTarget* aMaskDT, float aOpacity,
|
|
ComputedStyle* aSC,
|
|
const nsTArray<SVGMaskFrame*>& aMaskFrames,
|
|
const Matrix& aMaskSurfaceMatrix,
|
|
const nsPoint& aOffsetToUserSpace) {
|
|
MOZ_ASSERT(aMaskFrames.Length() > 0);
|
|
MOZ_ASSERT(aMaskDT->GetFormat() == SurfaceFormat::A8);
|
|
MOZ_ASSERT(aOpacity == 1.0 || aMaskFrames.Length() == 1);
|
|
|
|
const nsStyleSVGReset* svgReset = aSC->StyleSVGReset();
|
|
gfxMatrix cssPxToDevPxMatrix = SVGUtils::GetCSSPxToDevPxMatrix(aParams.frame);
|
|
|
|
nsPresContext* presContext = aParams.frame->PresContext();
|
|
gfxPoint devPixelOffsetToUserSpace = nsLayoutUtils::PointToGfxPoint(
|
|
aOffsetToUserSpace, presContext->AppUnitsPerDevPixel());
|
|
|
|
RefPtr<gfxContext> maskContext =
|
|
gfxContext::CreatePreservingTransformOrNull(aMaskDT);
|
|
MOZ_ASSERT(maskContext);
|
|
|
|
bool isMaskComplete = true;
|
|
|
|
// Multiple SVG masks interleave with image mask. Paint each layer onto
|
|
// aMaskDT one at a time.
|
|
for (int i = aMaskFrames.Length() - 1; i >= 0; i--) {
|
|
SVGMaskFrame* maskFrame = aMaskFrames[i];
|
|
CompositionOp compositionOp =
|
|
(i == int(aMaskFrames.Length() - 1))
|
|
? CompositionOp::OP_OVER
|
|
: nsCSSRendering::GetGFXCompositeMode(
|
|
svgReset->mMask.mLayers[i].mComposite);
|
|
|
|
// maskFrame != nullptr means we get a SVG mask.
|
|
// maskFrame == nullptr means we get an image mask.
|
|
if (maskFrame) {
|
|
SVGMaskFrame::MaskParams params(
|
|
maskContext, aParams.frame, cssPxToDevPxMatrix, aOpacity,
|
|
svgReset->mMask.mLayers[i].mMaskMode, aParams.imgParams);
|
|
RefPtr<SourceSurface> svgMask = maskFrame->GetMaskForMaskedFrame(params);
|
|
if (svgMask) {
|
|
Matrix tmp = aMaskDT->GetTransform();
|
|
aMaskDT->SetTransform(Matrix());
|
|
aMaskDT->MaskSurface(ColorPattern(DeviceColor(0.0, 0.0, 0.0, 1.0)),
|
|
svgMask, Point(0, 0),
|
|
DrawOptions(1.0, compositionOp));
|
|
aMaskDT->SetTransform(tmp);
|
|
}
|
|
} else if (svgReset->mMask.mLayers[i].mImage.IsResolved()) {
|
|
gfxContextMatrixAutoSaveRestore matRestore(maskContext);
|
|
|
|
maskContext->Multiply(gfxMatrix::Translation(-devPixelOffsetToUserSpace));
|
|
nsCSSRendering::PaintBGParams params =
|
|
nsCSSRendering::PaintBGParams::ForSingleLayer(
|
|
*presContext, aParams.dirtyRect, aParams.borderArea,
|
|
aParams.frame,
|
|
aParams.builder->GetBackgroundPaintFlags() |
|
|
nsCSSRendering::PAINTBG_MASK_IMAGE,
|
|
i, compositionOp, aOpacity);
|
|
|
|
aParams.imgParams.result &= nsCSSRendering::PaintStyleImageLayerWithSC(
|
|
params, *maskContext, aSC, *aParams.frame->StyleBorder());
|
|
} else {
|
|
isMaskComplete = false;
|
|
}
|
|
}
|
|
|
|
return isMaskComplete;
|
|
}
|
|
|
|
struct MaskPaintResult {
|
|
RefPtr<SourceSurface> maskSurface;
|
|
Matrix maskTransform;
|
|
bool transparentBlackMask;
|
|
bool opacityApplied;
|
|
|
|
MaskPaintResult() : transparentBlackMask(false), opacityApplied(false) {}
|
|
};
|
|
|
|
static MaskPaintResult CreateAndPaintMaskSurface(
|
|
const PaintFramesParams& aParams, float aOpacity, ComputedStyle* aSC,
|
|
const nsTArray<SVGMaskFrame*>& aMaskFrames,
|
|
const nsPoint& aOffsetToUserSpace) {
|
|
const nsStyleSVGReset* svgReset = aSC->StyleSVGReset();
|
|
MOZ_ASSERT(aMaskFrames.Length() > 0);
|
|
MaskPaintResult paintResult;
|
|
|
|
gfxContext& ctx = aParams.ctx;
|
|
|
|
// Optimization for single SVG mask.
|
|
if (((aMaskFrames.Length() == 1) && aMaskFrames[0])) {
|
|
gfxMatrix cssPxToDevPxMatrix =
|
|
SVGUtils::GetCSSPxToDevPxMatrix(aParams.frame);
|
|
paintResult.opacityApplied = true;
|
|
SVGMaskFrame::MaskParams params(
|
|
&ctx, aParams.frame, cssPxToDevPxMatrix, aOpacity,
|
|
svgReset->mMask.mLayers[0].mMaskMode, aParams.imgParams);
|
|
paintResult.maskSurface = aMaskFrames[0]->GetMaskForMaskedFrame(params);
|
|
paintResult.maskTransform = ctx.CurrentMatrix();
|
|
paintResult.maskTransform.Invert();
|
|
if (!paintResult.maskSurface) {
|
|
paintResult.transparentBlackMask = true;
|
|
}
|
|
|
|
return paintResult;
|
|
}
|
|
|
|
const Rect& maskSurfaceRect = aParams.maskRect.valueOr(Rect());
|
|
if (aParams.maskRect.isSome() && maskSurfaceRect.IsEmpty()) {
|
|
// XXX: Is this ever true?
|
|
paintResult.transparentBlackMask = true;
|
|
return paintResult;
|
|
}
|
|
|
|
RefPtr<DrawTarget> maskDT = ctx.GetDrawTarget()->CreateClippedDrawTarget(
|
|
maskSurfaceRect, SurfaceFormat::A8);
|
|
if (!maskDT || !maskDT->IsValid()) {
|
|
return paintResult;
|
|
}
|
|
|
|
// We can paint mask along with opacity only if
|
|
// 1. There is only one mask, or
|
|
// 2. No overlap among masks.
|
|
// Collision detect in #2 is not that trivial, we only accept #1 here.
|
|
paintResult.opacityApplied = (aMaskFrames.Length() == 1);
|
|
|
|
// Set context's matrix on maskContext, offset by the maskSurfaceRect's
|
|
// position. This makes sure that we combine the masks in device space.
|
|
Matrix maskSurfaceMatrix = ctx.CurrentMatrix();
|
|
|
|
bool isMaskComplete = PaintMaskSurface(
|
|
aParams, maskDT, paintResult.opacityApplied ? aOpacity : 1.0, aSC,
|
|
aMaskFrames, maskSurfaceMatrix, aOffsetToUserSpace);
|
|
|
|
if (!isMaskComplete ||
|
|
(aParams.imgParams.result != ImgDrawResult::SUCCESS &&
|
|
aParams.imgParams.result != ImgDrawResult::SUCCESS_NOT_COMPLETE &&
|
|
aParams.imgParams.result != ImgDrawResult::WRONG_SIZE)) {
|
|
// Now we know the status of mask resource since we used it while painting.
|
|
// According to the return value of PaintMaskSurface, we know whether mask
|
|
// resource is resolvable or not.
|
|
//
|
|
// For a HTML doc:
|
|
// According to css-masking spec, always create a mask surface when
|
|
// we have any item in maskFrame even if all of those items are
|
|
// non-resolvable <mask-sources> or <images>.
|
|
// Set paintResult.transparentBlackMask as true, the caller should stop
|
|
// painting masked content as if this mask is a transparent black one.
|
|
// For a SVG doc:
|
|
// SVG 1.1 say that if we fail to resolve a mask, we should draw the
|
|
// object unmasked.
|
|
// Left paintResult.maskSurface empty, the caller should paint all
|
|
// masked content as if this mask is an opaque white one(no mask).
|
|
paintResult.transparentBlackMask =
|
|
!aParams.frame->HasAnyStateBits(NS_FRAME_SVG_LAYOUT);
|
|
|
|
MOZ_ASSERT(!paintResult.maskSurface);
|
|
return paintResult;
|
|
}
|
|
|
|
paintResult.maskTransform = maskSurfaceMatrix;
|
|
if (!paintResult.maskTransform.Invert()) {
|
|
return paintResult;
|
|
}
|
|
|
|
paintResult.maskSurface = maskDT->Snapshot();
|
|
return paintResult;
|
|
}
|
|
|
|
static bool ValidateSVGFrame(nsIFrame* aFrame) {
|
|
#ifdef DEBUG
|
|
NS_ASSERTION(!aFrame->HasAnyStateBits(NS_FRAME_SVG_LAYOUT) ||
|
|
(NS_SVGDisplayListPaintingEnabled() &&
|
|
!aFrame->HasAnyStateBits(NS_FRAME_IS_NONDISPLAY)),
|
|
"Should not use SVGIntegrationUtils on this SVG frame");
|
|
#endif
|
|
|
|
bool hasSVGLayout = aFrame->HasAnyStateBits(NS_FRAME_SVG_LAYOUT);
|
|
if (hasSVGLayout) {
|
|
#ifdef DEBUG
|
|
ISVGDisplayableFrame* svgFrame = do_QueryFrame(aFrame);
|
|
MOZ_ASSERT(svgFrame && aFrame->GetContent()->IsSVGElement(),
|
|
"A non-SVG frame carries NS_FRAME_SVG_LAYOUT flag?");
|
|
#endif
|
|
|
|
const nsIContent* content = aFrame->GetContent();
|
|
if (!static_cast<const SVGElement*>(content)->HasValidDimensions()) {
|
|
// The SVG spec says not to draw _anything_
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
struct EffectOffsets {
|
|
// The offset between the reference frame and the bounding box of the
|
|
// target frame in app unit.
|
|
nsPoint offsetToBoundingBox;
|
|
// The offset between the reference frame and the bounding box of the
|
|
// target frame in app unit.
|
|
nsPoint offsetToUserSpace;
|
|
// The offset between the reference frame and the bounding box of the
|
|
// target frame in device unit.
|
|
gfxPoint offsetToUserSpaceInDevPx;
|
|
};
|
|
|
|
static EffectOffsets ComputeEffectOffset(nsIFrame* aFrame,
|
|
const PaintFramesParams& aParams) {
|
|
EffectOffsets result;
|
|
|
|
result.offsetToBoundingBox =
|
|
aParams.builder->ToReferenceFrame(aFrame) -
|
|
SVGIntegrationUtils::GetOffsetToBoundingBox(aFrame);
|
|
if (!aFrame->IsFrameOfType(nsIFrame::eSVG)) {
|
|
/* Snap the offset if the reference frame is not a SVG frame,
|
|
* since other frames will be snapped to pixel when rendering. */
|
|
result.offsetToBoundingBox =
|
|
nsPoint(aFrame->PresContext()->RoundAppUnitsToNearestDevPixels(
|
|
result.offsetToBoundingBox.x),
|
|
aFrame->PresContext()->RoundAppUnitsToNearestDevPixels(
|
|
result.offsetToBoundingBox.y));
|
|
}
|
|
|
|
// After applying only "aOffsetToBoundingBox", aParams.ctx would have its
|
|
// origin at the top left corner of frame's bounding box (over all
|
|
// continuations).
|
|
// However, SVG painting needs the origin to be located at the origin of the
|
|
// SVG frame's "user space", i.e. the space in which, for example, the
|
|
// frame's BBox lives.
|
|
// SVG geometry frames and foreignObject frames apply their own offsets, so
|
|
// their position is relative to their user space. So for these frame types,
|
|
// if we want aParams.ctx to be in user space, we first need to subtract the
|
|
// frame's position so that SVG painting can later add it again and the
|
|
// frame is painted in the right place.
|
|
gfxPoint toUserSpaceGfx =
|
|
SVGUtils::FrameSpaceInCSSPxToUserSpaceOffset(aFrame);
|
|
nsPoint toUserSpace =
|
|
nsPoint(nsPresContext::CSSPixelsToAppUnits(float(toUserSpaceGfx.x)),
|
|
nsPresContext::CSSPixelsToAppUnits(float(toUserSpaceGfx.y)));
|
|
|
|
result.offsetToUserSpace = result.offsetToBoundingBox - toUserSpace;
|
|
|
|
#ifdef DEBUG
|
|
bool hasSVGLayout = aFrame->HasAnyStateBits(NS_FRAME_SVG_LAYOUT);
|
|
NS_ASSERTION(
|
|
hasSVGLayout || result.offsetToBoundingBox == result.offsetToUserSpace,
|
|
"For non-SVG frames there shouldn't be any additional offset");
|
|
#endif
|
|
|
|
result.offsetToUserSpaceInDevPx = nsLayoutUtils::PointToGfxPoint(
|
|
result.offsetToUserSpace, aFrame->PresContext()->AppUnitsPerDevPixel());
|
|
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Setup transform matrix of a gfx context by a specific frame. Move the
|
|
* origin of aParams.ctx to the user space of aFrame.
|
|
*/
|
|
static EffectOffsets MoveContextOriginToUserSpace(
|
|
nsIFrame* aFrame, const PaintFramesParams& aParams) {
|
|
EffectOffsets offset = ComputeEffectOffset(aFrame, aParams);
|
|
|
|
aParams.ctx.SetMatrixDouble(aParams.ctx.CurrentMatrixDouble().PreTranslate(
|
|
offset.offsetToUserSpaceInDevPx));
|
|
|
|
return offset;
|
|
}
|
|
|
|
bool SVGIntegrationUtils::IsMaskResourceReady(nsIFrame* aFrame) {
|
|
nsIFrame* firstFrame =
|
|
nsLayoutUtils::FirstContinuationOrIBSplitSibling(aFrame);
|
|
nsTArray<SVGMaskFrame*> maskFrames;
|
|
// XXX check return value?
|
|
SVGObserverUtils::GetAndObserveMasks(firstFrame, &maskFrames);
|
|
|
|
const nsStyleSVGReset* svgReset = firstFrame->StyleSVGReset();
|
|
|
|
for (uint32_t i = 0; i < maskFrames.Length(); i++) {
|
|
// Refers to a valid SVG mask.
|
|
if (maskFrames[i]) {
|
|
continue;
|
|
}
|
|
|
|
// Refers to an external resource, which is not ready yet.
|
|
if (!svgReset->mMask.mLayers[i].mImage.IsComplete()) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Either all mask resources are ready, or no mask resource is needed.
|
|
return true;
|
|
}
|
|
|
|
class AutoPopGroup {
|
|
public:
|
|
AutoPopGroup() : mContext(nullptr) {}
|
|
|
|
~AutoPopGroup() {
|
|
if (mContext) {
|
|
mContext->PopGroupAndBlend();
|
|
}
|
|
}
|
|
|
|
void SetContext(gfxContext* aContext) { mContext = aContext; }
|
|
|
|
private:
|
|
gfxContext* mContext;
|
|
};
|
|
|
|
bool SVGIntegrationUtils::PaintMask(const PaintFramesParams& aParams,
|
|
bool& aOutIsMaskComplete) {
|
|
aOutIsMaskComplete = true;
|
|
|
|
SVGUtils::MaskUsage maskUsage;
|
|
SVGUtils::DetermineMaskUsage(aParams.frame, aParams.handleOpacity, maskUsage);
|
|
if (!maskUsage.shouldDoSomething()) {
|
|
return false;
|
|
}
|
|
|
|
nsIFrame* frame = aParams.frame;
|
|
if (!ValidateSVGFrame(frame)) {
|
|
return false;
|
|
}
|
|
|
|
gfxContext& ctx = aParams.ctx;
|
|
RefPtr<DrawTarget> maskTarget = ctx.GetDrawTarget();
|
|
|
|
if (maskUsage.shouldGenerateMaskLayer &&
|
|
(maskUsage.shouldGenerateClipMaskLayer ||
|
|
maskUsage.shouldApplyClipPath)) {
|
|
// We will paint both mask of positioned mask and clip-path into
|
|
// maskTarget.
|
|
//
|
|
// Create one extra draw target for drawing positioned mask, so that we do
|
|
// not have to copy the content of maskTarget before painting
|
|
// clip-path into it.
|
|
if (!maskTarget->CanCreateSimilarDrawTarget(maskTarget->GetSize(),
|
|
SurfaceFormat::A8)) {
|
|
return false;
|
|
}
|
|
maskTarget = maskTarget->CreateSimilarDrawTarget(maskTarget->GetSize(),
|
|
SurfaceFormat::A8);
|
|
}
|
|
|
|
nsIFrame* firstFrame =
|
|
nsLayoutUtils::FirstContinuationOrIBSplitSibling(frame);
|
|
nsTArray<SVGMaskFrame*> maskFrames;
|
|
// XXX check return value?
|
|
SVGObserverUtils::GetAndObserveMasks(firstFrame, &maskFrames);
|
|
|
|
AutoPopGroup autoPop;
|
|
bool shouldPushOpacity =
|
|
(maskUsage.opacity != 1.0) && (maskFrames.Length() != 1);
|
|
if (shouldPushOpacity) {
|
|
ctx.PushGroupForBlendBack(gfxContentType::COLOR_ALPHA, maskUsage.opacity);
|
|
autoPop.SetContext(&ctx);
|
|
}
|
|
|
|
gfxContextMatrixAutoSaveRestore matSR;
|
|
|
|
// Paint clip-path-basic-shape onto ctx
|
|
gfxContextAutoSaveRestore basicShapeSR;
|
|
if (maskUsage.shouldApplyBasicShapeOrPath) {
|
|
matSR.SetContext(&ctx);
|
|
|
|
MoveContextOriginToUserSpace(firstFrame, aParams);
|
|
|
|
basicShapeSR.SetContext(&ctx);
|
|
CSSClipPathInstance::ApplyBasicShapeOrPathClip(
|
|
ctx, frame, SVGUtils::GetCSSPxToDevPxMatrix(frame));
|
|
if (!maskUsage.shouldGenerateMaskLayer) {
|
|
// Only have basic-shape clip-path effect. Fill clipped region by
|
|
// opaque white.
|
|
ctx.SetDeviceColor(DeviceColor::MaskOpaqueWhite());
|
|
ctx.Fill();
|
|
|
|
return true;
|
|
}
|
|
}
|
|
|
|
// Paint mask onto ctx.
|
|
if (maskUsage.shouldGenerateMaskLayer) {
|
|
matSR.Restore();
|
|
matSR.SetContext(&ctx);
|
|
|
|
EffectOffsets offsets = MoveContextOriginToUserSpace(frame, aParams);
|
|
aOutIsMaskComplete = PaintMaskSurface(
|
|
aParams, maskTarget, shouldPushOpacity ? 1.0 : maskUsage.opacity,
|
|
firstFrame->Style(), maskFrames, ctx.CurrentMatrix(),
|
|
offsets.offsetToUserSpace);
|
|
}
|
|
|
|
// Paint clip-path onto ctx.
|
|
if (maskUsage.shouldGenerateClipMaskLayer || maskUsage.shouldApplyClipPath) {
|
|
matSR.Restore();
|
|
matSR.SetContext(&ctx);
|
|
|
|
MoveContextOriginToUserSpace(firstFrame, aParams);
|
|
Matrix clipMaskTransform;
|
|
gfxMatrix cssPxToDevPxMatrix = SVGUtils::GetCSSPxToDevPxMatrix(frame);
|
|
|
|
SVGClipPathFrame* clipPathFrame;
|
|
// XXX check return value?
|
|
SVGObserverUtils::GetAndObserveClipPath(firstFrame, &clipPathFrame);
|
|
RefPtr<SourceSurface> maskSurface =
|
|
maskUsage.shouldGenerateMaskLayer ? maskTarget->Snapshot() : nullptr;
|
|
clipPathFrame->PaintClipMask(ctx, frame, cssPxToDevPxMatrix, maskSurface,
|
|
ctx.CurrentMatrix());
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
template <class T>
|
|
void PaintMaskAndClipPathInternal(const PaintFramesParams& aParams,
|
|
const T& aPaintChild) {
|
|
MOZ_ASSERT(SVGIntegrationUtils::UsingMaskOrClipPathForFrame(aParams.frame),
|
|
"Should not use this method when no mask or clipPath effect"
|
|
"on this frame");
|
|
|
|
/* SVG defines the following rendering model:
|
|
*
|
|
* 1. Render geometry
|
|
* 2. Apply filter
|
|
* 3. Apply clipping, masking, group opacity
|
|
*
|
|
* We handle #3 here and perform a couple of optimizations:
|
|
*
|
|
* + Use cairo's clipPath when representable natively (single object
|
|
* clip region).
|
|
*
|
|
* + Merge opacity and masking if both used together.
|
|
*/
|
|
nsIFrame* frame = aParams.frame;
|
|
if (!ValidateSVGFrame(frame)) {
|
|
return;
|
|
}
|
|
|
|
SVGUtils::MaskUsage maskUsage;
|
|
SVGUtils::DetermineMaskUsage(aParams.frame, aParams.handleOpacity, maskUsage);
|
|
|
|
if (maskUsage.opacity == 0.0f) {
|
|
return;
|
|
}
|
|
|
|
gfxContext& context = aParams.ctx;
|
|
gfxContextMatrixAutoSaveRestore matrixAutoSaveRestore(&context);
|
|
|
|
nsIFrame* firstFrame =
|
|
nsLayoutUtils::FirstContinuationOrIBSplitSibling(frame);
|
|
|
|
SVGClipPathFrame* clipPathFrame;
|
|
// XXX check return value?
|
|
SVGObserverUtils::GetAndObserveClipPath(firstFrame, &clipPathFrame);
|
|
|
|
nsTArray<SVGMaskFrame*> maskFrames;
|
|
// XXX check return value?
|
|
SVGObserverUtils::GetAndObserveMasks(firstFrame, &maskFrames);
|
|
|
|
gfxMatrix cssPxToDevPxMatrix = SVGUtils::GetCSSPxToDevPxMatrix(frame);
|
|
|
|
bool shouldGenerateMask =
|
|
(maskUsage.opacity != 1.0f || maskUsage.shouldGenerateClipMaskLayer ||
|
|
maskUsage.shouldGenerateMaskLayer);
|
|
bool shouldPushMask = false;
|
|
|
|
/* Check if we need to do additional operations on this child's
|
|
* rendering, which necessitates rendering into another surface. */
|
|
if (shouldGenerateMask) {
|
|
gfxContextMatrixAutoSaveRestore matSR;
|
|
|
|
Matrix maskTransform;
|
|
RefPtr<SourceSurface> maskSurface;
|
|
bool opacityApplied = false;
|
|
|
|
if (maskUsage.shouldGenerateMaskLayer) {
|
|
matSR.SetContext(&context);
|
|
|
|
// For css-mask, we want to generate a mask for each continuation frame,
|
|
// so we setup context matrix by the position of the current frame,
|
|
// instead of the first continuation frame.
|
|
EffectOffsets offsets = MoveContextOriginToUserSpace(frame, aParams);
|
|
MaskPaintResult paintResult = CreateAndPaintMaskSurface(
|
|
aParams, maskUsage.opacity, firstFrame->Style(), maskFrames,
|
|
offsets.offsetToUserSpace);
|
|
|
|
if (paintResult.transparentBlackMask) {
|
|
return;
|
|
}
|
|
|
|
maskSurface = paintResult.maskSurface;
|
|
if (maskSurface) {
|
|
shouldPushMask = true;
|
|
// We want the mask to be untransformed so use the inverse of the
|
|
// current transform as the maskTransform to compensate.
|
|
maskTransform = context.CurrentMatrix();
|
|
maskTransform.Invert();
|
|
|
|
opacityApplied = paintResult.opacityApplied;
|
|
}
|
|
}
|
|
|
|
if (maskUsage.shouldGenerateClipMaskLayer) {
|
|
matSR.Restore();
|
|
matSR.SetContext(&context);
|
|
|
|
MoveContextOriginToUserSpace(firstFrame, aParams);
|
|
RefPtr<SourceSurface> clipMaskSurface = clipPathFrame->GetClipMask(
|
|
context, frame, cssPxToDevPxMatrix, maskSurface, maskTransform);
|
|
|
|
if (clipMaskSurface) {
|
|
maskSurface = clipMaskSurface;
|
|
// We want the mask to be untransformed so use the inverse of the
|
|
// current transform as the maskTransform to compensate.
|
|
maskTransform = context.CurrentMatrix();
|
|
maskTransform.Invert();
|
|
} else {
|
|
// Either entire surface is clipped out, or gfx buffer allocation
|
|
// failure in SVGClipPathFrame::GetClipMask.
|
|
return;
|
|
}
|
|
|
|
shouldPushMask = true;
|
|
}
|
|
|
|
// opacity != 1.0f.
|
|
if (!maskUsage.shouldGenerateClipMaskLayer &&
|
|
!maskUsage.shouldGenerateMaskLayer) {
|
|
MOZ_ASSERT(maskUsage.opacity != 1.0f);
|
|
|
|
matSR.SetContext(&context);
|
|
MoveContextOriginToUserSpace(firstFrame, aParams);
|
|
shouldPushMask = true;
|
|
}
|
|
|
|
if (shouldPushMask) {
|
|
if (aParams.layerManager &&
|
|
aParams.layerManager->GetRoot()->GetContentFlags() &
|
|
Layer::CONTENT_COMPONENT_ALPHA) {
|
|
context.PushGroupAndCopyBackground(
|
|
gfxContentType::COLOR_ALPHA,
|
|
opacityApplied ? 1.0 : maskUsage.opacity, maskSurface,
|
|
maskTransform);
|
|
} else {
|
|
context.PushGroupForBlendBack(gfxContentType::COLOR_ALPHA,
|
|
opacityApplied ? 1.0 : maskUsage.opacity,
|
|
maskSurface, maskTransform);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* If this frame has only a trivial clipPath, set up cairo's clipping now so
|
|
* we can just do normal painting and get it clipped appropriately.
|
|
*/
|
|
if (maskUsage.shouldApplyClipPath || maskUsage.shouldApplyBasicShapeOrPath) {
|
|
gfxContextMatrixAutoSaveRestore matSR(&context);
|
|
|
|
MoveContextOriginToUserSpace(firstFrame, aParams);
|
|
|
|
MOZ_ASSERT(!maskUsage.shouldApplyClipPath ||
|
|
!maskUsage.shouldApplyBasicShapeOrPath);
|
|
if (maskUsage.shouldApplyClipPath) {
|
|
clipPathFrame->ApplyClipPath(context, frame, cssPxToDevPxMatrix);
|
|
} else {
|
|
CSSClipPathInstance::ApplyBasicShapeOrPathClip(context, frame,
|
|
cssPxToDevPxMatrix);
|
|
}
|
|
}
|
|
|
|
/* Paint the child */
|
|
context.SetMatrix(matrixAutoSaveRestore.Matrix());
|
|
aPaintChild();
|
|
|
|
if (StaticPrefs::layers_draw_mask_debug()) {
|
|
gfxContextAutoSaveRestore saver(&context);
|
|
|
|
context.NewPath();
|
|
gfxRect drawingRect = nsLayoutUtils::RectToGfxRect(
|
|
aParams.borderArea, frame->PresContext()->AppUnitsPerDevPixel());
|
|
context.SnappedRectangle(drawingRect);
|
|
sRGBColor overlayColor(0.0f, 0.0f, 0.0f, 0.8f);
|
|
if (maskUsage.shouldGenerateMaskLayer) {
|
|
overlayColor.r = 1.0f; // red represents css positioned mask.
|
|
}
|
|
if (maskUsage.shouldApplyClipPath ||
|
|
maskUsage.shouldGenerateClipMaskLayer) {
|
|
overlayColor.g = 1.0f; // green represents clip-path:<clip-source>.
|
|
}
|
|
if (maskUsage.shouldApplyBasicShapeOrPath) {
|
|
overlayColor.b = 1.0f; // blue represents
|
|
// clip-path:<basic-shape>||<geometry-box>.
|
|
}
|
|
|
|
context.SetColor(overlayColor);
|
|
context.Fill();
|
|
}
|
|
|
|
if (maskUsage.shouldApplyClipPath || maskUsage.shouldApplyBasicShapeOrPath) {
|
|
context.PopClip();
|
|
}
|
|
|
|
if (shouldPushMask) {
|
|
context.PopGroupAndBlend();
|
|
}
|
|
}
|
|
|
|
void SVGIntegrationUtils::PaintMaskAndClipPath(
|
|
const PaintFramesParams& aParams) {
|
|
PaintMaskAndClipPathInternal(aParams, [&] {
|
|
gfxContext& context = aParams.ctx;
|
|
BasicLayerManager* basic = aParams.layerManager->AsBasicLayerManager();
|
|
RefPtr<gfxContext> oldCtx = basic->GetTarget();
|
|
basic->SetTarget(&context);
|
|
aParams.layerManager->EndTransaction(FrameLayerBuilder::DrawPaintedLayer,
|
|
aParams.builder);
|
|
basic->SetTarget(oldCtx);
|
|
});
|
|
}
|
|
|
|
void SVGIntegrationUtils::PaintMaskAndClipPath(
|
|
const PaintFramesParams& aParams,
|
|
const std::function<void()>& aPaintChild) {
|
|
PaintMaskAndClipPathInternal(aParams, aPaintChild);
|
|
}
|
|
|
|
void SVGIntegrationUtils::PaintFilter(const PaintFramesParams& aParams) {
|
|
MOZ_ASSERT(!aParams.builder->IsForGenerateGlyphMask(),
|
|
"Filter effect is discarded while generating glyph mask.");
|
|
MOZ_ASSERT(aParams.frame->StyleEffects()->HasFilters(),
|
|
"Should not use this method when no filter effect on this frame");
|
|
|
|
nsIFrame* frame = aParams.frame;
|
|
if (!ValidateSVGFrame(frame)) {
|
|
return;
|
|
}
|
|
|
|
float opacity = SVGUtils::ComputeOpacity(frame, aParams.handleOpacity);
|
|
if (opacity == 0.0f) {
|
|
return;
|
|
}
|
|
|
|
/* Properties are added lazily and may have been removed by a restyle,
|
|
so make sure all applicable ones are set again. */
|
|
nsIFrame* firstFrame =
|
|
nsLayoutUtils::FirstContinuationOrIBSplitSibling(frame);
|
|
// Note: we do not return here for eHasNoRefs since we must still handle any
|
|
// CSS filter functions.
|
|
// TODO: We currently pass nullptr instead of an nsTArray* here, but we
|
|
// actually should get the filter frames and then pass them into
|
|
// PaintFilteredFrame below! See bug 1494263.
|
|
// XXX: Do we need to check for eHasRefsSomeInvalid here given that
|
|
// nsDisplayFilter::BuildLayer returns nullptr for eHasRefsSomeInvalid?
|
|
// Or can we just assert !eHasRefsSomeInvalid?
|
|
if (SVGObserverUtils::GetAndObserveFilters(firstFrame, nullptr) ==
|
|
SVGObserverUtils::eHasRefsSomeInvalid) {
|
|
return;
|
|
}
|
|
|
|
gfxContext& context = aParams.ctx;
|
|
|
|
gfxContextAutoSaveRestore autoSR(&context);
|
|
EffectOffsets offsets = MoveContextOriginToUserSpace(firstFrame, aParams);
|
|
|
|
/* Paint the child and apply filters */
|
|
RegularFramePaintCallback callback(aParams.builder, aParams.layerManager,
|
|
offsets.offsetToUserSpaceInDevPx);
|
|
nsRegion dirtyRegion = aParams.dirtyRect - offsets.offsetToBoundingBox;
|
|
|
|
FilterInstance::PaintFilteredFrame(frame, &context, &callback, &dirtyRegion,
|
|
aParams.imgParams, opacity);
|
|
}
|
|
|
|
bool SVGIntegrationUtils::CreateWebRenderCSSFilters(
|
|
Span<const StyleFilter> aFilters, nsIFrame* aFrame,
|
|
WrFiltersHolder& aWrFilters) {
|
|
// All CSS filters are supported by WebRender. SVG filters are not fully
|
|
// supported, those use NS_STYLE_FILTER_URL and are handled separately.
|
|
|
|
// If there are too many filters to render, then just pretend that we
|
|
// succeeded, and don't render any of them.
|
|
if (aFilters.Length() >
|
|
StaticPrefs::gfx_webrender_max_filter_ops_per_chain()) {
|
|
return true;
|
|
}
|
|
aWrFilters.filters.SetCapacity(aFilters.Length());
|
|
auto& wrFilters = aWrFilters.filters;
|
|
for (const StyleFilter& filter : aFilters) {
|
|
switch (filter.tag) {
|
|
case StyleFilter::Tag::Brightness:
|
|
wrFilters.AppendElement(
|
|
wr::FilterOp::Brightness(filter.AsBrightness()));
|
|
break;
|
|
case StyleFilter::Tag::Contrast:
|
|
wrFilters.AppendElement(wr::FilterOp::Contrast(filter.AsContrast()));
|
|
break;
|
|
case StyleFilter::Tag::Grayscale:
|
|
wrFilters.AppendElement(wr::FilterOp::Grayscale(filter.AsGrayscale()));
|
|
break;
|
|
case StyleFilter::Tag::Invert:
|
|
wrFilters.AppendElement(wr::FilterOp::Invert(filter.AsInvert()));
|
|
break;
|
|
case StyleFilter::Tag::Opacity: {
|
|
float opacity = filter.AsOpacity();
|
|
wrFilters.AppendElement(wr::FilterOp::Opacity(
|
|
wr::PropertyBinding<float>::Value(opacity), opacity));
|
|
break;
|
|
}
|
|
case StyleFilter::Tag::Saturate:
|
|
wrFilters.AppendElement(wr::FilterOp::Saturate(filter.AsSaturate()));
|
|
break;
|
|
case StyleFilter::Tag::Sepia:
|
|
wrFilters.AppendElement(wr::FilterOp::Sepia(filter.AsSepia()));
|
|
break;
|
|
case StyleFilter::Tag::HueRotate: {
|
|
wrFilters.AppendElement(
|
|
wr::FilterOp::HueRotate(filter.AsHueRotate().ToDegrees()));
|
|
break;
|
|
}
|
|
case StyleFilter::Tag::Blur: {
|
|
// TODO(emilio): we should go directly from css pixels -> device pixels.
|
|
float appUnitsPerDevPixel =
|
|
aFrame->PresContext()->AppUnitsPerDevPixel();
|
|
float radius = NSAppUnitsToFloatPixels(filter.AsBlur().ToAppUnits(),
|
|
appUnitsPerDevPixel);
|
|
wrFilters.AppendElement(wr::FilterOp::Blur(radius, radius));
|
|
break;
|
|
}
|
|
case StyleFilter::Tag::DropShadow: {
|
|
float appUnitsPerDevPixel =
|
|
aFrame->PresContext()->AppUnitsPerDevPixel();
|
|
const StyleSimpleShadow& shadow = filter.AsDropShadow();
|
|
nscolor color = shadow.color.CalcColor(aFrame);
|
|
|
|
wr::Shadow wrShadow;
|
|
wrShadow.offset = {
|
|
NSAppUnitsToFloatPixels(shadow.horizontal.ToAppUnits(),
|
|
appUnitsPerDevPixel),
|
|
NSAppUnitsToFloatPixels(shadow.vertical.ToAppUnits(),
|
|
appUnitsPerDevPixel)};
|
|
wrShadow.blur_radius = NSAppUnitsToFloatPixels(shadow.blur.ToAppUnits(),
|
|
appUnitsPerDevPixel);
|
|
wrShadow.color = {NS_GET_R(color) / 255.0f, NS_GET_G(color) / 255.0f,
|
|
NS_GET_B(color) / 255.0f, NS_GET_A(color) / 255.0f};
|
|
wrFilters.AppendElement(wr::FilterOp::DropShadow(wrShadow));
|
|
break;
|
|
}
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool SVGIntegrationUtils::BuildWebRenderFilters(
|
|
nsIFrame* aFilteredFrame, Span<const StyleFilter> aFilters,
|
|
WrFiltersHolder& aWrFilters, Maybe<nsRect>& aPostFilterClip) {
|
|
return FilterInstance::BuildWebRenderFilters(aFilteredFrame, aFilters,
|
|
aWrFilters, aPostFilterClip);
|
|
}
|
|
|
|
bool SVGIntegrationUtils::CanCreateWebRenderFiltersForFrame(nsIFrame* aFrame) {
|
|
WrFiltersHolder wrFilters;
|
|
Maybe<nsRect> filterClip;
|
|
auto filterChain = aFrame->StyleEffects()->mFilters.AsSpan();
|
|
return CreateWebRenderCSSFilters(filterChain, aFrame, wrFilters) ||
|
|
BuildWebRenderFilters(aFrame, filterChain, wrFilters, filterClip);
|
|
}
|
|
|
|
bool SVGIntegrationUtils::UsesSVGEffectsNotSupportedInCompositor(
|
|
nsIFrame* aFrame) {
|
|
// WebRender supports masks / clip-paths and some filters in the compositor.
|
|
// Non-WebRender doesn't support any SVG effects in the compositor.
|
|
if (aFrame->StyleEffects()->HasFilters()) {
|
|
return !gfx::gfxVars::UseWebRender() ||
|
|
!SVGIntegrationUtils::CanCreateWebRenderFiltersForFrame(aFrame);
|
|
}
|
|
if (SVGIntegrationUtils::UsingMaskOrClipPathForFrame(aFrame)) {
|
|
return !gfx::gfxVars::UseWebRender();
|
|
}
|
|
return false;
|
|
}
|
|
|
|
class PaintFrameCallback : public gfxDrawingCallback {
|
|
public:
|
|
PaintFrameCallback(nsIFrame* aFrame, const nsSize aPaintServerSize,
|
|
const IntSize aRenderSize, uint32_t aFlags)
|
|
: mFrame(aFrame),
|
|
mPaintServerSize(aPaintServerSize),
|
|
mRenderSize(aRenderSize),
|
|
mFlags(aFlags) {}
|
|
virtual bool operator()(gfxContext* aContext, const gfxRect& aFillRect,
|
|
const SamplingFilter aSamplingFilter,
|
|
const gfxMatrix& aTransform) override;
|
|
|
|
private:
|
|
nsIFrame* mFrame;
|
|
nsSize mPaintServerSize;
|
|
IntSize mRenderSize;
|
|
uint32_t mFlags;
|
|
};
|
|
|
|
bool PaintFrameCallback::operator()(gfxContext* aContext,
|
|
const gfxRect& aFillRect,
|
|
const SamplingFilter aSamplingFilter,
|
|
const gfxMatrix& aTransform) {
|
|
if (mFrame->HasAnyStateBits(NS_FRAME_DRAWING_AS_PAINTSERVER)) {
|
|
return false;
|
|
}
|
|
|
|
AutoSetRestorePaintServerState paintServer(mFrame);
|
|
|
|
aContext->Save();
|
|
|
|
// Clip to aFillRect so that we don't paint outside.
|
|
aContext->NewPath();
|
|
aContext->Rectangle(aFillRect);
|
|
aContext->Clip();
|
|
|
|
gfxMatrix invmatrix = aTransform;
|
|
if (!invmatrix.Invert()) {
|
|
return false;
|
|
}
|
|
aContext->Multiply(invmatrix);
|
|
|
|
// nsLayoutUtils::PaintFrame will anchor its painting at mFrame. But we want
|
|
// to have it anchored at the top left corner of the bounding box of all of
|
|
// mFrame's continuations. So we add a translation transform.
|
|
int32_t appUnitsPerDevPixel = mFrame->PresContext()->AppUnitsPerDevPixel();
|
|
nsPoint offset = SVGIntegrationUtils::GetOffsetToBoundingBox(mFrame);
|
|
gfxPoint devPxOffset = gfxPoint(offset.x, offset.y) / appUnitsPerDevPixel;
|
|
aContext->Multiply(gfxMatrix::Translation(devPxOffset));
|
|
|
|
gfxSize paintServerSize =
|
|
gfxSize(mPaintServerSize.width, mPaintServerSize.height) /
|
|
mFrame->PresContext()->AppUnitsPerDevPixel();
|
|
|
|
// nsLayoutUtils::PaintFrame wants to render with paintServerSize, but we
|
|
// want it to render with mRenderSize, so we need to set up a scale transform.
|
|
gfxFloat scaleX = mRenderSize.width / paintServerSize.width;
|
|
gfxFloat scaleY = mRenderSize.height / paintServerSize.height;
|
|
aContext->Multiply(gfxMatrix::Scaling(scaleX, scaleY));
|
|
|
|
// Draw.
|
|
nsRect dirty(-offset.x, -offset.y, mPaintServerSize.width,
|
|
mPaintServerSize.height);
|
|
|
|
using PaintFrameFlags = nsLayoutUtils::PaintFrameFlags;
|
|
PaintFrameFlags flags = PaintFrameFlags::InTransform;
|
|
if (mFlags & SVGIntegrationUtils::FLAG_SYNC_DECODE_IMAGES) {
|
|
flags |= PaintFrameFlags::SyncDecodeImages;
|
|
}
|
|
nsLayoutUtils::PaintFrame(aContext, mFrame, dirty, NS_RGBA(0, 0, 0, 0),
|
|
nsDisplayListBuilderMode::Painting, flags);
|
|
|
|
nsIFrame* currentFrame = mFrame;
|
|
while ((currentFrame = currentFrame->GetNextContinuation()) != nullptr) {
|
|
offset = currentFrame->GetOffsetToCrossDoc(mFrame);
|
|
devPxOffset = gfxPoint(offset.x, offset.y) / appUnitsPerDevPixel;
|
|
|
|
aContext->Save();
|
|
aContext->Multiply(gfxMatrix::Scaling(1 / scaleX, 1 / scaleY));
|
|
aContext->Multiply(gfxMatrix::Translation(devPxOffset));
|
|
aContext->Multiply(gfxMatrix::Scaling(scaleX, scaleY));
|
|
|
|
nsLayoutUtils::PaintFrame(aContext, currentFrame, dirty - offset,
|
|
NS_RGBA(0, 0, 0, 0),
|
|
nsDisplayListBuilderMode::Painting, flags);
|
|
|
|
aContext->Restore();
|
|
}
|
|
|
|
aContext->Restore();
|
|
|
|
return true;
|
|
}
|
|
|
|
/* static */
|
|
already_AddRefed<gfxDrawable> SVGIntegrationUtils::DrawableFromPaintServer(
|
|
nsIFrame* aFrame, nsIFrame* aTarget, const nsSize& aPaintServerSize,
|
|
const IntSize& aRenderSize, const DrawTarget* aDrawTarget,
|
|
const gfxMatrix& aContextMatrix, uint32_t aFlags) {
|
|
// aPaintServerSize is the size that would be filled when using
|
|
// background-repeat:no-repeat and background-size:auto. For normal background
|
|
// images, this would be the intrinsic size of the image; for gradients and
|
|
// patterns this would be the whole target frame fill area.
|
|
// aRenderSize is what we will be actually filling after accounting for
|
|
// background-size.
|
|
if (SVGPaintServerFrame* server = do_QueryFrame(aFrame)) {
|
|
// aFrame is either a pattern or a gradient. These fill the whole target
|
|
// frame by default, so aPaintServerSize is the whole target background fill
|
|
// area.
|
|
gfxRect overrideBounds(0, 0, aPaintServerSize.width,
|
|
aPaintServerSize.height);
|
|
overrideBounds.Scale(1.0 / aFrame->PresContext()->AppUnitsPerDevPixel());
|
|
imgDrawingParams imgParams(aFlags);
|
|
RefPtr<gfxPattern> pattern = server->GetPaintServerPattern(
|
|
aTarget, aDrawTarget, aContextMatrix, &nsStyleSVG::mFill, 1.0,
|
|
imgParams, &overrideBounds);
|
|
|
|
if (!pattern) {
|
|
return nullptr;
|
|
}
|
|
|
|
// pattern is now set up to fill aPaintServerSize. But we want it to
|
|
// fill aRenderSize, so we need to add a scaling transform.
|
|
// We couldn't just have set overrideBounds to aRenderSize - it would have
|
|
// worked for gradients, but for patterns it would result in a different
|
|
// pattern size.
|
|
gfxFloat scaleX = overrideBounds.Width() / aRenderSize.width;
|
|
gfxFloat scaleY = overrideBounds.Height() / aRenderSize.height;
|
|
gfxMatrix scaleMatrix = gfxMatrix::Scaling(scaleX, scaleY);
|
|
pattern->SetMatrix(scaleMatrix * pattern->GetMatrix());
|
|
RefPtr<gfxDrawable> drawable = new gfxPatternDrawable(pattern, aRenderSize);
|
|
return drawable.forget();
|
|
}
|
|
|
|
if (aFrame->IsFrameOfType(nsIFrame::eSVG) &&
|
|
!static_cast<ISVGDisplayableFrame*>(do_QueryFrame(aFrame))) {
|
|
MOZ_ASSERT_UNREACHABLE(
|
|
"We should prevent painting of unpaintable SVG "
|
|
"before we get here");
|
|
return nullptr;
|
|
}
|
|
|
|
// We don't want to paint into a surface as long as we don't need to, so we
|
|
// set up a drawing callback.
|
|
RefPtr<gfxDrawingCallback> cb =
|
|
new PaintFrameCallback(aFrame, aPaintServerSize, aRenderSize, aFlags);
|
|
RefPtr<gfxDrawable> drawable = new gfxCallbackDrawable(cb, aRenderSize);
|
|
return drawable.forget();
|
|
}
|
|
|
|
} // namespace mozilla
|