SVGClipPathFrame.cpp (16190B)
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/. */ 6 7 // Main header first: 8 #include "SVGClipPathFrame.h" 9 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" 21 22 using namespace mozilla::dom; 23 using namespace mozilla::gfx; 24 using namespace mozilla::image; 25 26 //---------------------------------------------------------------------- 27 // Implementation 28 29 nsIFrame* NS_NewSVGClipPathFrame(mozilla::PresShell* aPresShell, 30 mozilla::ComputedStyle* aStyle) { 31 return new (aPresShell) 32 mozilla::SVGClipPathFrame(aStyle, aPresShell->GetPresContext()); 33 } 34 35 namespace mozilla { 36 37 NS_IMPL_FRAMEARENA_HELPERS(SVGClipPathFrame) 38 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"); 45 46 const DrawTarget* drawTarget = aContext.GetDrawTarget(); 47 48 // No need for AutoReferenceChainGuard since simple clip paths by definition 49 // don't reference another clip path. 50 51 // Restore current transform after applying clip path: 52 gfxContextMatrixAutoSaveRestore autoRestoreTransform(&aContext); 53 54 RefPtr<Path> clipPath; 55 56 if (singleClipPathChild) { 57 SVGGeometryFrame* pathFrame = do_QueryFrame(singleClipPathChild); 58 if (pathFrame && pathFrame->StyleVisibility()->IsVisible()) { 59 SVGGeometryElement* pathElement = 60 static_cast<SVGGeometryElement*>(pathFrame->GetContent()); 61 62 gfxMatrix toChildsUserSpace = 63 SVGUtils::GetTransformMatrixInUserSpace(pathFrame) * 64 (GetClipPathTransform(aClippedFrame) * aMatrix); 65 66 gfxMatrix newMatrix = aContext.CurrentMatrixDouble() 67 .PreMultiply(toChildsUserSpace) 68 .NudgeToIntegers(); 69 if (!newMatrix.IsSingular()) { 70 aContext.SetMatrixDouble(newMatrix); 71 FillRule clipRule = 72 SVGUtils::ToFillRule(pathFrame->StyleSVG()->mClipRule); 73 clipPath = pathElement->GetOrBuildPath(drawTarget, clipRule); 74 } 75 } 76 } 77 78 if (clipPath) { 79 aContext.Clip(clipPath); 80 } else { 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()); 84 } 85 } 86 87 static void ComposeExtraMask(DrawTarget* aTarget, SourceSurface* aExtraMask) { 88 MOZ_ASSERT(aExtraMask); 89 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); 96 } 97 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); 106 107 gfxGroupForBlendAutoSaveRestore autoGroupForBlend(&aMaskContext); 108 if (maskUsage.ShouldApplyClipPath()) { 109 clipPathThatClipsClipPath->ApplyClipPath(aMaskContext, aClippedFrame, 110 aMatrix); 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); 120 } 121 122 // Paint our children into the mask: 123 for (auto* kid : mFrames) { 124 PaintFrameIntoMask(kid, aClippedFrame, aMaskContext); 125 } 126 127 if (maskUsage.ShouldApplyClipPath()) { 128 aMaskContext.PopClip(); 129 } 130 } 131 132 void SVGClipPathFrame::PaintClipMask(gfxContext& aMaskContext, 133 nsIFrame* aClippedFrame, 134 const gfxMatrix& aMatrix, 135 SourceSurface* aExtraMask) { 136 static int16_t sRefChainLengthCounter = AutoReferenceChainGuard::noChain; 137 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 145 } 146 if (!IsValid()) { 147 return; 148 } 149 150 DrawTarget* maskDT = aMaskContext.GetDrawTarget(); 151 MOZ_ASSERT(maskDT->GetFormat() == SurfaceFormat::A8); 152 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 156 // transform. 157 mMatrixForChildren = GetClipPathTransform(aClippedFrame) * aMatrix; 158 159 PaintChildren(aMaskContext, aClippedFrame, aMatrix); 160 161 if (aExtraMask) { 162 ComposeExtraMask(maskDT, aExtraMask); 163 } 164 } 165 166 void SVGClipPathFrame::PaintFrameIntoMask(nsIFrame* aFrame, 167 nsIFrame* aClippedFrame, 168 gfxContext& aTarget) { 169 ISVGDisplayableFrame* frame = do_QueryFrame(aFrame); 170 if (!frame) { 171 return; 172 } 173 174 // The CTM of each frame referencing us can be different. 175 frame->NotifySVGChanged(ISVGDisplayableFrame::ChangeFlag::TransformChanged); 176 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) { 183 return; 184 } 185 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); 196 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); 203 } 204 205 gfxMatrix toChildsUserSpace = mMatrixForChildren; 206 nsIFrame* child = do_QueryFrame(frame); 207 nsIContent* childContent = child->GetContent(); 208 if (childContent->IsSVGElement()) { 209 toChildsUserSpace = 210 SVGUtils::GetTransformMatrixInUserSpace(child) * mMatrixForChildren; 211 } 212 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; 216 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); 221 222 if (maskUsage.ShouldApplyClipPath()) { 223 aTarget.PopClip(); 224 } 225 } 226 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); 233 if (!maskDT) { 234 return nullptr; 235 } 236 237 gfxContext maskContext(maskDT, /* aPreserveTransform */ true); 238 PaintClipMask(maskContext, aClippedFrame, aMatrix, aExtraMask); 239 240 RefPtr<SourceSurface> surface = maskDT->Snapshot(); 241 return surface.forget(); 242 } 243 244 bool SVGClipPathFrame::PointIsInsideClipPath(nsIFrame* aClippedFrame, 245 const gfxPoint& aPoint) { 246 static int16_t sRefChainLengthCounter = AutoReferenceChainGuard::noChain; 247 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 255 } 256 if (!IsValid()) { 257 return false; 258 } 259 260 gfxMatrix matrix = GetClipPathTransform(aClippedFrame); 261 if (!matrix.Invert()) { 262 return false; 263 } 264 gfxPoint point = matrix.TransformPoint(aPoint); 265 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); 274 if (clipPathFrame && 275 !clipPathFrame->PointIsInsideClipPath(aClippedFrame, aPoint)) { 276 return false; 277 } 278 279 for (auto* kid : mFrames) { 280 ISVGDisplayableFrame* SVGFrame = do_QueryFrame(kid); 281 if (SVGFrame) { 282 gfxPoint pointForChild = point; 283 284 gfxMatrix m = SVGUtils::GetTransformMatrixInUserSpace(kid); 285 if (!m.IsIdentity()) { 286 if (!m.Invert()) { 287 return false; 288 } 289 pointForChild = m.TransformPoint(point); 290 } 291 if (SVGFrame->GetFrameForPoint(pointForChild)) { 292 return true; 293 } 294 } 295 } 296 297 return false; 298 } 299 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) { 304 return false; 305 } 306 307 if (aSingleChild) { 308 *aSingleChild = nullptr; 309 } 310 311 nsIFrame* foundChild = nullptr; 312 for (auto* kid : mFrames) { 313 ISVGDisplayableFrame* svgChild = do_QueryFrame(kid); 314 if (!svgChild) { 315 continue; 316 } 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()) { 320 return false; 321 } 322 323 // or where the child is itself clipped 324 if (SVGObserverUtils::GetAndObserveClipPath(kid, nullptr) == 325 SVGObserverUtils::eHasRefsAllValid) { 326 return false; 327 } 328 329 foundChild = kid; 330 } 331 if (aSingleChild) { 332 *aSingleChild = foundChild; 333 } 334 return true; 335 } 336 337 bool SVGClipPathFrame::IsValid() { 338 if (SVGObserverUtils::GetAndObserveClipPath(this, nullptr) == 339 SVGObserverUtils::eHasRefsSomeInvalid) { 340 return false; 341 } 342 343 for (auto* kid : mFrames) { 344 LayoutFrameType kidType = kid->Type(); 345 346 if (kidType == LayoutFrameType::SVGUse) { 347 for (nsIFrame* grandKid : kid->PrincipalChildList()) { 348 LayoutFrameType grandKidType = grandKid->Type(); 349 350 if (grandKidType != LayoutFrameType::SVGGeometry && 351 grandKidType != LayoutFrameType::SVGText) { 352 return false; 353 } 354 } 355 continue; 356 } 357 358 if (kidType != LayoutFrameType::SVGGeometry && 359 kidType != LayoutFrameType::SVGText) { 360 return false; 361 } 362 } 363 364 return true; 365 } 366 367 nsresult SVGClipPathFrame::AttributeChanged(int32_t aNameSpaceID, 368 nsAtom* aAttribute, 369 AttrModType aModType) { 370 if (aNameSpaceID == kNameSpaceID_None && 371 aAttribute == nsGkAtoms::clipPathUnits) { 372 SVGObserverUtils::InvalidateRenderingObservers(this); 373 } 374 return SVGContainerFrame::AttributeChanged(aNameSpaceID, aAttribute, 375 aModType); 376 } 377 378 #ifdef DEBUG 379 void SVGClipPathFrame::Init(nsIContent* aContent, nsContainerFrame* aParent, 380 nsIFrame* aPrevInFlow) { 381 NS_ASSERTION(aContent->IsSVGElement(nsGkAtoms::clipPath), 382 "Content is not an SVG clipPath!"); 383 384 SVGContainerFrame::Init(aContent, aParent, aPrevInFlow); 385 } 386 #endif 387 388 gfxMatrix SVGClipPathFrame::GetCanvasTM() { return mMatrixForChildren; } 389 390 gfxMatrix SVGClipPathFrame::GetClipPathTransform(nsIFrame* aClippedFrame) { 391 gfxMatrix tm = SVGUtils::GetTransformMatrixInUserSpace(this); 392 393 auto* content = static_cast<SVGClipPathElement*>(GetContent()); 394 SVGAnimatedEnumeration* clipPathUnits = 395 &content->mEnumAttributes[SVGClipPathElement::CLIPPATHUNITS]; 396 397 uint32_t flags = SVGUtils::eBBoxIncludeFillGeometry | 398 (aClippedFrame->StyleBorder()->mBoxDecorationBreak == 399 StyleBoxDecorationBreak::Clone 400 ? SVGUtils::eIncludeOnlyCurrentFrameForNonSVGElement 401 : 0); 402 403 return SVGUtils::AdjustMatrixForUnits(tm, clipPathUnits, aClippedFrame, 404 flags); 405 } 406 407 SVGBBox SVGClipPathFrame::GetBBoxForClipPathFrame(const SVGBBox& aBBox, 408 const gfxMatrix& aMatrix, 409 uint32_t aFlags) { 410 SVGClipPathFrame* clipPathThatClipsClipPath; 411 if (SVGObserverUtils::GetAndObserveClipPath(this, 412 &clipPathThatClipsClipPath) == 413 SVGObserverUtils::eHasRefsSomeInvalid) { 414 return SVGBBox(); 415 } 416 417 nsIContent* node = GetContent()->GetFirstChild(); 418 SVGBBox unionBBox; 419 for (; node; node = node->GetNextSibling()) { 420 if (nsIFrame* frame = node->GetPrimaryFrame()) { 421 ISVGDisplayableFrame* svg = do_QueryFrame(frame); 422 if (svg) { 423 gfxMatrix matrix = 424 SVGUtils::GetTransformMatrixInUserSpace(frame) * aMatrix; 425 SVGBBox tmpBBox = svg->GetBBoxContribution( 426 gfx::ToMatrix(matrix), SVGUtils::eBBoxIncludeFillGeometry); 427 SVGClipPathFrame* clipPathFrame; 428 if (SVGObserverUtils::GetAndObserveClipPath(frame, &clipPathFrame) != 429 SVGObserverUtils::eHasRefsSomeInvalid && 430 clipPathFrame) { 431 tmpBBox = 432 clipPathFrame->GetBBoxForClipPathFrame(tmpBBox, aMatrix, aFlags); 433 } 434 if (!(aFlags & SVGUtils::eDoNotClipToBBoxOfContentInsideClipPath)) { 435 tmpBBox.Intersect(aBBox); 436 } 437 unionBBox.UnionEdges(tmpBBox); 438 } 439 } 440 } 441 442 if (clipPathThatClipsClipPath) { 443 unionBBox.Intersect(clipPathThatClipsClipPath->GetBBoxForClipPathFrame( 444 aBBox, aMatrix, aFlags)); 445 } 446 return unionBBox; 447 } 448 449 } // namespace mozilla