Line data Source code
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 "AutoReferenceChainGuard.h"
11 : #include "DrawResult.h"
12 : #include "gfxContext.h"
13 : #include "mozilla/dom/SVGClipPathElement.h"
14 : #include "nsGkAtoms.h"
15 : #include "nsSVGEffects.h"
16 : #include "SVGGeometryElement.h"
17 : #include "SVGGeometryFrame.h"
18 : #include "nsSVGUtils.h"
19 :
20 : using namespace mozilla;
21 : using namespace mozilla::dom;
22 : using namespace mozilla::gfx;
23 : using namespace mozilla::image;
24 :
25 : //----------------------------------------------------------------------
26 : // Implementation
27 :
28 : nsIFrame*
29 6 : NS_NewSVGClipPathFrame(nsIPresShell* aPresShell, nsStyleContext* aContext)
30 : {
31 6 : return new (aPresShell) nsSVGClipPathFrame(aContext);
32 : }
33 :
34 6 : NS_IMPL_FRAMEARENA_HELPERS(nsSVGClipPathFrame)
35 :
36 : void
37 2 : nsSVGClipPathFrame::ApplyClipPath(gfxContext& aContext,
38 : nsIFrame* aClippedFrame,
39 : const gfxMatrix& aMatrix)
40 : {
41 2 : MOZ_ASSERT(IsTrivial(), "Caller needs to use GetClipMask");
42 :
43 2 : DrawTarget& aDrawTarget = *aContext.GetDrawTarget();
44 :
45 : // No need for AutoReferenceChainGuard since simple clip paths by definition
46 : // don't reference another clip path.
47 :
48 : // Restore current transform after applying clip path:
49 4 : gfxContextMatrixAutoSaveRestore autoRestore(&aContext);
50 :
51 4 : RefPtr<Path> clipPath;
52 :
53 2 : nsSVGDisplayableFrame* singleClipPathChild = nullptr;
54 2 : IsTrivial(&singleClipPathChild);
55 :
56 2 : if (singleClipPathChild) {
57 2 : SVGGeometryFrame* pathFrame = do_QueryFrame(singleClipPathChild);
58 2 : if (pathFrame) {
59 : SVGGeometryElement* pathElement =
60 2 : static_cast<SVGGeometryElement*>(pathFrame->GetContent());
61 : gfxMatrix toChildsUserSpace = pathElement->
62 4 : PrependLocalTransformsTo(GetClipPathTransform(aClippedFrame) * aMatrix,
63 4 : eUserSpaceToParent);
64 : gfxMatrix newMatrix =
65 2 : aContext.CurrentMatrix().PreMultiply(toChildsUserSpace).NudgeToIntegers();
66 2 : if (!newMatrix.IsSingular()) {
67 2 : aContext.SetMatrix(newMatrix);
68 : FillRule clipRule =
69 2 : nsSVGUtils::ToFillRule(pathFrame->StyleSVG()->mClipRule);
70 2 : clipPath = pathElement->GetOrBuildPath(aDrawTarget, clipRule);
71 : }
72 : }
73 : }
74 :
75 2 : if (clipPath) {
76 2 : aContext.Clip(clipPath);
77 : } else {
78 : // The spec says clip away everything if we have no children or the
79 : // clipping path otherwise can't be resolved:
80 0 : aContext.Clip(Rect());
81 : }
82 2 : }
83 :
84 : already_AddRefed<DrawTarget>
85 0 : nsSVGClipPathFrame::CreateClipMask(gfxContext& aReferenceContext,
86 : IntPoint& aOffset)
87 : {
88 0 : gfxContextMatrixAutoSaveRestore autoRestoreMatrix(&aReferenceContext);
89 :
90 0 : aReferenceContext.SetMatrix(gfxMatrix());
91 0 : gfxRect rect = aReferenceContext.GetClipExtents();
92 0 : IntRect bounds = RoundedOut(ToRect(rect));
93 0 : if (bounds.IsEmpty()) {
94 : // We don't need to create a mask surface, all drawing is clipped anyway.
95 0 : return nullptr;
96 : }
97 :
98 0 : DrawTarget* referenceDT = aReferenceContext.GetDrawTarget();
99 : RefPtr<DrawTarget> maskDT =
100 0 : referenceDT->CreateSimilarDrawTarget(bounds.Size(), SurfaceFormat::A8);
101 :
102 0 : aOffset = bounds.TopLeft();
103 :
104 0 : return maskDT.forget();
105 : }
106 :
107 : static void
108 0 : ComposeExtraMask(DrawTarget* aTarget, const gfxMatrix& aMaskTransfrom,
109 : SourceSurface* aExtraMask, const Matrix& aExtraMasksTransform)
110 : {
111 0 : MOZ_ASSERT(aExtraMask);
112 :
113 0 : Matrix origin = aTarget->GetTransform();
114 0 : aTarget->SetTransform(aExtraMasksTransform * aTarget->GetTransform());
115 0 : aTarget->MaskSurface(ColorPattern(Color(0.0, 0.0, 0.0, 1.0)),
116 : aExtraMask,
117 : Point(0, 0),
118 0 : DrawOptions(1.0, CompositionOp::OP_IN));
119 0 : aTarget->SetTransform(origin);
120 0 : }
121 :
122 : void
123 0 : nsSVGClipPathFrame::PaintClipMask(gfxContext& aMaskContext,
124 : nsIFrame* aClippedFrame,
125 : const gfxMatrix& aMatrix,
126 : Matrix* aMaskTransform,
127 : SourceSurface* aExtraMask,
128 : const Matrix& aExtraMasksTransform)
129 : {
130 : static int16_t sRefChainLengthCounter = AutoReferenceChainGuard::noChain;
131 :
132 : // A clipPath can reference another clipPath, creating a chain of clipPaths
133 : // that must all be applied. We re-enter this method for each clipPath in a
134 : // chain, so we need to protect against reference chain related crashes etc.:
135 : AutoReferenceChainGuard refChainGuard(this, &mIsBeingProcessed,
136 0 : &sRefChainLengthCounter);
137 0 : if (MOZ_UNLIKELY(!refChainGuard.Reference())) {
138 0 : return; // Break reference chain
139 : }
140 :
141 0 : DrawTarget* maskDT = aMaskContext.GetDrawTarget();
142 0 : MOZ_ASSERT(maskDT->GetFormat() == SurfaceFormat::A8);
143 :
144 : // Paint this clipPath's contents into aMaskDT:
145 : // We need to set mMatrixForChildren here so that under the PaintSVG calls
146 : // on our children (below) our GetCanvasTM() method will return the correct
147 : // transform.
148 0 : mMatrixForChildren = GetClipPathTransform(aClippedFrame) * aMatrix;
149 :
150 : // Check if this clipPath is itself clipped by another clipPath:
151 : nsSVGClipPathFrame* clipPathThatClipsClipPath =
152 0 : nsSVGEffects::GetEffectProperties(this).GetClipPathFrame();
153 0 : nsSVGUtils::MaskUsage maskUsage;
154 0 : nsSVGUtils::DetermineMaskUsage(this, true, maskUsage);
155 :
156 0 : if (maskUsage.shouldApplyClipPath) {
157 : clipPathThatClipsClipPath->ApplyClipPath(aMaskContext, aClippedFrame,
158 0 : aMatrix);
159 0 : } else if (maskUsage.shouldGenerateClipMaskLayer) {
160 0 : Matrix maskTransform;
161 : RefPtr<SourceSurface> maskSurface =
162 0 : clipPathThatClipsClipPath->GetClipMask(aMaskContext, aClippedFrame,
163 0 : aMatrix, &maskTransform);
164 0 : aMaskContext.PushGroupForBlendBack(gfxContentType::ALPHA, 1.0,
165 0 : maskSurface, maskTransform);
166 : // The corresponding PopGroupAndBlend call below will mask the
167 : // blend using |maskSurface|.
168 : }
169 :
170 : // Paint our children into the mask:
171 0 : for (nsIFrame* kid = mFrames.FirstChild(); kid;
172 : kid = kid->GetNextSibling()) {
173 0 : PaintFrameIntoMask(kid, aClippedFrame, aMaskContext, aMatrix);
174 : }
175 :
176 0 : if (maskUsage.shouldGenerateClipMaskLayer) {
177 0 : aMaskContext.PopGroupAndBlend();
178 0 : } else if (maskUsage.shouldApplyClipPath) {
179 0 : aMaskContext.PopClip();
180 : }
181 :
182 : // Moz2D transforms in the opposite direction to Thebes
183 0 : gfxMatrix maskTransfrom = aMaskContext.CurrentMatrix();
184 0 : maskTransfrom.Invert();
185 :
186 0 : if (aExtraMask) {
187 0 : ComposeExtraMask(maskDT, maskTransfrom, aExtraMask, aExtraMasksTransform);
188 : }
189 :
190 0 : *aMaskTransform = ToMatrix(maskTransfrom);
191 : }
192 :
193 : void
194 0 : nsSVGClipPathFrame::PaintFrameIntoMask(nsIFrame *aFrame,
195 : nsIFrame* aClippedFrame,
196 : gfxContext& aTarget,
197 : const gfxMatrix& aMatrix)
198 : {
199 0 : nsSVGDisplayableFrame* frame = do_QueryFrame(aFrame);
200 0 : if (!frame) {
201 0 : return;
202 : }
203 :
204 : // The CTM of each frame referencing us can be different.
205 0 : frame->NotifySVGChanged(nsSVGDisplayableFrame::TRANSFORM_CHANGED);
206 :
207 : // Children of this clipPath may themselves be clipped.
208 : nsSVGEffects::EffectProperties effectProperties =
209 0 : nsSVGEffects::GetEffectProperties(aFrame);
210 0 : if (effectProperties.HasInvalidClipPath()) {
211 0 : return;
212 : }
213 : nsSVGClipPathFrame *clipPathThatClipsChild =
214 0 : effectProperties.GetClipPathFrame();
215 :
216 0 : nsSVGUtils::MaskUsage maskUsage;
217 0 : nsSVGUtils::DetermineMaskUsage(aFrame, true, maskUsage);
218 0 : if (maskUsage.shouldApplyClipPath) {
219 0 : clipPathThatClipsChild->ApplyClipPath(aTarget, aClippedFrame, aMatrix);
220 0 : } else if (maskUsage.shouldGenerateClipMaskLayer) {
221 0 : Matrix maskTransform;
222 : RefPtr<SourceSurface> maskSurface =
223 0 : clipPathThatClipsChild->GetClipMask(aTarget, aClippedFrame,
224 0 : aMatrix, &maskTransform);
225 0 : aTarget.PushGroupForBlendBack(gfxContentType::ALPHA, 1.0,
226 0 : maskSurface, maskTransform);
227 : // The corresponding PopGroupAndBlend call below will mask the
228 : // blend using |maskSurface|.
229 : }
230 :
231 0 : gfxMatrix toChildsUserSpace = mMatrixForChildren;
232 0 : nsIFrame* child = do_QueryFrame(frame);
233 0 : nsIContent* childContent = child->GetContent();
234 0 : if (childContent->IsSVGElement()) {
235 : toChildsUserSpace =
236 : static_cast<const nsSVGElement*>(childContent)->
237 0 : PrependLocalTransformsTo(mMatrixForChildren, eUserSpaceToParent);
238 : }
239 :
240 : // clipPath does not result in any image rendering, so we just use a dummy
241 : // imgDrawingParams instead of requiring our caller to pass one.
242 0 : image::imgDrawingParams imgParams;
243 :
244 : // Our children have NS_STATE_SVG_CLIPPATH_CHILD set on them, and
245 : // SVGGeometryFrame::Render checks for that state bit and paints
246 : // only the geometry (opaque black) if set.
247 0 : frame->PaintSVG(aTarget, toChildsUserSpace, imgParams);
248 :
249 0 : if (maskUsage.shouldGenerateClipMaskLayer) {
250 0 : aTarget.PopGroupAndBlend();
251 0 : } else if (maskUsage.shouldApplyClipPath) {
252 0 : aTarget.PopClip();
253 : }
254 : }
255 :
256 : already_AddRefed<SourceSurface>
257 0 : nsSVGClipPathFrame::GetClipMask(gfxContext& aReferenceContext,
258 : nsIFrame* aClippedFrame,
259 : const gfxMatrix& aMatrix,
260 : Matrix* aMaskTransform,
261 : SourceSurface* aExtraMask,
262 : const Matrix& aExtraMasksTransform)
263 : {
264 0 : IntPoint offset;
265 0 : RefPtr<DrawTarget> maskDT = CreateClipMask(aReferenceContext, offset);
266 0 : if (!maskDT) {
267 0 : return nullptr;
268 : }
269 :
270 0 : RefPtr<gfxContext> maskContext = gfxContext::CreateOrNull(maskDT);
271 0 : if (!maskContext) {
272 0 : gfxCriticalError() << "SVGClipPath context problem " << gfx::hexa(maskDT);
273 0 : return nullptr;
274 : }
275 0 : maskContext->SetMatrix(aReferenceContext.CurrentMatrix() *
276 0 : gfxMatrix::Translation(-offset));
277 :
278 0 : PaintClipMask(*maskContext, aClippedFrame, aMatrix, aMaskTransform,
279 0 : aExtraMask, aExtraMasksTransform);
280 :
281 0 : RefPtr<SourceSurface> surface = maskDT->Snapshot();
282 0 : return surface.forget();
283 : }
284 :
285 : bool
286 0 : nsSVGClipPathFrame::PointIsInsideClipPath(nsIFrame* aClippedFrame,
287 : const gfxPoint &aPoint)
288 : {
289 : static int16_t sRefChainLengthCounter = AutoReferenceChainGuard::noChain;
290 :
291 : // A clipPath can reference another clipPath, creating a chain of clipPaths
292 : // that must all be applied. We re-enter this method for each clipPath in a
293 : // chain, so we need to protect against reference chain related crashes etc.:
294 : AutoReferenceChainGuard refChainGuard(this, &mIsBeingProcessed,
295 0 : &sRefChainLengthCounter);
296 0 : if (MOZ_UNLIKELY(!refChainGuard.Reference())) {
297 0 : return false; // Break reference chain
298 : }
299 :
300 0 : gfxMatrix matrix = GetClipPathTransform(aClippedFrame);
301 0 : if (!matrix.Invert()) {
302 0 : return false;
303 : }
304 0 : gfxPoint point = matrix.TransformPoint(aPoint);
305 :
306 : // clipPath elements can themselves be clipped by a different clip path. In
307 : // that case the other clip path further clips away the element that is being
308 : // clipped by the original clipPath. If this clipPath is being clipped by a
309 : // different clip path we need to check if it prevents the original element
310 : // from recieving events at aPoint:
311 : nsSVGClipPathFrame *clipPathFrame =
312 0 : nsSVGEffects::GetEffectProperties(this).GetClipPathFrame();
313 0 : if (clipPathFrame &&
314 0 : !clipPathFrame->PointIsInsideClipPath(aClippedFrame, aPoint)) {
315 0 : return false;
316 : }
317 :
318 0 : for (nsIFrame* kid = mFrames.FirstChild(); kid;
319 : kid = kid->GetNextSibling()) {
320 0 : nsSVGDisplayableFrame* SVGFrame = do_QueryFrame(kid);
321 0 : if (SVGFrame) {
322 0 : gfxPoint pointForChild = point;
323 0 : gfxMatrix m = static_cast<nsSVGElement*>(kid->GetContent())->
324 0 : PrependLocalTransformsTo(gfxMatrix(), eUserSpaceToParent);
325 0 : if (!m.IsIdentity()) {
326 0 : if (!m.Invert()) {
327 0 : return false;
328 : }
329 0 : pointForChild = m.TransformPoint(point);
330 : }
331 0 : if (SVGFrame->GetFrameForPoint(pointForChild)) {
332 0 : return true;
333 : }
334 : }
335 : }
336 :
337 0 : return false;
338 : }
339 :
340 : bool
341 6 : nsSVGClipPathFrame::IsTrivial(nsSVGDisplayableFrame **aSingleChild)
342 : {
343 : // If the clip path is clipped then it's non-trivial
344 6 : if (nsSVGEffects::GetEffectProperties(this).GetClipPathFrame())
345 0 : return false;
346 :
347 6 : if (aSingleChild) {
348 2 : *aSingleChild = nullptr;
349 : }
350 :
351 6 : nsSVGDisplayableFrame* foundChild = nullptr;
352 :
353 12 : for (nsIFrame* kid = mFrames.FirstChild(); kid;
354 : kid = kid->GetNextSibling()) {
355 6 : nsSVGDisplayableFrame* svgChild = do_QueryFrame(kid);
356 6 : if (svgChild) {
357 : // We consider a non-trivial clipPath to be one containing
358 : // either more than one svg child and/or a svg container
359 6 : if (foundChild || svgChild->IsDisplayContainer())
360 0 : return false;
361 :
362 : // or where the child is itself clipped
363 6 : if (nsSVGEffects::GetEffectProperties(kid).GetClipPathFrame())
364 0 : return false;
365 :
366 6 : foundChild = svgChild;
367 : }
368 : }
369 6 : if (aSingleChild) {
370 2 : *aSingleChild = foundChild;
371 : }
372 6 : return true;
373 : }
374 :
375 : bool
376 2 : nsSVGClipPathFrame::IsValid()
377 : {
378 : static int16_t sRefChainLengthCounter = AutoReferenceChainGuard::noChain;
379 :
380 : // A clipPath can reference another clipPath, creating a chain of clipPaths
381 : // that must all be applied. We re-enter this method for each clipPath in a
382 : // chain, so we need to protect against reference chain related crashes etc.:
383 : AutoReferenceChainGuard refChainGuard(this, &mIsBeingProcessed,
384 4 : &sRefChainLengthCounter);
385 2 : if (MOZ_UNLIKELY(!refChainGuard.Reference())) {
386 0 : return false; // Break reference chain
387 : }
388 :
389 2 : if (nsSVGEffects::GetEffectProperties(this).HasInvalidClipPath()) {
390 0 : return false;
391 : }
392 :
393 4 : for (nsIFrame* kid = mFrames.FirstChild(); kid;
394 : kid = kid->GetNextSibling()) {
395 :
396 2 : LayoutFrameType kidType = kid->Type();
397 :
398 2 : if (kidType == LayoutFrameType::SVGUse) {
399 0 : for (nsIFrame* grandKid : kid->PrincipalChildList()) {
400 :
401 0 : LayoutFrameType grandKidType = grandKid->Type();
402 :
403 0 : if (grandKidType != LayoutFrameType::SVGGeometry &&
404 : grandKidType != LayoutFrameType::SVGText) {
405 0 : return false;
406 : }
407 : }
408 0 : continue;
409 : }
410 :
411 2 : if (kidType != LayoutFrameType::SVGGeometry &&
412 : kidType != LayoutFrameType::SVGText) {
413 0 : return false;
414 : }
415 : }
416 :
417 2 : return true;
418 : }
419 :
420 : nsresult
421 0 : nsSVGClipPathFrame::AttributeChanged(int32_t aNameSpaceID,
422 : nsIAtom* aAttribute,
423 : int32_t aModType)
424 : {
425 0 : if (aNameSpaceID == kNameSpaceID_None) {
426 0 : if (aAttribute == nsGkAtoms::transform) {
427 0 : nsSVGEffects::InvalidateDirectRenderingObservers(this);
428 0 : nsSVGUtils::NotifyChildrenOfSVGChange(this,
429 0 : nsSVGDisplayableFrame::TRANSFORM_CHANGED);
430 : }
431 0 : if (aAttribute == nsGkAtoms::clipPathUnits) {
432 0 : nsSVGEffects::InvalidateDirectRenderingObservers(this);
433 : }
434 : }
435 :
436 0 : return nsSVGContainerFrame::AttributeChanged(aNameSpaceID,
437 0 : aAttribute, aModType);
438 : }
439 :
440 : void
441 6 : nsSVGClipPathFrame::Init(nsIContent* aContent,
442 : nsContainerFrame* aParent,
443 : nsIFrame* aPrevInFlow)
444 : {
445 6 : NS_ASSERTION(aContent->IsSVGElement(nsGkAtoms::clipPath),
446 : "Content is not an SVG clipPath!");
447 :
448 6 : AddStateBits(NS_STATE_SVG_CLIPPATH_CHILD);
449 6 : nsSVGContainerFrame::Init(aContent, aParent, aPrevInFlow);
450 6 : }
451 :
452 : gfxMatrix
453 0 : nsSVGClipPathFrame::GetCanvasTM()
454 : {
455 0 : return mMatrixForChildren;
456 : }
457 :
458 : gfxMatrix
459 2 : nsSVGClipPathFrame::GetClipPathTransform(nsIFrame* aClippedFrame)
460 : {
461 2 : SVGClipPathElement *content = static_cast<SVGClipPathElement*>(mContent);
462 :
463 2 : gfxMatrix tm = content->PrependLocalTransformsTo(gfxMatrix());
464 :
465 : nsSVGEnum* clipPathUnits =
466 2 : &content->mEnumAttributes[SVGClipPathElement::CLIPPATHUNITS];
467 :
468 2 : return nsSVGUtils::AdjustMatrixForUnits(tm, clipPathUnits, aClippedFrame);
469 : }
470 :
471 : SVGBBox
472 0 : nsSVGClipPathFrame::GetBBoxForClipPathFrame(const SVGBBox &aBBox,
473 : const gfxMatrix &aMatrix)
474 : {
475 0 : nsIContent* node = GetContent()->GetFirstChild();
476 0 : SVGBBox unionBBox, tmpBBox;
477 0 : for (; node; node = node->GetNextSibling()) {
478 : nsIFrame *frame =
479 0 : static_cast<nsSVGElement*>(node)->GetPrimaryFrame();
480 0 : if (frame) {
481 0 : nsSVGDisplayableFrame* svg = do_QueryFrame(frame);
482 0 : if (svg) {
483 0 : tmpBBox = svg->GetBBoxContribution(mozilla::gfx::ToMatrix(aMatrix),
484 0 : nsSVGUtils::eBBoxIncludeFill);
485 : nsSVGEffects::EffectProperties effectProperties =
486 0 : nsSVGEffects::GetEffectProperties(frame);
487 0 : if (effectProperties.HasNoOrValidClipPath()) {
488 : nsSVGClipPathFrame *clipPathFrame =
489 0 : effectProperties.GetClipPathFrame();
490 0 : if (clipPathFrame) {
491 0 : tmpBBox = clipPathFrame->GetBBoxForClipPathFrame(tmpBBox, aMatrix);
492 : }
493 : }
494 0 : tmpBBox.Intersect(aBBox);
495 0 : unionBBox.UnionEdges(tmpBBox);
496 : }
497 : }
498 : }
499 :
500 : nsSVGEffects::EffectProperties props =
501 0 : nsSVGEffects::GetEffectProperties(this);
502 0 : if (props.mClipPath) {
503 0 : if (props.HasInvalidClipPath()) {
504 0 : unionBBox = SVGBBox();
505 : } else {
506 0 : nsSVGClipPathFrame *clipPathFrame = props.GetClipPathFrame();
507 0 : if (clipPathFrame) {
508 0 : tmpBBox = clipPathFrame->GetBBoxForClipPathFrame(aBBox, aMatrix);
509 0 : unionBBox.Intersect(tmpBBox);
510 : }
511 : }
512 : }
513 0 : return unionBBox;
514 : }
|