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 MOZ_ASSERT(IsTrivial(), "Caller needs to use GetClipMask");
44 const DrawTarget
* drawTarget
= aContext
.GetDrawTarget();
46 // No need for AutoReferenceChainGuard since simple clip paths by definition
47 // don't reference another clip path.
49 // Restore current transform after applying clip path:
50 gfxContextMatrixAutoSaveRestore
autoRestore(&aContext
);
52 RefPtr
<Path
> clipPath
;
54 ISVGDisplayableFrame
* singleClipPathChild
= nullptr;
55 IsTrivial(&singleClipPathChild
);
57 if (singleClipPathChild
) {
58 SVGGeometryFrame
* pathFrame
= do_QueryFrame(singleClipPathChild
);
59 if (pathFrame
&& pathFrame
->StyleVisibility()->IsVisible()) {
60 SVGGeometryElement
* pathElement
=
61 static_cast<SVGGeometryElement
*>(pathFrame
->GetContent());
63 gfxMatrix toChildsUserSpace
=
64 SVGUtils::GetTransformMatrixInUserSpace(pathFrame
) *
65 (GetClipPathTransform(aClippedFrame
) * aMatrix
);
67 gfxMatrix newMatrix
= aContext
.CurrentMatrixDouble()
68 .PreMultiply(toChildsUserSpace
)
70 if (!newMatrix
.IsSingular()) {
71 aContext
.SetMatrixDouble(newMatrix
);
73 SVGUtils::ToFillRule(pathFrame
->StyleSVG()->mClipRule
);
74 clipPath
= pathElement
->GetOrBuildPath(drawTarget
, clipRule
);
80 aContext
.Clip(clipPath
);
82 // The spec says clip away everything if we have no children or the
83 // clipping path otherwise can't be resolved:
84 aContext
.Clip(Rect());
88 static void ComposeExtraMask(DrawTarget
* aTarget
, SourceSurface
* aExtraMask
) {
89 MOZ_ASSERT(aExtraMask
);
91 Matrix origin
= aTarget
->GetTransform();
92 aTarget
->SetTransform(Matrix());
93 aTarget
->MaskSurface(ColorPattern(DeviceColor(0.0, 0.0, 0.0, 1.0)),
94 aExtraMask
, Point(0, 0),
95 DrawOptions(1.0, CompositionOp::OP_IN
));
96 aTarget
->SetTransform(origin
);
99 void SVGClipPathFrame::PaintClipMask(gfxContext
& aMaskContext
,
100 nsIFrame
* aClippedFrame
,
101 const gfxMatrix
& aMatrix
,
102 SourceSurface
* aExtraMask
) {
103 static int16_t sRefChainLengthCounter
= AutoReferenceChainGuard::noChain
;
105 // A clipPath can reference another clipPath, creating a chain of clipPaths
106 // that must all be applied. We re-enter this method for each clipPath in a
107 // chain, so we need to protect against reference chain related crashes etc.:
108 AutoReferenceChainGuard
refChainGuard(this, &mIsBeingProcessed
,
109 &sRefChainLengthCounter
);
110 if (MOZ_UNLIKELY(!refChainGuard
.Reference())) {
111 return; // Break reference chain
114 DrawTarget
* maskDT
= aMaskContext
.GetDrawTarget();
115 MOZ_ASSERT(maskDT
->GetFormat() == SurfaceFormat::A8
);
117 // Paint this clipPath's contents into aMaskDT:
118 // We need to set mMatrixForChildren here so that under the PaintSVG calls
119 // on our children (below) our GetCanvasTM() method will return the correct
121 mMatrixForChildren
= GetClipPathTransform(aClippedFrame
) * aMatrix
;
123 // Check if this clipPath is itself clipped by another clipPath:
124 SVGClipPathFrame
* clipPathThatClipsClipPath
;
125 // XXX check return value?
126 SVGObserverUtils::GetAndObserveClipPath(this, &clipPathThatClipsClipPath
);
127 SVGUtils::MaskUsage maskUsage
;
128 SVGUtils::DetermineMaskUsage(this, true, maskUsage
);
130 if (maskUsage
.shouldApplyClipPath
) {
131 clipPathThatClipsClipPath
->ApplyClipPath(aMaskContext
, aClippedFrame
,
133 } else if (maskUsage
.shouldGenerateClipMaskLayer
) {
134 RefPtr
<SourceSurface
> maskSurface
= clipPathThatClipsClipPath
->GetClipMask(
135 aMaskContext
, aClippedFrame
, aMatrix
);
136 // We want the mask to be untransformed so use the inverse of the current
137 // transform as the maskTransform to compensate.
138 Matrix maskTransform
= aMaskContext
.CurrentMatrix();
139 maskTransform
.Invert();
140 aMaskContext
.PushGroupForBlendBack(gfxContentType::ALPHA
, 1.0, maskSurface
,
142 // The corresponding PopGroupAndBlend call below will mask the
143 // blend using |maskSurface|.
146 // Paint our children into the mask:
147 for (nsIFrame
* kid
= mFrames
.FirstChild(); kid
; kid
= kid
->GetNextSibling()) {
148 PaintFrameIntoMask(kid
, aClippedFrame
, aMaskContext
);
151 if (maskUsage
.shouldGenerateClipMaskLayer
) {
152 aMaskContext
.PopGroupAndBlend();
153 } else if (maskUsage
.shouldApplyClipPath
) {
154 aMaskContext
.PopClip();
158 ComposeExtraMask(maskDT
, aExtraMask
);
162 void SVGClipPathFrame::PaintFrameIntoMask(nsIFrame
* aFrame
,
163 nsIFrame
* aClippedFrame
,
164 gfxContext
& aTarget
) {
165 ISVGDisplayableFrame
* frame
= do_QueryFrame(aFrame
);
170 // The CTM of each frame referencing us can be different.
171 frame
->NotifySVGChanged(ISVGDisplayableFrame::TRANSFORM_CHANGED
);
173 // Children of this clipPath may themselves be clipped.
174 SVGClipPathFrame
* clipPathThatClipsChild
;
175 // XXX check return value?
176 if (SVGObserverUtils::GetAndObserveClipPath(aFrame
,
177 &clipPathThatClipsChild
) ==
178 SVGObserverUtils::eHasRefsSomeInvalid
) {
182 SVGUtils::MaskUsage maskUsage
;
183 SVGUtils::DetermineMaskUsage(aFrame
, true, maskUsage
);
184 if (maskUsage
.shouldApplyClipPath
) {
185 clipPathThatClipsChild
->ApplyClipPath(aTarget
, aClippedFrame
,
187 } else if (maskUsage
.shouldGenerateClipMaskLayer
) {
188 RefPtr
<SourceSurface
> maskSurface
= clipPathThatClipsChild
->GetClipMask(
189 aTarget
, aClippedFrame
, mMatrixForChildren
);
191 // We want the mask to be untransformed so use the inverse of the current
192 // transform as the maskTransform to compensate.
193 Matrix maskTransform
= aTarget
.CurrentMatrix();
194 maskTransform
.Invert();
195 aTarget
.PushGroupForBlendBack(gfxContentType::ALPHA
, 1.0, maskSurface
,
197 // The corresponding PopGroupAndBlend call below will mask the
198 // blend using |maskSurface|.
201 gfxMatrix toChildsUserSpace
= mMatrixForChildren
;
202 nsIFrame
* child
= do_QueryFrame(frame
);
203 nsIContent
* childContent
= child
->GetContent();
204 if (childContent
->IsSVGElement()) {
206 SVGUtils::GetTransformMatrixInUserSpace(child
) * mMatrixForChildren
;
209 // clipPath does not result in any image rendering, so we just use a dummy
210 // imgDrawingParams instead of requiring our caller to pass one.
211 image::imgDrawingParams imgParams
;
213 // Our children have NS_STATE_SVG_CLIPPATH_CHILD set on them, and
214 // SVGGeometryFrame::Render checks for that state bit and paints
215 // only the geometry (opaque black) if set.
216 frame
->PaintSVG(aTarget
, toChildsUserSpace
, imgParams
);
218 if (maskUsage
.shouldGenerateClipMaskLayer
) {
219 aTarget
.PopGroupAndBlend();
220 } else if (maskUsage
.shouldApplyClipPath
) {
225 already_AddRefed
<SourceSurface
> SVGClipPathFrame::GetClipMask(
226 gfxContext
& aReferenceContext
, nsIFrame
* aClippedFrame
,
227 const gfxMatrix
& aMatrix
, SourceSurface
* aExtraMask
) {
228 RefPtr
<DrawTarget
> maskDT
=
229 aReferenceContext
.GetDrawTarget()->CreateClippedDrawTarget(
230 Rect(), SurfaceFormat::A8
);
235 RefPtr
<gfxContext
> maskContext
=
236 gfxContext::CreatePreservingTransformOrNull(maskDT
);
238 gfxCriticalError() << "SVGClipPath context problem " << gfx::hexa(maskDT
);
242 PaintClipMask(*maskContext
, aClippedFrame
, aMatrix
, aExtraMask
);
244 RefPtr
<SourceSurface
> surface
= maskDT
->Snapshot();
245 return surface
.forget();
248 bool SVGClipPathFrame::PointIsInsideClipPath(nsIFrame
* aClippedFrame
,
249 const gfxPoint
& aPoint
) {
250 static int16_t sRefChainLengthCounter
= AutoReferenceChainGuard::noChain
;
252 // A clipPath can reference another clipPath, creating a chain of clipPaths
253 // that must all be applied. We re-enter this method for each clipPath in a
254 // chain, so we need to protect against reference chain related crashes etc.:
255 AutoReferenceChainGuard
refChainGuard(this, &mIsBeingProcessed
,
256 &sRefChainLengthCounter
);
257 if (MOZ_UNLIKELY(!refChainGuard
.Reference())) {
258 return false; // Break reference chain
261 gfxMatrix matrix
= GetClipPathTransform(aClippedFrame
);
262 if (!matrix
.Invert()) {
265 gfxPoint point
= matrix
.TransformPoint(aPoint
);
267 // clipPath elements can themselves be clipped by a different clip path. In
268 // that case the other clip path further clips away the element that is being
269 // clipped by the original clipPath. If this clipPath is being clipped by a
270 // different clip path we need to check if it prevents the original element
271 // from receiving events at aPoint:
272 SVGClipPathFrame
* clipPathFrame
;
273 // XXX check return value?
274 SVGObserverUtils::GetAndObserveClipPath(this, &clipPathFrame
);
276 !clipPathFrame
->PointIsInsideClipPath(aClippedFrame
, aPoint
)) {
280 for (nsIFrame
* kid
= mFrames
.FirstChild(); kid
; kid
= kid
->GetNextSibling()) {
281 ISVGDisplayableFrame
* SVGFrame
= do_QueryFrame(kid
);
283 gfxPoint pointForChild
= point
;
285 gfxMatrix m
= SVGUtils::GetTransformMatrixInUserSpace(kid
);
286 if (!m
.IsIdentity()) {
290 pointForChild
= m
.TransformPoint(point
);
292 if (SVGFrame
->GetFrameForPoint(pointForChild
)) {
301 bool SVGClipPathFrame::IsTrivial(ISVGDisplayableFrame
** aSingleChild
) {
302 // If the clip path is clipped then it's non-trivial
303 if (SVGObserverUtils::GetAndObserveClipPath(this, nullptr) ==
304 SVGObserverUtils::eHasRefsAllValid
) {
309 *aSingleChild
= nullptr;
312 ISVGDisplayableFrame
* foundChild
= nullptr;
314 for (nsIFrame
* kid
= mFrames
.FirstChild(); kid
; kid
= kid
->GetNextSibling()) {
315 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
) {
329 foundChild
= svgChild
;
333 *aSingleChild
= foundChild
;
338 bool SVGClipPathFrame::IsValid() {
339 static int16_t sRefChainLengthCounter
= AutoReferenceChainGuard::noChain
;
341 // A clipPath can reference another clipPath, creating a chain of clipPaths
342 // that must all be applied. We re-enter this method for each clipPath in a
343 // chain, so we need to protect against reference chain related crashes etc.:
344 AutoReferenceChainGuard
refChainGuard(this, &mIsBeingProcessed
,
345 &sRefChainLengthCounter
);
346 if (MOZ_UNLIKELY(!refChainGuard
.Reference())) {
347 return false; // Break reference chain
350 if (SVGObserverUtils::GetAndObserveClipPath(this, nullptr) ==
351 SVGObserverUtils::eHasRefsSomeInvalid
) {
355 for (nsIFrame
* kid
= mFrames
.FirstChild(); kid
; kid
= kid
->GetNextSibling()) {
356 LayoutFrameType kidType
= kid
->Type();
358 if (kidType
== LayoutFrameType::SVGUse
) {
359 for (nsIFrame
* grandKid
: kid
->PrincipalChildList()) {
360 LayoutFrameType grandKidType
= grandKid
->Type();
362 if (grandKidType
!= LayoutFrameType::SVGGeometry
&&
363 grandKidType
!= LayoutFrameType::SVGText
) {
370 if (kidType
!= LayoutFrameType::SVGGeometry
&&
371 kidType
!= LayoutFrameType::SVGText
) {
379 nsresult
SVGClipPathFrame::AttributeChanged(int32_t aNameSpaceID
,
382 if (aNameSpaceID
== kNameSpaceID_None
) {
383 if (aAttribute
== nsGkAtoms::transform
) {
384 SVGObserverUtils::InvalidateDirectRenderingObservers(this);
385 SVGUtils::NotifyChildrenOfSVGChange(
386 this, ISVGDisplayableFrame::TRANSFORM_CHANGED
);
388 if (aAttribute
== nsGkAtoms::clipPathUnits
) {
389 SVGObserverUtils::InvalidateDirectRenderingObservers(this);
393 return SVGContainerFrame::AttributeChanged(aNameSpaceID
, aAttribute
,
397 void SVGClipPathFrame::Init(nsIContent
* aContent
, nsContainerFrame
* aParent
,
398 nsIFrame
* aPrevInFlow
) {
399 NS_ASSERTION(aContent
->IsSVGElement(nsGkAtoms::clipPath
),
400 "Content is not an SVG clipPath!");
402 AddStateBits(NS_STATE_SVG_CLIPPATH_CHILD
);
403 AddStateBits(NS_FRAME_MAY_BE_TRANSFORMED
);
404 SVGContainerFrame::Init(aContent
, aParent
, aPrevInFlow
);
407 gfxMatrix
SVGClipPathFrame::GetCanvasTM() { return mMatrixForChildren
; }
409 gfxMatrix
SVGClipPathFrame::GetClipPathTransform(nsIFrame
* aClippedFrame
) {
410 SVGClipPathElement
* content
= static_cast<SVGClipPathElement
*>(GetContent());
412 gfxMatrix tm
= content
->PrependLocalTransformsTo({}, eChildToUserSpace
) *
413 SVGUtils::GetTransformMatrixInUserSpace(this);
415 SVGAnimatedEnumeration
* clipPathUnits
=
416 &content
->mEnumAttributes
[SVGClipPathElement::CLIPPATHUNITS
];
418 uint32_t flags
= SVGUtils::eBBoxIncludeFillGeometry
|
419 (aClippedFrame
->StyleBorder()->mBoxDecorationBreak
==
420 StyleBoxDecorationBreak::Clone
421 ? SVGUtils::eIncludeOnlyCurrentFrameForNonSVGElement
424 return SVGUtils::AdjustMatrixForUnits(tm
, clipPathUnits
, aClippedFrame
,
428 SVGBBox
SVGClipPathFrame::GetBBoxForClipPathFrame(const SVGBBox
& aBBox
,
429 const gfxMatrix
& aMatrix
,
431 SVGClipPathFrame
* clipPathThatClipsClipPath
;
432 if (SVGObserverUtils::GetAndObserveClipPath(this,
433 &clipPathThatClipsClipPath
) ==
434 SVGObserverUtils::eHasRefsSomeInvalid
) {
438 nsIContent
* node
= GetContent()->GetFirstChild();
439 SVGBBox unionBBox
, tmpBBox
;
440 for (; node
; node
= node
->GetNextSibling()) {
441 SVGElement
* svgNode
= static_cast<SVGElement
*>(node
);
442 nsIFrame
* frame
= svgNode
->GetPrimaryFrame();
444 ISVGDisplayableFrame
* svg
= do_QueryFrame(frame
);
447 SVGUtils::GetTransformMatrixInUserSpace(frame
) * aMatrix
;
448 tmpBBox
= svg
->GetBBoxContribution(gfx::ToMatrix(matrix
),
449 SVGUtils::eBBoxIncludeFill
);
450 SVGClipPathFrame
* clipPathFrame
;
451 if (SVGObserverUtils::GetAndObserveClipPath(frame
, &clipPathFrame
) !=
452 SVGObserverUtils::eHasRefsSomeInvalid
&&
455 clipPathFrame
->GetBBoxForClipPathFrame(tmpBBox
, aMatrix
, aFlags
);
457 if (!(aFlags
& SVGUtils::eDoNotClipToBBoxOfContentInsideClipPath
)) {
458 tmpBBox
.Intersect(aBBox
);
460 unionBBox
.UnionEdges(tmpBBox
);
465 if (clipPathThatClipsClipPath
) {
466 tmpBBox
= clipPathThatClipsClipPath
->GetBBoxForClipPathFrame(aBBox
, aMatrix
,
468 unionBBox
.Intersect(tmpBBox
);
473 bool SVGClipPathFrame::IsSVGTransformed(Matrix
* aOwnTransforms
,
474 Matrix
* aFromParentTransforms
) const {
475 const auto* e
= static_cast<SVGElement
const*>(GetContent());
476 Matrix m
= ToMatrix(e
->PrependLocalTransformsTo({}, eUserSpaceToParent
));
478 if (m
.IsIdentity()) {
482 if (aOwnTransforms
) {
489 } // namespace mozilla