|
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ |
|
2 /* This Source Code Form is subject to the terms of the Mozilla Public |
|
3 * License, v. 2.0. If a copy of the MPL was not distributed with this |
|
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
|
5 |
|
6 // Main header first: |
|
7 #include "nsSVGClipPathFrame.h" |
|
8 |
|
9 // Keep others in (case-insensitive) order: |
|
10 #include "gfxContext.h" |
|
11 #include "nsGkAtoms.h" |
|
12 #include "nsRenderingContext.h" |
|
13 #include "mozilla/dom/SVGClipPathElement.h" |
|
14 #include "nsSVGEffects.h" |
|
15 #include "nsSVGUtils.h" |
|
16 |
|
17 using namespace mozilla::dom; |
|
18 |
|
19 //---------------------------------------------------------------------- |
|
20 // Implementation |
|
21 |
|
22 nsIFrame* |
|
23 NS_NewSVGClipPathFrame(nsIPresShell* aPresShell, nsStyleContext* aContext) |
|
24 { |
|
25 return new (aPresShell) nsSVGClipPathFrame(aContext); |
|
26 } |
|
27 |
|
28 NS_IMPL_FRAMEARENA_HELPERS(nsSVGClipPathFrame) |
|
29 |
|
30 nsresult |
|
31 nsSVGClipPathFrame::ClipPaint(nsRenderingContext* aContext, |
|
32 nsIFrame* aParent, |
|
33 const gfxMatrix &aMatrix) |
|
34 { |
|
35 // If the flag is set when we get here, it means this clipPath frame |
|
36 // has already been used painting the current clip, and the document |
|
37 // has a clip reference loop. |
|
38 if (mInUse) { |
|
39 NS_WARNING("Clip loop detected!"); |
|
40 return NS_OK; |
|
41 } |
|
42 AutoClipPathReferencer clipRef(this); |
|
43 |
|
44 mClipParent = aParent; |
|
45 if (mClipParentMatrix) { |
|
46 *mClipParentMatrix = aMatrix; |
|
47 } else { |
|
48 mClipParentMatrix = new gfxMatrix(aMatrix); |
|
49 } |
|
50 |
|
51 gfxContext *gfx = aContext->ThebesContext(); |
|
52 |
|
53 nsISVGChildFrame *singleClipPathChild = nullptr; |
|
54 |
|
55 if (IsTrivial(&singleClipPathChild)) { |
|
56 // Notify our child that it's painting as part of a clipPath, and that |
|
57 // we only require it to draw its path (it should skip filling, etc.): |
|
58 SVGAutoRenderState mode(aContext, SVGAutoRenderState::CLIP); |
|
59 |
|
60 if (!singleClipPathChild) { |
|
61 // We have no children - the spec says clip away everything: |
|
62 gfx->Rectangle(gfxRect()); |
|
63 } else { |
|
64 singleClipPathChild->NotifySVGChanged( |
|
65 nsISVGChildFrame::TRANSFORM_CHANGED); |
|
66 singleClipPathChild->PaintSVG(aContext, nullptr); |
|
67 } |
|
68 gfx->Clip(); |
|
69 gfx->NewPath(); |
|
70 return NS_OK; |
|
71 } |
|
72 |
|
73 // Seems like this is a non-trivial clipPath, so we need to use a clip mask. |
|
74 |
|
75 // Notify our children that they're painting into a clip mask: |
|
76 SVGAutoRenderState mode(aContext, SVGAutoRenderState::CLIP_MASK); |
|
77 |
|
78 // Check if this clipPath is itself clipped by another clipPath: |
|
79 nsSVGClipPathFrame *clipPathFrame = |
|
80 nsSVGEffects::GetEffectProperties(this).GetClipPathFrame(nullptr); |
|
81 bool referencedClipIsTrivial; |
|
82 if (clipPathFrame) { |
|
83 referencedClipIsTrivial = clipPathFrame->IsTrivial(); |
|
84 gfx->Save(); |
|
85 if (referencedClipIsTrivial) { |
|
86 clipPathFrame->ClipPaint(aContext, aParent, aMatrix); |
|
87 } else { |
|
88 gfx->PushGroup(gfxContentType::ALPHA); |
|
89 } |
|
90 } |
|
91 |
|
92 for (nsIFrame* kid = mFrames.FirstChild(); kid; |
|
93 kid = kid->GetNextSibling()) { |
|
94 nsISVGChildFrame* SVGFrame = do_QueryFrame(kid); |
|
95 if (SVGFrame) { |
|
96 // The CTM of each frame referencing us can be different. |
|
97 SVGFrame->NotifySVGChanged(nsISVGChildFrame::TRANSFORM_CHANGED); |
|
98 |
|
99 bool isOK = true; |
|
100 nsSVGClipPathFrame *clipPathFrame = |
|
101 nsSVGEffects::GetEffectProperties(kid).GetClipPathFrame(&isOK); |
|
102 if (!isOK) { |
|
103 continue; |
|
104 } |
|
105 |
|
106 bool isTrivial; |
|
107 |
|
108 if (clipPathFrame) { |
|
109 isTrivial = clipPathFrame->IsTrivial(); |
|
110 gfx->Save(); |
|
111 if (isTrivial) { |
|
112 clipPathFrame->ClipPaint(aContext, aParent, aMatrix); |
|
113 } else { |
|
114 gfx->PushGroup(gfxContentType::ALPHA); |
|
115 } |
|
116 } |
|
117 |
|
118 SVGFrame->PaintSVG(aContext, nullptr); |
|
119 |
|
120 if (clipPathFrame) { |
|
121 if (!isTrivial) { |
|
122 gfx->PopGroupToSource(); |
|
123 |
|
124 nsRefPtr<gfxPattern> clipMaskSurface; |
|
125 gfx->PushGroup(gfxContentType::ALPHA); |
|
126 |
|
127 clipPathFrame->ClipPaint(aContext, aParent, aMatrix); |
|
128 clipMaskSurface = gfx->PopGroup(); |
|
129 |
|
130 if (clipMaskSurface) { |
|
131 gfx->Mask(clipMaskSurface); |
|
132 } |
|
133 } |
|
134 gfx->Restore(); |
|
135 } |
|
136 } |
|
137 } |
|
138 |
|
139 if (clipPathFrame) { |
|
140 if (!referencedClipIsTrivial) { |
|
141 gfx->PopGroupToSource(); |
|
142 |
|
143 nsRefPtr<gfxPattern> clipMaskSurface; |
|
144 gfx->PushGroup(gfxContentType::ALPHA); |
|
145 |
|
146 clipPathFrame->ClipPaint(aContext, aParent, aMatrix); |
|
147 clipMaskSurface = gfx->PopGroup(); |
|
148 |
|
149 if (clipMaskSurface) { |
|
150 gfx->Mask(clipMaskSurface); |
|
151 } |
|
152 } |
|
153 gfx->Restore(); |
|
154 } |
|
155 |
|
156 return NS_OK; |
|
157 } |
|
158 |
|
159 bool |
|
160 nsSVGClipPathFrame::ClipHitTest(nsIFrame* aParent, |
|
161 const gfxMatrix &aMatrix, |
|
162 const nsPoint &aPoint) |
|
163 { |
|
164 // If the flag is set when we get here, it means this clipPath frame |
|
165 // has already been used in hit testing against the current clip, |
|
166 // and the document has a clip reference loop. |
|
167 if (mInUse) { |
|
168 NS_WARNING("Clip loop detected!"); |
|
169 return false; |
|
170 } |
|
171 AutoClipPathReferencer clipRef(this); |
|
172 |
|
173 mClipParent = aParent; |
|
174 if (mClipParentMatrix) { |
|
175 *mClipParentMatrix = aMatrix; |
|
176 } else { |
|
177 mClipParentMatrix = new gfxMatrix(aMatrix); |
|
178 } |
|
179 |
|
180 nsSVGClipPathFrame *clipPathFrame = |
|
181 nsSVGEffects::GetEffectProperties(this).GetClipPathFrame(nullptr); |
|
182 if (clipPathFrame && !clipPathFrame->ClipHitTest(aParent, aMatrix, aPoint)) |
|
183 return false; |
|
184 |
|
185 for (nsIFrame* kid = mFrames.FirstChild(); kid; |
|
186 kid = kid->GetNextSibling()) { |
|
187 nsISVGChildFrame* SVGFrame = do_QueryFrame(kid); |
|
188 if (SVGFrame) { |
|
189 // Notify the child frame that we may be working with a |
|
190 // different transform, so it can update its covered region |
|
191 // (used to shortcut hit testing). |
|
192 SVGFrame->NotifySVGChanged(nsISVGChildFrame::TRANSFORM_CHANGED); |
|
193 |
|
194 if (SVGFrame->GetFrameForPoint(aPoint)) |
|
195 return true; |
|
196 } |
|
197 } |
|
198 return false; |
|
199 } |
|
200 |
|
201 bool |
|
202 nsSVGClipPathFrame::IsTrivial(nsISVGChildFrame **aSingleChild) |
|
203 { |
|
204 // If the clip path is clipped then it's non-trivial |
|
205 if (nsSVGEffects::GetEffectProperties(this).GetClipPathFrame(nullptr)) |
|
206 return false; |
|
207 |
|
208 if (aSingleChild) { |
|
209 *aSingleChild = nullptr; |
|
210 } |
|
211 |
|
212 nsISVGChildFrame *foundChild = nullptr; |
|
213 |
|
214 for (nsIFrame* kid = mFrames.FirstChild(); kid; |
|
215 kid = kid->GetNextSibling()) { |
|
216 nsISVGChildFrame *svgChild = do_QueryFrame(kid); |
|
217 if (svgChild) { |
|
218 // We consider a non-trivial clipPath to be one containing |
|
219 // either more than one svg child and/or a svg container |
|
220 if (foundChild || svgChild->IsDisplayContainer()) |
|
221 return false; |
|
222 |
|
223 // or where the child is itself clipped |
|
224 if (nsSVGEffects::GetEffectProperties(kid).GetClipPathFrame(nullptr)) |
|
225 return false; |
|
226 |
|
227 foundChild = svgChild; |
|
228 } |
|
229 } |
|
230 if (aSingleChild) { |
|
231 *aSingleChild = foundChild; |
|
232 } |
|
233 return true; |
|
234 } |
|
235 |
|
236 bool |
|
237 nsSVGClipPathFrame::IsValid() |
|
238 { |
|
239 if (mInUse) { |
|
240 NS_WARNING("Clip loop detected!"); |
|
241 return false; |
|
242 } |
|
243 AutoClipPathReferencer clipRef(this); |
|
244 |
|
245 bool isOK = true; |
|
246 nsSVGEffects::GetEffectProperties(this).GetClipPathFrame(&isOK); |
|
247 if (!isOK) { |
|
248 return false; |
|
249 } |
|
250 |
|
251 for (nsIFrame* kid = mFrames.FirstChild(); kid; |
|
252 kid = kid->GetNextSibling()) { |
|
253 |
|
254 nsIAtom *type = kid->GetType(); |
|
255 |
|
256 if (type == nsGkAtoms::svgUseFrame) { |
|
257 for (nsIFrame* grandKid = kid->GetFirstPrincipalChild(); grandKid; |
|
258 grandKid = grandKid->GetNextSibling()) { |
|
259 |
|
260 nsIAtom *type = grandKid->GetType(); |
|
261 |
|
262 if (type != nsGkAtoms::svgPathGeometryFrame && |
|
263 type != nsGkAtoms::svgTextFrame) { |
|
264 return false; |
|
265 } |
|
266 } |
|
267 continue; |
|
268 } |
|
269 if (type != nsGkAtoms::svgPathGeometryFrame && |
|
270 type != nsGkAtoms::svgTextFrame) { |
|
271 return false; |
|
272 } |
|
273 } |
|
274 return true; |
|
275 } |
|
276 |
|
277 nsresult |
|
278 nsSVGClipPathFrame::AttributeChanged(int32_t aNameSpaceID, |
|
279 nsIAtom* aAttribute, |
|
280 int32_t aModType) |
|
281 { |
|
282 if (aNameSpaceID == kNameSpaceID_None) { |
|
283 if (aAttribute == nsGkAtoms::transform) { |
|
284 nsSVGEffects::InvalidateDirectRenderingObservers(this); |
|
285 nsSVGUtils::NotifyChildrenOfSVGChange(this, |
|
286 nsISVGChildFrame::TRANSFORM_CHANGED); |
|
287 } |
|
288 if (aAttribute == nsGkAtoms::clipPathUnits) { |
|
289 nsSVGEffects::InvalidateRenderingObservers(this); |
|
290 } |
|
291 } |
|
292 |
|
293 return nsSVGClipPathFrameBase::AttributeChanged(aNameSpaceID, |
|
294 aAttribute, aModType); |
|
295 } |
|
296 |
|
297 void |
|
298 nsSVGClipPathFrame::Init(nsIContent* aContent, |
|
299 nsIFrame* aParent, |
|
300 nsIFrame* aPrevInFlow) |
|
301 { |
|
302 NS_ASSERTION(aContent->IsSVG(nsGkAtoms::clipPath), |
|
303 "Content is not an SVG clipPath!"); |
|
304 |
|
305 AddStateBits(NS_STATE_SVG_CLIPPATH_CHILD); |
|
306 nsSVGClipPathFrameBase::Init(aContent, aParent, aPrevInFlow); |
|
307 } |
|
308 |
|
309 nsIAtom * |
|
310 nsSVGClipPathFrame::GetType() const |
|
311 { |
|
312 return nsGkAtoms::svgClipPathFrame; |
|
313 } |
|
314 |
|
315 gfxMatrix |
|
316 nsSVGClipPathFrame::GetCanvasTM(uint32_t aFor, nsIFrame* aTransformRoot) |
|
317 { |
|
318 SVGClipPathElement *content = static_cast<SVGClipPathElement*>(mContent); |
|
319 |
|
320 gfxMatrix tm = |
|
321 content->PrependLocalTransformsTo(mClipParentMatrix ? |
|
322 *mClipParentMatrix : gfxMatrix()); |
|
323 |
|
324 return nsSVGUtils::AdjustMatrixForUnits(tm, |
|
325 &content->mEnumAttributes[SVGClipPathElement::CLIPPATHUNITS], |
|
326 mClipParent); |
|
327 } |