1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
8 #include "SVGClipPathFrame.h"
10 // Keep others in (case-insensitive) order:
11 #include "AutoReferenceChainGuard.h"
12 #include "ImgDrawResult.h"
13 #include "gfxContext.h"
14 #include "mozilla/PresShell.h"
15 #include "mozilla/SVGGeometryFrame.h"
16 #include "mozilla/SVGObserverUtils.h"
17 #include "mozilla/SVGUtils.h"
18 #include "mozilla/dom/SVGClipPathElement.h"
19 #include "mozilla/dom/SVGGeometryElement.h"
20 #include "nsGkAtoms.h"
22 using namespace mozilla::dom
;
23 using namespace mozilla::gfx
;
24 using namespace mozilla::image
;
26 //----------------------------------------------------------------------
29 nsIFrame
* NS_NewSVGClipPathFrame(mozilla::PresShell
* aPresShell
,
30 mozilla::ComputedStyle
* aStyle
) {
31 return new (aPresShell
)
32 mozilla::SVGClipPathFrame(aStyle
, aPresShell
->GetPresContext());
37 NS_IMPL_FRAMEARENA_HELPERS(SVGClipPathFrame
)
39 void SVGClipPathFrame::ApplyClipPath(gfxContext
& aContext
,
40 nsIFrame
* aClippedFrame
,
41 const gfxMatrix
& aMatrix
) {
42 nsIFrame
* singleClipPathChild
= nullptr;
43 DebugOnly
<bool> trivial
= IsTrivial(&singleClipPathChild
);
44 MOZ_ASSERT(trivial
, "Caller needs to use GetClipMask");
46 const DrawTarget
* drawTarget
= aContext
.GetDrawTarget();
48 // No need for AutoReferenceChainGuard since simple clip paths by definition
49 // don't reference another clip path.
51 // Restore current transform after applying clip path:
52 gfxContextMatrixAutoSaveRestore
autoRestoreTransform(&aContext
);
54 RefPtr
<Path
> clipPath
;
56 if (singleClipPathChild
) {
57 SVGGeometryFrame
* pathFrame
= do_QueryFrame(singleClipPathChild
);
58 if (pathFrame
&& pathFrame
->StyleVisibility()->IsVisible()) {
59 SVGGeometryElement
* pathElement
=
60 static_cast<SVGGeometryElement
*>(pathFrame
->GetContent());
62 gfxMatrix toChildsUserSpace
=
63 SVGUtils::GetTransformMatrixInUserSpace(pathFrame
) *
64 (GetClipPathTransform(aClippedFrame
) * aMatrix
);
66 gfxMatrix newMatrix
= aContext
.CurrentMatrixDouble()
67 .PreMultiply(toChildsUserSpace
)
69 if (!newMatrix
.IsSingular()) {
70 aContext
.SetMatrixDouble(newMatrix
);
72 SVGUtils::ToFillRule(pathFrame
->StyleSVG()->mClipRule
);
73 clipPath
= pathElement
->GetOrBuildPath(drawTarget
, clipRule
);
79 aContext
.Clip(clipPath
);
81 // The spec says clip away everything if we have no children or the
82 // clipping path otherwise can't be resolved:
83 aContext
.Clip(Rect());
87 static void ComposeExtraMask(DrawTarget
* aTarget
, SourceSurface
* aExtraMask
) {
88 MOZ_ASSERT(aExtraMask
);
90 Matrix origin
= aTarget
->GetTransform();
91 aTarget
->SetTransform(Matrix());
92 aTarget
->MaskSurface(ColorPattern(DeviceColor(0.0, 0.0, 0.0, 1.0)),
93 aExtraMask
, Point(0, 0),
94 DrawOptions(1.0, CompositionOp::OP_IN
));
95 aTarget
->SetTransform(origin
);
98 void SVGClipPathFrame::PaintChildren(gfxContext
& aMaskContext
,
99 nsIFrame
* aClippedFrame
,
100 const gfxMatrix
& aMatrix
) {
101 // Check if this clipPath is itself clipped by another clipPath:
102 SVGClipPathFrame
* clipPathThatClipsClipPath
;
103 // XXX check return value?
104 SVGObserverUtils::GetAndObserveClipPath(this, &clipPathThatClipsClipPath
);
105 SVGUtils::MaskUsage maskUsage
= SVGUtils::DetermineMaskUsage(this, true);
107 gfxGroupForBlendAutoSaveRestore
autoGroupForBlend(&aMaskContext
);
108 if (maskUsage
.ShouldApplyClipPath()) {
109 clipPathThatClipsClipPath
->ApplyClipPath(aMaskContext
, aClippedFrame
,
111 } else if (maskUsage
.ShouldGenerateClipMaskLayer()) {
112 RefPtr
<SourceSurface
> maskSurface
= clipPathThatClipsClipPath
->GetClipMask(
113 aMaskContext
, aClippedFrame
, aMatrix
);
114 // We want the mask to be untransformed so use the inverse of the current
115 // transform as the maskTransform to compensate.
116 Matrix maskTransform
= aMaskContext
.CurrentMatrix();
117 maskTransform
.Invert();
118 autoGroupForBlend
.PushGroupForBlendBack(gfxContentType::ALPHA
, 1.0f
,
119 maskSurface
, maskTransform
);
122 // Paint our children into the mask:
123 for (auto* kid
: mFrames
) {
124 PaintFrameIntoMask(kid
, aClippedFrame
, aMaskContext
);
127 if (maskUsage
.ShouldApplyClipPath()) {
128 aMaskContext
.PopClip();
132 void SVGClipPathFrame::PaintClipMask(gfxContext
& aMaskContext
,
133 nsIFrame
* aClippedFrame
,
134 const gfxMatrix
& aMatrix
,
135 SourceSurface
* aExtraMask
) {
136 static int16_t sRefChainLengthCounter
= AutoReferenceChainGuard::noChain
;
138 // A clipPath can reference another clipPath, creating a chain of clipPaths
139 // that must all be applied. We re-enter this method for each clipPath in a
140 // chain, so we need to protect against reference chain related crashes etc.:
141 AutoReferenceChainGuard
refChainGuard(this, &mIsBeingProcessed
,
142 &sRefChainLengthCounter
);
143 if (MOZ_UNLIKELY(!refChainGuard
.Reference())) {
144 return; // Break reference chain
150 DrawTarget
* maskDT
= aMaskContext
.GetDrawTarget();
151 MOZ_ASSERT(maskDT
->GetFormat() == SurfaceFormat::A8
);
153 // Paint this clipPath's contents into aMaskDT:
154 // We need to set mMatrixForChildren here so that under the PaintSVG calls
155 // on our children (below) our GetCanvasTM() method will return the correct
157 mMatrixForChildren
= GetClipPathTransform(aClippedFrame
) * aMatrix
;
159 PaintChildren(aMaskContext
, aClippedFrame
, aMatrix
);
162 ComposeExtraMask(maskDT
, aExtraMask
);
166 void SVGClipPathFrame::PaintFrameIntoMask(nsIFrame
* aFrame
,
167 nsIFrame
* aClippedFrame
,
168 gfxContext
& aTarget
) {
169 ISVGDisplayableFrame
* frame
= do_QueryFrame(aFrame
);
174 // The CTM of each frame referencing us can be different.
175 frame
->NotifySVGChanged(ISVGDisplayableFrame::TRANSFORM_CHANGED
);
177 // Children of this clipPath may themselves be clipped.
178 SVGClipPathFrame
* clipPathThatClipsChild
;
179 // XXX check return value?
180 if (SVGObserverUtils::GetAndObserveClipPath(aFrame
,
181 &clipPathThatClipsChild
) ==
182 SVGObserverUtils::eHasRefsSomeInvalid
) {
186 SVGUtils::MaskUsage maskUsage
= SVGUtils::DetermineMaskUsage(aFrame
, true);
187 gfxGroupForBlendAutoSaveRestore
autoGroupForBlend(&aTarget
);
188 if (maskUsage
.ShouldApplyClipPath()) {
189 clipPathThatClipsChild
->ApplyClipPath(
190 aTarget
, aClippedFrame
,
191 SVGUtils::GetTransformMatrixInUserSpace(aFrame
) * mMatrixForChildren
);
192 } else if (maskUsage
.ShouldGenerateClipMaskLayer()) {
193 RefPtr
<SourceSurface
> maskSurface
= clipPathThatClipsChild
->GetClipMask(
194 aTarget
, aClippedFrame
,
195 SVGUtils::GetTransformMatrixInUserSpace(aFrame
) * mMatrixForChildren
);
197 // We want the mask to be untransformed so use the inverse of the current
198 // transform as the maskTransform to compensate.
199 Matrix maskTransform
= aTarget
.CurrentMatrix();
200 maskTransform
.Invert();
201 autoGroupForBlend
.PushGroupForBlendBack(gfxContentType::ALPHA
, 1.0f
,
202 maskSurface
, maskTransform
);
205 gfxMatrix toChildsUserSpace
= mMatrixForChildren
;
206 nsIFrame
* child
= do_QueryFrame(frame
);
207 nsIContent
* childContent
= child
->GetContent();
208 if (childContent
->IsSVGElement()) {
210 SVGUtils::GetTransformMatrixInUserSpace(child
) * mMatrixForChildren
;
213 // clipPath does not result in any image rendering, so we just use a dummy
214 // imgDrawingParams instead of requiring our caller to pass one.
215 image::imgDrawingParams imgParams
;
217 // Our children have NS_STATE_SVG_CLIPPATH_CHILD set on them, and
218 // SVGGeometryFrame::Render checks for that state bit and paints
219 // only the geometry (opaque black) if set.
220 frame
->PaintSVG(aTarget
, toChildsUserSpace
, imgParams
);
222 if (maskUsage
.ShouldApplyClipPath()) {
227 already_AddRefed
<SourceSurface
> SVGClipPathFrame::GetClipMask(
228 gfxContext
& aReferenceContext
, nsIFrame
* aClippedFrame
,
229 const gfxMatrix
& aMatrix
, SourceSurface
* aExtraMask
) {
230 RefPtr
<DrawTarget
> maskDT
=
231 aReferenceContext
.GetDrawTarget()->CreateClippedDrawTarget(
232 Rect(), SurfaceFormat::A8
);
237 gfxContext
maskContext(maskDT
, /* aPreserveTransform */ true);
238 PaintClipMask(maskContext
, aClippedFrame
, aMatrix
, aExtraMask
);
240 RefPtr
<SourceSurface
> surface
= maskDT
->Snapshot();
241 return surface
.forget();
244 bool SVGClipPathFrame::PointIsInsideClipPath(nsIFrame
* aClippedFrame
,
245 const gfxPoint
& aPoint
) {
246 static int16_t sRefChainLengthCounter
= AutoReferenceChainGuard::noChain
;
248 // A clipPath can reference another clipPath, creating a chain of clipPaths
249 // that must all be applied. We re-enter this method for each clipPath in a
250 // chain, so we need to protect against reference chain related crashes etc.:
251 AutoReferenceChainGuard
refChainGuard(this, &mIsBeingProcessed
,
252 &sRefChainLengthCounter
);
253 if (MOZ_UNLIKELY(!refChainGuard
.Reference())) {
254 return false; // Break reference chain
260 gfxMatrix matrix
= GetClipPathTransform(aClippedFrame
);
261 if (!matrix
.Invert()) {
264 gfxPoint point
= matrix
.TransformPoint(aPoint
);
266 // clipPath elements can themselves be clipped by a different clip path. In
267 // that case the other clip path further clips away the element that is being
268 // clipped by the original clipPath. If this clipPath is being clipped by a
269 // different clip path we need to check if it prevents the original element
270 // from receiving events at aPoint:
271 SVGClipPathFrame
* clipPathFrame
;
272 // XXX check return value?
273 SVGObserverUtils::GetAndObserveClipPath(this, &clipPathFrame
);
275 !clipPathFrame
->PointIsInsideClipPath(aClippedFrame
, aPoint
)) {
279 for (auto* kid
: mFrames
) {
280 ISVGDisplayableFrame
* SVGFrame
= do_QueryFrame(kid
);
282 gfxPoint pointForChild
= point
;
284 gfxMatrix m
= SVGUtils::GetTransformMatrixInUserSpace(kid
);
285 if (!m
.IsIdentity()) {
289 pointForChild
= m
.TransformPoint(point
);
291 if (SVGFrame
->GetFrameForPoint(pointForChild
)) {
300 bool SVGClipPathFrame::IsTrivial(nsIFrame
** aSingleChild
) {
301 // If the clip path is clipped then it's non-trivial
302 if (SVGObserverUtils::GetAndObserveClipPath(this, nullptr) ==
303 SVGObserverUtils::eHasRefsAllValid
) {
308 *aSingleChild
= nullptr;
311 nsIFrame
* foundChild
= nullptr;
312 for (auto* kid
: mFrames
) {
313 ISVGDisplayableFrame
* svgChild
= do_QueryFrame(kid
);
317 // We consider a non-trivial clipPath to be one containing
318 // either more than one svg child and/or a svg container
319 if (foundChild
|| svgChild
->IsDisplayContainer()) {
323 // or where the child is itself clipped
324 if (SVGObserverUtils::GetAndObserveClipPath(kid
, nullptr) ==
325 SVGObserverUtils::eHasRefsAllValid
) {
332 *aSingleChild
= foundChild
;
337 bool SVGClipPathFrame::IsValid() {
338 if (SVGObserverUtils::GetAndObserveClipPath(this, nullptr) ==
339 SVGObserverUtils::eHasRefsSomeInvalid
) {
343 for (auto* kid
: mFrames
) {
344 LayoutFrameType kidType
= kid
->Type();
346 if (kidType
== LayoutFrameType::SVGUse
) {
347 for (nsIFrame
* grandKid
: kid
->PrincipalChildList()) {
348 LayoutFrameType grandKidType
= grandKid
->Type();
350 if (grandKidType
!= LayoutFrameType::SVGGeometry
&&
351 grandKidType
!= LayoutFrameType::SVGText
) {
358 if (kidType
!= LayoutFrameType::SVGGeometry
&&
359 kidType
!= LayoutFrameType::SVGText
) {
367 nsresult
SVGClipPathFrame::AttributeChanged(int32_t aNameSpaceID
,
370 if (aNameSpaceID
== kNameSpaceID_None
) {
371 if (aAttribute
== nsGkAtoms::transform
) {
372 SVGObserverUtils::InvalidateRenderingObservers(this);
373 SVGUtils::NotifyChildrenOfSVGChange(
374 this, ISVGDisplayableFrame::TRANSFORM_CHANGED
);
376 if (aAttribute
== nsGkAtoms::clipPathUnits
) {
377 SVGObserverUtils::InvalidateRenderingObservers(this);
381 return SVGContainerFrame::AttributeChanged(aNameSpaceID
, aAttribute
,
386 void SVGClipPathFrame::Init(nsIContent
* aContent
, nsContainerFrame
* aParent
,
387 nsIFrame
* aPrevInFlow
) {
388 NS_ASSERTION(aContent
->IsSVGElement(nsGkAtoms::clipPath
),
389 "Content is not an SVG clipPath!");
391 SVGContainerFrame::Init(aContent
, aParent
, aPrevInFlow
);
395 gfxMatrix
SVGClipPathFrame::GetCanvasTM() { return mMatrixForChildren
; }
397 gfxMatrix
SVGClipPathFrame::GetClipPathTransform(nsIFrame
* aClippedFrame
) {
398 SVGClipPathElement
* content
= static_cast<SVGClipPathElement
*>(GetContent());
400 gfxMatrix tm
= content
->PrependLocalTransformsTo({}, eChildToUserSpace
) *
401 SVGUtils::GetTransformMatrixInUserSpace(this);
403 SVGAnimatedEnumeration
* clipPathUnits
=
404 &content
->mEnumAttributes
[SVGClipPathElement::CLIPPATHUNITS
];
406 uint32_t flags
= SVGUtils::eBBoxIncludeFillGeometry
|
407 (aClippedFrame
->StyleBorder()->mBoxDecorationBreak
==
408 StyleBoxDecorationBreak::Clone
409 ? SVGUtils::eIncludeOnlyCurrentFrameForNonSVGElement
412 return SVGUtils::AdjustMatrixForUnits(tm
, clipPathUnits
, aClippedFrame
,
416 SVGBBox
SVGClipPathFrame::GetBBoxForClipPathFrame(const SVGBBox
& aBBox
,
417 const gfxMatrix
& aMatrix
,
419 SVGClipPathFrame
* clipPathThatClipsClipPath
;
420 if (SVGObserverUtils::GetAndObserveClipPath(this,
421 &clipPathThatClipsClipPath
) ==
422 SVGObserverUtils::eHasRefsSomeInvalid
) {
426 nsIContent
* node
= GetContent()->GetFirstChild();
428 for (; node
; node
= node
->GetNextSibling()) {
429 if (nsIFrame
* frame
= node
->GetPrimaryFrame()) {
430 ISVGDisplayableFrame
* svg
= do_QueryFrame(frame
);
433 SVGUtils::GetTransformMatrixInUserSpace(frame
) * aMatrix
;
434 SVGBBox tmpBBox
= svg
->GetBBoxContribution(
435 gfx::ToMatrix(matrix
), SVGUtils::eBBoxIncludeFillGeometry
);
436 SVGClipPathFrame
* clipPathFrame
;
437 if (SVGObserverUtils::GetAndObserveClipPath(frame
, &clipPathFrame
) !=
438 SVGObserverUtils::eHasRefsSomeInvalid
&&
441 clipPathFrame
->GetBBoxForClipPathFrame(tmpBBox
, aMatrix
, aFlags
);
443 if (!(aFlags
& SVGUtils::eDoNotClipToBBoxOfContentInsideClipPath
)) {
444 tmpBBox
.Intersect(aBBox
);
446 unionBBox
.UnionEdges(tmpBBox
);
451 if (clipPathThatClipsClipPath
) {
452 unionBBox
.Intersect(clipPathThatClipsClipPath
->GetBBoxForClipPathFrame(
453 aBBox
, aMatrix
, aFlags
));
458 bool SVGClipPathFrame::IsSVGTransformed(Matrix
* aOwnTransforms
,
459 Matrix
* aFromParentTransforms
) const {
460 const auto* e
= static_cast<SVGElement
const*>(GetContent());
461 Matrix m
= ToMatrix(e
->PrependLocalTransformsTo({}, eUserSpaceToParent
));
463 if (m
.IsIdentity()) {
467 if (aOwnTransforms
) {
474 } // namespace mozilla