SVGUtils.cpp (55930B)
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 // This is also necessary to ensure our definition of M_SQRT1_2 is picked up 9 #include "SVGUtils.h" 10 11 #include <algorithm> 12 13 // Keep others in (case-insensitive) order: 14 #include "SVGAnimatedLength.h" 15 #include "SVGPaintServerFrame.h" 16 #include "gfx2DGlue.h" 17 #include "gfxContext.h" 18 #include "gfxMatrix.h" 19 #include "gfxPlatform.h" 20 #include "gfxRect.h" 21 #include "gfxUtils.h" 22 #include "mozilla/CSSClipPathInstance.h" 23 #include "mozilla/FilterInstance.h" 24 #include "mozilla/ISVGDisplayableFrame.h" 25 #include "mozilla/Preferences.h" 26 #include "mozilla/PresShell.h" 27 #include "mozilla/SVGClipPathFrame.h" 28 #include "mozilla/SVGContainerFrame.h" 29 #include "mozilla/SVGContentUtils.h" 30 #include "mozilla/SVGContextPaint.h" 31 #include "mozilla/SVGForeignObjectFrame.h" 32 #include "mozilla/SVGGeometryFrame.h" 33 #include "mozilla/SVGIntegrationUtils.h" 34 #include "mozilla/SVGMaskFrame.h" 35 #include "mozilla/SVGObserverUtils.h" 36 #include "mozilla/SVGOuterSVGFrame.h" 37 #include "mozilla/SVGTextFrame.h" 38 #include "mozilla/StaticPrefs_svg.h" 39 #include "mozilla/dom/Document.h" 40 #include "mozilla/dom/SVGClipPathElement.h" 41 #include "mozilla/dom/SVGGeometryElement.h" 42 #include "mozilla/dom/SVGPathElement.h" 43 #include "mozilla/dom/SVGUnitTypesBinding.h" 44 #include "mozilla/dom/SVGViewportElement.h" 45 #include "mozilla/gfx/2D.h" 46 #include "mozilla/gfx/PatternHelpers.h" 47 #include "nsCSSFrameConstructor.h" 48 #include "nsDisplayList.h" 49 #include "nsFrameList.h" 50 #include "nsGkAtoms.h" 51 #include "nsIContent.h" 52 #include "nsIFrame.h" 53 #include "nsIFrameInlines.h" 54 #include "nsLayoutUtils.h" 55 #include "nsPresContext.h" 56 #include "nsStyleStruct.h" 57 #include "nsStyleTransformMatrix.h" 58 #include "nsTextFrame.h" 59 60 using namespace mozilla::dom; 61 using namespace mozilla::dom::SVGUnitTypes_Binding; 62 using namespace mozilla::gfx; 63 using namespace mozilla::image; 64 65 bool NS_SVGNewGetBBoxEnabled() { 66 return mozilla::StaticPrefs::svg_new_getBBox_enabled(); 67 } 68 69 namespace mozilla { 70 71 // we only take the address of this: 72 static gfx::UserDataKey sSVGAutoRenderStateKey; 73 74 SVGAutoRenderState::SVGAutoRenderState(DrawTarget* aDrawTarget) 75 : mDrawTarget(aDrawTarget), 76 mOriginalRenderState(nullptr), 77 mPaintingToWindow(false) { 78 mOriginalRenderState = aDrawTarget->RemoveUserData(&sSVGAutoRenderStateKey); 79 // We always remove ourselves from aContext before it dies, so 80 // passing nullptr as the destroy function is okay. 81 aDrawTarget->AddUserData(&sSVGAutoRenderStateKey, this, nullptr); 82 } 83 84 SVGAutoRenderState::~SVGAutoRenderState() { 85 mDrawTarget->RemoveUserData(&sSVGAutoRenderStateKey); 86 if (mOriginalRenderState) { 87 mDrawTarget->AddUserData(&sSVGAutoRenderStateKey, mOriginalRenderState, 88 nullptr); 89 } 90 } 91 92 void SVGAutoRenderState::SetPaintingToWindow(bool aPaintingToWindow) { 93 mPaintingToWindow = aPaintingToWindow; 94 } 95 96 /* static */ 97 bool SVGAutoRenderState::IsPaintingToWindow(DrawTarget* aDrawTarget) { 98 void* state = aDrawTarget->GetUserData(&sSVGAutoRenderStateKey); 99 if (state) { 100 return static_cast<SVGAutoRenderState*>(state)->mPaintingToWindow; 101 } 102 return false; 103 } 104 105 // Unlike containers, leaf frames do not include GetPosition() in 106 // GetCanvasTM(). 107 static bool FrameDoesNotIncludePositionInTM(const nsIFrame* aFrame) { 108 return aFrame->IsSVGGeometryFrame() || aFrame->IsSVGImageFrame() || 109 aFrame->IsInSVGTextSubtree(); 110 } 111 112 nsRect SVGUtils::GetPostFilterInkOverflowRect(nsIFrame* aFrame, 113 const nsRect& aPreFilterRect) { 114 MOZ_ASSERT(aFrame->HasAnyStateBits(NS_FRAME_SVG_LAYOUT), 115 "Called on invalid frame type"); 116 117 // Note: we do not return here for eHasNoRefs since we must still handle any 118 // CSS filter functions. 119 // in that case we disable painting of the element. 120 nsTArray<SVGFilterFrame*> filterFrames; 121 if (!aFrame->StyleEffects()->HasFilters() || 122 SVGObserverUtils::GetAndObserveFilters(aFrame, &filterFrames) == 123 SVGObserverUtils::eHasRefsSomeInvalid) { 124 return aPreFilterRect; 125 } 126 127 return FilterInstance::GetPostFilterBounds(aFrame, filterFrames, nullptr, 128 &aPreFilterRect) 129 .valueOr(aPreFilterRect); 130 } 131 132 bool SVGUtils::OuterSVGIsCallingReflowSVG(nsIFrame* aFrame) { 133 return GetOuterSVGFrame(aFrame)->IsCallingReflowSVG(); 134 } 135 136 bool SVGUtils::AnyOuterSVGIsCallingReflowSVG(nsIFrame* aFrame) { 137 SVGOuterSVGFrame* outer = GetOuterSVGFrame(aFrame); 138 do { 139 if (outer->IsCallingReflowSVG()) { 140 return true; 141 } 142 outer = GetOuterSVGFrame(outer->GetParent()); 143 } while (outer); 144 return false; 145 } 146 147 void SVGUtils::ScheduleReflowSVG(nsIFrame* aFrame) { 148 MOZ_ASSERT(aFrame->IsSVGFrame(), "Passed bad frame!"); 149 150 // If this is triggered, the callers should be fixed to call us before 151 // ReflowSVG is called. If we try to mark dirty bits on frames while we're 152 // in the process of removing them, things will get messed up. 153 MOZ_ASSERT(!OuterSVGIsCallingReflowSVG(aFrame), 154 "Do not call under ISVGDisplayableFrame::ReflowSVG!"); 155 156 // We don't call SVGObserverUtils::InvalidateRenderingObservers here because 157 // we should only be called under InvalidateAndScheduleReflowSVG (which 158 // calls InvalidateBounds) or SVGDisplayContainerFrame::InsertFrames 159 // (at which point the frame has no observers). 160 161 if (aFrame->HasAnyStateBits(NS_FRAME_IS_NONDISPLAY)) { 162 return; 163 } 164 165 if (aFrame->HasAnyStateBits(NS_FRAME_IS_DIRTY | NS_FRAME_FIRST_REFLOW)) { 166 // Nothing to do if we're already dirty, or if the outer-<svg> 167 // hasn't yet had its initial reflow. 168 return; 169 } 170 171 SVGOuterSVGFrame* outerSVGFrame = nullptr; 172 173 // We must not add dirty bits to the SVGOuterSVGFrame or else 174 // PresShell::FrameNeedsReflow won't work when we pass it in below. 175 if (aFrame->IsSVGOuterSVGFrame()) { 176 outerSVGFrame = static_cast<SVGOuterSVGFrame*>(aFrame); 177 } else { 178 aFrame->MarkSubtreeDirty(); 179 180 nsIFrame* f = aFrame->GetParent(); 181 while (f && !f->IsSVGOuterSVGFrame()) { 182 if (f->HasAnyStateBits(NS_FRAME_IS_DIRTY | NS_FRAME_HAS_DIRTY_CHILDREN)) { 183 return; 184 } 185 f->AddStateBits(NS_FRAME_HAS_DIRTY_CHILDREN); 186 f = f->GetParent(); 187 MOZ_ASSERT(f->IsSVGFrame(), "IsSVGOuterSVGFrame check above not valid!"); 188 } 189 190 outerSVGFrame = static_cast<SVGOuterSVGFrame*>(f); 191 192 MOZ_ASSERT(outerSVGFrame && outerSVGFrame->IsSVGOuterSVGFrame(), 193 "Did not find SVGOuterSVGFrame!"); 194 } 195 196 if (outerSVGFrame->HasAnyStateBits(NS_FRAME_IN_REFLOW)) { 197 // We're currently under an SVGOuterSVGFrame::Reflow call so there is no 198 // need to call PresShell::FrameNeedsReflow, since we have an 199 // SVGOuterSVGFrame::DidReflow call pending. 200 return; 201 } 202 203 nsFrameState dirtyBit = 204 (outerSVGFrame == aFrame ? NS_FRAME_IS_DIRTY 205 : NS_FRAME_HAS_DIRTY_CHILDREN); 206 207 aFrame->PresShell()->FrameNeedsReflow(outerSVGFrame, IntrinsicDirty::None, 208 dirtyBit); 209 } 210 211 bool SVGUtils::NeedsReflowSVG(const nsIFrame* aFrame) { 212 MOZ_ASSERT(aFrame->IsSVGFrame(), "SVG uses bits differently!"); 213 214 // The flags we test here may change, hence why we have this separate 215 // function. 216 return aFrame->IsSubtreeDirty(); 217 } 218 219 Size SVGUtils::GetContextSize(const nsIFrame* aFrame) { 220 Size size; 221 222 MOZ_ASSERT(aFrame->GetContent()->IsSVGElement(), "bad cast"); 223 const SVGElement* element = static_cast<SVGElement*>(aFrame->GetContent()); 224 225 SVGViewportElement* ctx = element->GetCtx(); 226 if (ctx) { 227 size.width = ctx->GetLength(SVGContentUtils::X); 228 size.height = ctx->GetLength(SVGContentUtils::Y); 229 } 230 return size; 231 } 232 233 float SVGUtils::ObjectSpace(const gfxRect& aRect, 234 const dom::UserSpaceMetrics& aMetrics, 235 const SVGAnimatedLength* aLength) { 236 float axis; 237 238 switch (aLength->GetCtxType()) { 239 case SVGContentUtils::X: 240 axis = aRect.Width(); 241 break; 242 case SVGContentUtils::Y: 243 axis = aRect.Height(); 244 break; 245 case SVGContentUtils::XY: 246 axis = float(SVGContentUtils::ComputeNormalizedHypotenuse( 247 aRect.Width(), aRect.Height())); 248 break; 249 default: 250 MOZ_ASSERT_UNREACHABLE("unexpected ctx type"); 251 axis = 0.0f; 252 break; 253 } 254 if (aLength->IsPercentage()) { 255 // Multiply first to avoid precision errors: 256 return axis * aLength->GetAnimValInSpecifiedUnits() / 100; 257 } 258 return aLength->GetAnimValueWithZoom(aMetrics) * axis; 259 } 260 261 float SVGUtils::UserSpace(nsIFrame* aNonSVGContext, 262 const SVGAnimatedLength* aLength) { 263 MOZ_ASSERT(!aNonSVGContext->IsTextFrame(), "Not expecting text content"); 264 return aLength->GetAnimValueWithZoom(aNonSVGContext); 265 } 266 267 float SVGUtils::UserSpace(const UserSpaceMetrics& aMetrics, 268 const SVGAnimatedLength* aLength) { 269 return aLength->GetAnimValueWithZoom(aMetrics); 270 } 271 272 SVGOuterSVGFrame* SVGUtils::GetOuterSVGFrame(nsIFrame* aFrame) { 273 return static_cast<SVGOuterSVGFrame*>(nsLayoutUtils::GetClosestFrameOfType( 274 aFrame, LayoutFrameType::SVGOuterSVG)); 275 } 276 277 nsIFrame* SVGUtils::GetOuterSVGFrameAndCoveredRegion(nsIFrame* aFrame, 278 nsRect* aRect) { 279 ISVGDisplayableFrame* svg = do_QueryFrame(aFrame); 280 if (!svg) { 281 return nullptr; 282 } 283 SVGOuterSVGFrame* outer = GetOuterSVGFrame(aFrame); 284 if (outer == svg) { 285 return nullptr; 286 } 287 288 if (aFrame->HasAnyStateBits(NS_FRAME_IS_NONDISPLAY)) { 289 *aRect = nsRect(); 290 return outer; 291 } 292 293 auto ctm = nsLayoutUtils::GetTransformToAncestor(RelativeTo{aFrame}, 294 RelativeTo{outer}); 295 296 Matrix mm; 297 ctm.ProjectTo2D(); 298 ctm.CanDraw2D(&mm); 299 gfxMatrix m = ThebesMatrix(mm); 300 301 float appUnitsPerDevPixel = aFrame->PresContext()->AppUnitsPerDevPixel(); 302 float devPixelPerCSSPixel = 303 float(AppUnitsPerCSSPixel()) / appUnitsPerDevPixel; 304 305 // The matrix that GetBBox accepts should operate on "user space", 306 // i.e. with CSS pixel unit. 307 m.PreScale(devPixelPerCSSPixel, devPixelPerCSSPixel); 308 309 auto initPosition = gfxPoint( 310 NSAppUnitsToFloatPixels(aFrame->GetPosition().x, AppUnitsPerCSSPixel()), 311 NSAppUnitsToFloatPixels(aFrame->GetPosition().y, AppUnitsPerCSSPixel())); 312 313 // Both SVGUtils::GetBBox and nsLayoutUtils::GetTransformToAncestor 314 // will count this displacement, we should remove it here to avoid 315 // double-counting. 316 m.PreTranslate(-initPosition); 317 318 uint32_t flags = SVGUtils::eForGetClientRects | SVGUtils::eBBoxIncludeFill | 319 SVGUtils::eBBoxIncludeStroke | 320 SVGUtils::eBBoxIncludeMarkers | 321 SVGUtils::eUseUserSpaceOfUseElement; 322 323 gfxRect bbox = SVGUtils::GetBBox(aFrame, flags, &m); 324 *aRect = nsLayoutUtils::RoundGfxRectToAppRect(bbox, appUnitsPerDevPixel); 325 326 return outer; 327 } 328 329 gfxMatrix SVGUtils::GetCanvasTM(nsIFrame* aFrame) { 330 // XXX yuck, we really need a common interface for GetCanvasTM 331 332 if (!aFrame->HasAnyStateBits(NS_FRAME_SVG_LAYOUT)) { 333 return GetCSSPxToDevPxMatrix(aFrame); 334 } 335 336 if (aFrame->IsSVGForeignObjectFrame()) { 337 return static_cast<SVGForeignObjectFrame*>(aFrame)->GetCanvasTM(); 338 } 339 340 if (SVGContainerFrame* containerFrame = do_QueryFrame(aFrame)) { 341 return containerFrame->GetCanvasTM(); 342 } 343 344 MOZ_ASSERT(aFrame->GetParent()->IsSVGContainerFrame()); 345 346 auto* parent = static_cast<SVGContainerFrame*>(aFrame->GetParent()); 347 auto* content = static_cast<SVGElement*>(aFrame->GetContent()); 348 349 return content->ChildToUserSpaceTransform() * parent->GetCanvasTM(); 350 } 351 352 bool SVGUtils::GetParentSVGTransforms(const nsIFrame* aFrame, 353 gfx::Matrix* aFromParentTransform) { 354 MOZ_ASSERT(aFrame->HasAllStateBits(NS_FRAME_SVG_LAYOUT | 355 NS_FRAME_MAY_BE_TRANSFORMED), 356 "Expecting an SVG frame that can be transformed"); 357 if (SVGContainerFrame* parent = do_QueryFrame(aFrame->GetParent())) { 358 return parent->HasChildrenOnlyTransform(aFromParentTransform); 359 } 360 return false; 361 } 362 363 void SVGUtils::NotifyChildrenOfSVGChange( 364 nsIFrame* aFrame, ISVGDisplayableFrame::ChangeFlags aFlags) { 365 for (nsIFrame* kid : aFrame->PrincipalChildList()) { 366 ISVGDisplayableFrame* SVGFrame = do_QueryFrame(kid); 367 if (SVGFrame) { 368 SVGFrame->NotifySVGChanged(aFlags); 369 } else { 370 NS_ASSERTION(kid->IsSVGFrame() || kid->IsInSVGTextSubtree(), 371 "SVG frame expected"); 372 // recurse into the children of container frames e.g. <clipPath>, <mask> 373 // in case they have child frames with transformation matrices 374 if (kid->IsSVGFrame()) { 375 NotifyChildrenOfSVGChange(kid, aFlags); 376 } 377 } 378 } 379 } 380 381 // ************************************************************ 382 383 float SVGUtils::ComputeOpacity(const nsIFrame* aFrame, bool aHandleOpacity) { 384 if (!aHandleOpacity) { 385 return 1.0f; 386 } 387 388 const auto* styleEffects = aFrame->StyleEffects(); 389 390 if (!styleEffects->IsOpaque() && SVGUtils::CanOptimizeOpacity(aFrame)) { 391 return 1.0f; 392 } 393 394 return styleEffects->mOpacity; 395 } 396 397 SVGUtils::MaskUsage SVGUtils::DetermineMaskUsage(const nsIFrame* aFrame, 398 bool aHandleOpacity) { 399 MaskUsage usage; 400 401 using ClipPathType = StyleClipPath::Tag; 402 403 usage.mOpacity = ComputeOpacity(aFrame, aHandleOpacity); 404 405 nsIFrame* firstFrame = 406 nsLayoutUtils::FirstContinuationOrIBSplitSibling(aFrame); 407 408 const nsStyleSVGReset* svgReset = firstFrame->StyleSVGReset(); 409 410 if (SVGObserverUtils::GetAndObserveMasks(firstFrame, nullptr) != 411 SVGObserverUtils::eHasNoRefs) { 412 usage.mShouldGenerateMaskLayer = true; 413 } 414 415 SVGClipPathFrame* clipPathFrame; 416 // XXX check return value? 417 SVGObserverUtils::GetAndObserveClipPath(firstFrame, &clipPathFrame); 418 MOZ_ASSERT(!clipPathFrame || svgReset->mClipPath.IsUrl()); 419 420 switch (svgReset->mClipPath.tag) { 421 case ClipPathType::Url: 422 if (clipPathFrame) { 423 if (clipPathFrame->IsTrivial()) { 424 usage.mShouldApplyClipPath = true; 425 } else { 426 usage.mShouldGenerateClipMaskLayer = true; 427 } 428 } 429 break; 430 case ClipPathType::Shape: { 431 usage.mShouldApplyBasicShapeOrPath = true; 432 const auto& shape = svgReset->mClipPath.AsShape()._0; 433 usage.mIsSimpleClipShape = 434 !usage.mShouldGenerateMaskLayer && 435 (shape->IsRect() || shape->IsCircle() || shape->IsEllipse()); 436 break; 437 } 438 case ClipPathType::Box: 439 usage.mShouldApplyBasicShapeOrPath = true; 440 break; 441 case ClipPathType::None: 442 MOZ_ASSERT(!usage.mShouldGenerateClipMaskLayer && 443 !usage.mShouldApplyClipPath && 444 !usage.mShouldApplyBasicShapeOrPath); 445 break; 446 default: 447 MOZ_ASSERT_UNREACHABLE("Unsupported clip-path type."); 448 break; 449 } 450 return usage; 451 } 452 453 class MixModeBlender { 454 public: 455 using Factory = gfx::Factory; 456 457 MixModeBlender(nsIFrame* aFrame, gfxContext* aContext) 458 : mFrame(aFrame), mSourceCtx(aContext) { 459 MOZ_ASSERT(mFrame && mSourceCtx); 460 } 461 462 bool ShouldCreateDrawTargetForBlend() const { 463 return mFrame->StyleEffects()->HasMixBlendMode(); 464 } 465 466 gfxContext* CreateBlendTarget(const gfxMatrix& aTransform) { 467 MOZ_ASSERT(ShouldCreateDrawTargetForBlend()); 468 469 // Create a temporary context to draw to so we can blend it back with 470 // another operator. 471 IntRect drawRect = ComputeClipExtsInDeviceSpace(aTransform); 472 if (drawRect.IsEmpty()) { 473 return nullptr; 474 } 475 476 RefPtr<DrawTarget> targetDT = 477 mSourceCtx->GetDrawTarget()->CreateSimilarDrawTarget( 478 drawRect.Size(), SurfaceFormat::B8G8R8A8); 479 if (!targetDT || !targetDT->IsValid()) { 480 return nullptr; 481 } 482 483 MOZ_ASSERT(!mTargetCtx, 484 "CreateBlendTarget is designed to be used once only."); 485 486 mTargetCtx = gfxContext::CreateOrNull(targetDT); 487 MOZ_ASSERT(mTargetCtx); // already checked the draw target above 488 mTargetCtx->SetMatrix(mSourceCtx->CurrentMatrix() * 489 Matrix::Translation(-drawRect.TopLeft())); 490 491 mTargetOffset = drawRect.TopLeft(); 492 493 return mTargetCtx.get(); 494 } 495 496 void BlendToTarget() { 497 MOZ_ASSERT(ShouldCreateDrawTargetForBlend()); 498 MOZ_ASSERT(mTargetCtx, 499 "BlendToTarget should be used after CreateBlendTarget."); 500 501 RefPtr<SourceSurface> targetSurf = mTargetCtx->GetDrawTarget()->Snapshot(); 502 503 gfxContextAutoSaveRestore save(mSourceCtx); 504 mSourceCtx->SetMatrix(Matrix()); // This will be restored right after. 505 auto pattern = MakeRefPtr<gfxPattern>( 506 targetSurf, Matrix::Translation(mTargetOffset.x, mTargetOffset.y)); 507 mSourceCtx->SetPattern(pattern); 508 mSourceCtx->Paint(); 509 } 510 511 private: 512 MixModeBlender() = delete; 513 514 IntRect ComputeClipExtsInDeviceSpace(const gfxMatrix& aTransform) { 515 // These are used if we require a temporary surface for a custom blend 516 // mode. Clip the source context first, so that we can generate a smaller 517 // temporary surface. (Since we will clip this context in 518 // SetupContextMatrix, a pair of save/restore is needed.) 519 gfxContextAutoSaveRestore saver; 520 521 if (!mFrame->HasAnyStateBits(NS_FRAME_IS_NONDISPLAY)) { 522 saver.SetContext(mSourceCtx); 523 // aFrame has a valid ink overflow rect, so clip to it before calling 524 // PushGroup() to minimize the size of the surfaces we'll composite: 525 gfxContextMatrixAutoSaveRestore matrixAutoSaveRestore(mSourceCtx); 526 mSourceCtx->Multiply(aTransform); 527 nsRect overflowRect = mFrame->InkOverflowRectRelativeToSelf(); 528 if (FrameDoesNotIncludePositionInTM(mFrame)) { 529 overflowRect = overflowRect + mFrame->GetPosition(); 530 } 531 mSourceCtx->Clip(NSRectToSnappedRect( 532 overflowRect, mFrame->PresContext()->AppUnitsPerDevPixel(), 533 *mSourceCtx->GetDrawTarget())); 534 } 535 536 // Get the clip extents in device space. 537 gfxRect clippedFrameSurfaceRect = 538 mSourceCtx->GetClipExtents(gfxContext::eDeviceSpace); 539 clippedFrameSurfaceRect.RoundOut(); 540 541 IntRect result; 542 ToRect(clippedFrameSurfaceRect).ToIntRect(&result); 543 544 return mSourceCtx->GetDrawTarget()->CanCreateSimilarDrawTarget( 545 result.Size(), SurfaceFormat::B8G8R8A8) 546 ? result 547 : IntRect(); 548 } 549 550 nsIFrame* mFrame; 551 gfxContext* mSourceCtx; 552 UniquePtr<gfxContext> mTargetCtx; 553 IntPoint mTargetOffset; 554 }; 555 556 void SVGUtils::PaintFrameWithEffects(nsIFrame* aFrame, gfxContext& aContext, 557 const gfxMatrix& aTransform, 558 imgDrawingParams& aImgParams) { 559 NS_ASSERTION(aFrame->HasAnyStateBits(NS_FRAME_IS_NONDISPLAY) || 560 aFrame->PresContext()->Document()->IsSVGGlyphsDocument(), 561 "Only painting of non-display SVG should take this code path"); 562 563 ISVGDisplayableFrame* svgFrame = do_QueryFrame(aFrame); 564 if (!svgFrame) { 565 return; 566 } 567 568 MaskUsage maskUsage = DetermineMaskUsage(aFrame, true); 569 if (maskUsage.IsTransparent()) { 570 return; 571 } 572 573 if (auto* svg = SVGElement::FromNode(aFrame->GetContent())) { 574 if (!svg->HasValidDimensions()) { 575 return; 576 } 577 if (aFrame->IsSVGSymbolFrame() && !svg->IsInSVGUseShadowTree()) { 578 return; 579 } 580 } 581 582 /* SVG defines the following rendering model: 583 * 584 * 1. Render fill 585 * 2. Render stroke 586 * 3. Render markers 587 * 4. Apply filter 588 * 5. Apply clipping, masking, group opacity 589 * 590 * We follow this, but perform a couple of optimizations: 591 * 592 * + Use cairo's clipPath when representable natively (single object 593 * clip region). 594 * 595 * + Merge opacity and masking if both used together. 596 */ 597 598 /* Properties are added lazily and may have been removed by a restyle, 599 so make sure all applicable ones are set again. */ 600 SVGClipPathFrame* clipPathFrame; 601 nsTArray<SVGMaskFrame*> maskFrames; 602 nsTArray<SVGFilterFrame*> filterFrames; 603 const bool hasInvalidFilter = 604 SVGObserverUtils::GetAndObserveFilters(aFrame, &filterFrames) == 605 SVGObserverUtils::eHasRefsSomeInvalid; 606 SVGObserverUtils::GetAndObserveClipPath(aFrame, &clipPathFrame); 607 SVGObserverUtils::GetAndObserveMasks(aFrame, &maskFrames); 608 609 SVGMaskFrame* maskFrame = maskFrames.IsEmpty() ? nullptr : maskFrames[0]; 610 611 MixModeBlender blender(aFrame, &aContext); 612 gfxContext* target = blender.ShouldCreateDrawTargetForBlend() 613 ? blender.CreateBlendTarget(aTransform) 614 : &aContext; 615 616 if (!target) { 617 return; 618 } 619 620 /* Check if we need to do additional operations on this child's 621 * rendering, which necessitates rendering into another surface. */ 622 bool shouldPushMask = false; 623 624 if (maskUsage.ShouldGenerateMask()) { 625 RefPtr<SourceSurface> maskSurface; 626 627 // maskFrame can be nullptr even if maskUsage.ShouldGenerateMaskLayer() is 628 // true. That happens when a user gives an unresolvable mask-id, such as 629 // mask:url() 630 // mask:url(#id-which-does-not-exist) 631 // Since we only uses SVGUtils with SVG elements, not like mask on an 632 // HTML element, we should treat an unresolvable mask as no-mask here. 633 if (maskUsage.ShouldGenerateMaskLayer() && maskFrame) { 634 StyleMaskMode maskMode = 635 aFrame->StyleSVGReset()->mMask.mLayers[0].mMaskMode; 636 SVGMaskFrame::MaskParams params(aContext.GetDrawTarget(), aFrame, 637 aTransform, maskUsage.Opacity(), maskMode, 638 aImgParams); 639 640 maskSurface = maskFrame->GetMaskForMaskedFrame(params); 641 642 if (!maskSurface) { 643 // Either entire surface is clipped out, or gfx buffer allocation 644 // failure in SVGMaskFrame::GetMaskForMaskedFrame. 645 return; 646 } 647 shouldPushMask = true; 648 } 649 650 if (maskUsage.ShouldGenerateClipMaskLayer()) { 651 RefPtr<SourceSurface> clipMaskSurface = 652 clipPathFrame->GetClipMask(aContext, aFrame, aTransform, maskSurface); 653 if (clipMaskSurface) { 654 maskSurface = clipMaskSurface; 655 } else { 656 // Either entire surface is clipped out, or gfx buffer allocation 657 // failure in SVGClipPathFrame::GetClipMask. 658 return; 659 } 660 shouldPushMask = true; 661 } 662 663 if (!maskUsage.ShouldGenerateLayer()) { 664 shouldPushMask = true; 665 } 666 667 // SVG mask multiply opacity into maskSurface already, so we do not bother 668 // to apply opacity again. 669 if (shouldPushMask) { 670 // We want the mask to be untransformed so use the inverse of the 671 // current transform as the maskTransform to compensate. 672 Matrix maskTransform = aContext.CurrentMatrix(); 673 maskTransform.Invert(); 674 target->PushGroupForBlendBack(gfxContentType::COLOR_ALPHA, 675 maskFrame ? 1.0f : maskUsage.Opacity(), 676 maskSurface, maskTransform); 677 } 678 } 679 680 /* If this frame has only a trivial clipPath, set up cairo's clipping now so 681 * we can just do normal painting and get it clipped appropriately. 682 */ 683 if (maskUsage.ShouldApplyClipPath() || 684 maskUsage.ShouldApplyBasicShapeOrPath()) { 685 if (maskUsage.ShouldApplyClipPath()) { 686 clipPathFrame->ApplyClipPath(aContext, aFrame, aTransform); 687 } else { 688 CSSClipPathInstance::ApplyBasicShapeOrPathClip(aContext, aFrame, 689 aTransform); 690 } 691 } 692 693 /* Paint the child */ 694 695 // Invalid filters should render the unfiltered contents per spec. 696 if (aFrame->StyleEffects()->HasFilters() && !hasInvalidFilter) { 697 gfxContextMatrixAutoSaveRestore autoSR(target); 698 699 // 'target' is currently scaled such that its user space units are CSS 700 // pixels (SVG user space units). But PaintFilteredFrame expects it to be 701 // scaled in such a way that its user space units are device pixels. So we 702 // have to adjust the scale. 703 gfxMatrix reverseScaleMatrix = SVGUtils::GetCSSPxToDevPxMatrix(aFrame); 704 DebugOnly<bool> invertible = reverseScaleMatrix.Invert(); 705 target->SetMatrixDouble(reverseScaleMatrix * aTransform * 706 target->CurrentMatrixDouble()); 707 708 auto callback = [&](gfxContext& aContext, imgDrawingParams& aImgParams, 709 const gfxMatrix* aFilterTransform, 710 const nsIntRect* aDirtyRect) { 711 svgFrame->PaintSVG(aContext, 712 aFilterTransform 713 ? SVGUtils::GetCSSPxToDevPxMatrix(aFrame) 714 : aTransform, 715 aImgParams); 716 }; 717 // If we're masking a userSpaceOnUse mask we may need to include the 718 // stroke too. Err on the side of caution and include it always. 719 gfxRect bbox = GetBBox(aFrame, SVGUtils::eUseFrameBoundsForOuterSVG | 720 SVGUtils::eBBoxIncludeFillGeometry | 721 SVGUtils::eBBoxIncludeStroke); 722 FilterInstance::PaintFilteredFrame( 723 aFrame, aFrame->StyleEffects()->mFilters.AsSpan(), filterFrames, target, 724 callback, nullptr, aImgParams, 1.0f, &bbox); 725 } else { 726 svgFrame->PaintSVG(*target, aTransform, aImgParams); 727 } 728 729 if (maskUsage.ShouldApplyClipPath() || 730 maskUsage.ShouldApplyBasicShapeOrPath()) { 731 aContext.PopClip(); 732 } 733 734 if (shouldPushMask) { 735 target->PopGroupAndBlend(); 736 } 737 738 if (blender.ShouldCreateDrawTargetForBlend()) { 739 MOZ_ASSERT(target != &aContext); 740 blender.BlendToTarget(); 741 } 742 } 743 744 bool SVGUtils::HitTestClip(nsIFrame* aFrame, const gfxPoint& aPoint) { 745 const nsStyleSVGReset* svgReset = aFrame->StyleSVGReset(); 746 if (!svgReset->HasClipPath()) { 747 return true; 748 } 749 if (svgReset->mClipPath.IsUrl()) { 750 // If the clip-path property references non-existent or invalid clipPath 751 // element(s) we ignore it. 752 SVGClipPathFrame* clipPathFrame; 753 SVGObserverUtils::GetAndObserveClipPath(aFrame, &clipPathFrame); 754 return !clipPathFrame || 755 clipPathFrame->PointIsInsideClipPath(aFrame, aPoint); 756 } 757 return CSSClipPathInstance::HitTestBasicShapeOrPathClip(aFrame, aPoint); 758 } 759 760 IntSize SVGUtils::ConvertToSurfaceSize(const gfxSize& aSize, 761 bool* aResultOverflows) { 762 IntSize surfaceSize(ClampToInt(ceil(aSize.width)), 763 ClampToInt(ceil(aSize.height))); 764 765 *aResultOverflows = surfaceSize.width != ceil(aSize.width) || 766 surfaceSize.height != ceil(aSize.height); 767 768 if (!Factory::AllowedSurfaceSize(surfaceSize)) { 769 surfaceSize.width = std::min(kReasonableSurfaceSize, surfaceSize.width); 770 surfaceSize.height = std::min(kReasonableSurfaceSize, surfaceSize.height); 771 *aResultOverflows = true; 772 } 773 774 return surfaceSize; 775 } 776 777 bool SVGUtils::HitTestRect(const gfx::Matrix& aMatrix, float aRX, float aRY, 778 float aRWidth, float aRHeight, float aX, float aY) { 779 gfx::Rect rect(aRX, aRY, aRWidth, aRHeight); 780 if (rect.IsEmpty() || aMatrix.IsSingular()) { 781 return false; 782 } 783 gfx::Matrix toRectSpace = aMatrix; 784 toRectSpace.Invert(); 785 gfx::Point p = toRectSpace.TransformPoint(gfx::Point(aX, aY)); 786 return rect.x <= p.x && p.x <= rect.XMost() && rect.y <= p.y && 787 p.y <= rect.YMost(); 788 } 789 790 gfxRect SVGUtils::GetClipRectForFrame(const nsIFrame* aFrame, float aX, 791 float aY, float aWidth, float aHeight) { 792 const nsStyleDisplay* disp = aFrame->StyleDisplay(); 793 const nsStyleEffects* effects = aFrame->StyleEffects(); 794 795 bool clipApplies = disp->mOverflowX == StyleOverflow::Hidden || 796 disp->mOverflowY == StyleOverflow::Hidden; 797 798 if (!clipApplies || effects->mClip.IsAuto()) { 799 return gfxRect(aX, aY, aWidth, aHeight); 800 } 801 802 const auto& rect = effects->mClip.AsRect(); 803 nsRect coordClipRect = rect.ToLayoutRect(); 804 nsIntRect clipPxRect = coordClipRect.ToOutsidePixels(AppUnitsPerCSSPixel()); 805 gfxRect clipRect = 806 gfxRect(clipPxRect.x, clipPxRect.y, clipPxRect.width, clipPxRect.height); 807 if (rect.right.IsAuto()) { 808 clipRect.width = std::max(aWidth - clipRect.X(), 0.0); 809 } 810 if (rect.bottom.IsAuto()) { 811 clipRect.height = std::max(aHeight - clipRect.Y(), 0.0); 812 } 813 if (disp->mOverflowX != StyleOverflow::Hidden) { 814 clipRect.x = aX; 815 clipRect.width = aWidth; 816 } 817 if (disp->mOverflowY != StyleOverflow::Hidden) { 818 clipRect.y = aY; 819 clipRect.height = aHeight; 820 } 821 return clipRect; 822 } 823 824 gfxRect SVGUtils::GetBBox(nsIFrame* aFrame, uint32_t aFlags, 825 const gfxMatrix* aToBoundsSpace) { 826 if (aFrame->IsTextFrame()) { 827 aFrame = aFrame->GetParent(); 828 } 829 830 if (aFrame->IsInSVGTextSubtree()) { 831 // It is possible to apply a gradient, pattern, clipping path, mask or 832 // filter to text. When one of these facilities is applied to text 833 // the bounding box is the entire text element in all cases. 834 aFrame = 835 nsLayoutUtils::GetClosestFrameOfType(aFrame, LayoutFrameType::SVGText); 836 } 837 838 ISVGDisplayableFrame* svg = do_QueryFrame(aFrame); 839 const bool hasSVGLayout = aFrame->HasAnyStateBits(NS_FRAME_SVG_LAYOUT); 840 if (hasSVGLayout && !svg) { 841 // An SVG frame, but not one that can be displayed directly (for 842 // example, nsGradientFrame). These can't contribute to the bbox. 843 return gfxRect(); 844 } 845 846 const bool isOuterSVG = svg && !hasSVGLayout; 847 MOZ_ASSERT(!isOuterSVG || aFrame->IsSVGOuterSVGFrame()); 848 if (!svg || (isOuterSVG && (aFlags & eUseFrameBoundsForOuterSVG))) { 849 // An HTML element or an SVG outer frame. 850 MOZ_ASSERT(!hasSVGLayout); 851 bool onlyCurrentFrame = aFlags & eIncludeOnlyCurrentFrameForNonSVGElement; 852 return SVGIntegrationUtils::GetSVGBBoxForNonSVGFrame( 853 aFrame, 854 /* aUnionContinuations = */ !onlyCurrentFrame); 855 } 856 857 MOZ_ASSERT(svg); 858 859 if (auto* element = SVGElement::FromNodeOrNull(aFrame->GetContent())) { 860 if (!element->HasValidDimensions()) { 861 return gfxRect(); 862 } 863 } 864 865 // Clean out flags which have no effects on returning bbox from now, so that 866 // we can cache and reuse ObjectBoundingBoxProperty() in the code below. 867 aFlags &= 868 ~(eIncludeOnlyCurrentFrameForNonSVGElement | eUseFrameBoundsForOuterSVG); 869 if (!aFrame->IsSVGUseFrame()) { 870 aFlags &= ~eUseUserSpaceOfUseElement; 871 } 872 873 if (aFlags == eBBoxIncludeFillGeometry && 874 // We only cache bbox in element's own user space 875 !aToBoundsSpace) { 876 gfxRect* prop = aFrame->GetProperty(ObjectBoundingBoxProperty()); 877 if (prop) { 878 return *prop; 879 } 880 } 881 882 gfxMatrix matrix; 883 if (aToBoundsSpace) { 884 matrix = *aToBoundsSpace; 885 } 886 887 if (aFrame->IsSVGForeignObjectFrame() || 888 aFlags & SVGUtils::eUseUserSpaceOfUseElement) { 889 // The spec says getBBox "Returns the tight bounding box in *current user 890 // space*". So we should really be doing this for all elements, but that 891 // needs investigation to check that we won't break too much content. 892 // NOTE: When changing this to apply to other frame types, make sure to 893 // also update SVGUtils::FrameSpaceInCSSPxToUserSpaceOffset. 894 MOZ_ASSERT(aFrame->GetContent()->IsSVGElement(), "bad cast"); 895 auto* element = static_cast<SVGElement*>(aFrame->GetContent()); 896 matrix = element->ChildToUserSpaceTransform() * matrix; 897 } 898 gfxRect bbox = 899 svg->GetBBoxContribution(ToMatrix(matrix), aFlags).ToThebesRect(); 900 // Account for 'clipped'. 901 if (aFlags & SVGUtils::eBBoxIncludeClipped) { 902 gfxRect clipRect; 903 gfxRect fillBBox = 904 svg->GetBBoxContribution({}, SVGUtils::eBBoxIncludeFill).ToThebesRect(); 905 // XXX Should probably check for overflow: clip too. 906 bool hasClip = aFrame->StyleDisplay()->IsScrollableOverflow(); 907 if (hasClip) { 908 clipRect = SVGUtils::GetClipRectForFrame(aFrame, 0.0f, 0.0f, 909 fillBBox.width, fillBBox.height); 910 clipRect.MoveBy(fillBBox.TopLeft()); 911 if (aFrame->IsSVGForeignObjectFrame() || aFrame->IsSVGUseFrame()) { 912 clipRect = matrix.TransformBounds(clipRect); 913 } 914 } 915 SVGClipPathFrame* clipPathFrame; 916 if (SVGObserverUtils::GetAndObserveClipPath(aFrame, &clipPathFrame) == 917 SVGObserverUtils::eHasRefsSomeInvalid) { 918 bbox = gfxRect(); 919 } else { 920 if (clipPathFrame) { 921 SVGClipPathElement* clipContent = 922 static_cast<SVGClipPathElement*>(clipPathFrame->GetContent()); 923 if (clipContent->IsUnitsObjectBoundingBox()) { 924 matrix.PreTranslate(fillBBox.TopLeft()); 925 matrix.PreScale(fillBBox.width, fillBBox.height); 926 } else if (aFrame->IsSVGForeignObjectFrame()) { 927 matrix = gfxMatrix(); 928 } 929 matrix *= SVGUtils::GetTransformMatrixInUserSpace(clipPathFrame); 930 931 bbox = clipPathFrame->GetBBoxForClipPathFrame(bbox, matrix, aFlags) 932 .ToThebesRect(); 933 } 934 935 if (hasClip && !(aFlags & eDoNotClipToBBoxOfContentInsideClipPath)) { 936 bbox = bbox.Intersect(clipRect); 937 } 938 939 if (bbox.IsEmpty()) { 940 bbox = gfxRect(); 941 } 942 } 943 } 944 945 if (aFlags == eBBoxIncludeFillGeometry && 946 // We only cache bbox in element's own user space 947 !aToBoundsSpace) { 948 // Obtaining the bbox for objectBoundingBox calculations is common so we 949 // cache the result for future calls, since calculation can be expensive: 950 aFrame->SetProperty(ObjectBoundingBoxProperty(), new gfxRect(bbox)); 951 } 952 953 return bbox; 954 } 955 956 gfxPoint SVGUtils::FrameSpaceInCSSPxToUserSpaceOffset(const nsIFrame* aFrame) { 957 if (!aFrame->HasAnyStateBits(NS_FRAME_SVG_LAYOUT)) { 958 // The user space for non-SVG frames is defined as the bounding box of the 959 // frame's border-box rects over all continuations. 960 return gfxPoint(); 961 } 962 963 // Leaf frames apply their own offset inside their user space. 964 if (FrameDoesNotIncludePositionInTM(aFrame)) { 965 return nsLayoutUtils::RectToGfxRect(aFrame->GetRect(), 966 AppUnitsPerCSSPixel()) 967 .TopLeft(); 968 } 969 970 // For foreignObject frames, SVGUtils::GetBBox applies their local 971 // transform, so we need to do the same here. 972 if (aFrame->IsSVGForeignObjectFrame()) { 973 gfxMatrix transform = static_cast<SVGElement*>(aFrame->GetContent()) 974 ->ChildToUserSpaceTransform(); 975 NS_ASSERTION(!transform.HasNonTranslation(), 976 "we're relying on this being an offset-only transform"); 977 return transform.GetTranslation(); 978 } 979 980 return gfxPoint(); 981 } 982 983 static gfxRect GetBoundingBoxRelativeRect(const SVGAnimatedLength* aXYWH, 984 const SVGElement* aElement, 985 const gfxRect& aBBox) { 986 SVGElementMetrics metrics(aElement); 987 return gfxRect(aBBox.x + SVGUtils::ObjectSpace(aBBox, metrics, &aXYWH[0]), 988 aBBox.y + SVGUtils::ObjectSpace(aBBox, metrics, &aXYWH[1]), 989 SVGUtils::ObjectSpace(aBBox, metrics, &aXYWH[2]), 990 SVGUtils::ObjectSpace(aBBox, metrics, &aXYWH[3])); 991 } 992 993 gfxRect SVGUtils::GetRelativeRect(uint16_t aUnits, 994 const SVGAnimatedLength* aXYWH, 995 const gfxRect& aBBox, 996 const SVGElement* aElement, 997 const UserSpaceMetrics& aMetrics) { 998 if (aUnits == SVG_UNIT_TYPE_OBJECTBOUNDINGBOX) { 999 return GetBoundingBoxRelativeRect(aXYWH, aElement, aBBox); 1000 } 1001 return gfxRect(UserSpace(aMetrics, &aXYWH[0]), UserSpace(aMetrics, &aXYWH[1]), 1002 UserSpace(aMetrics, &aXYWH[2]), 1003 UserSpace(aMetrics, &aXYWH[3])); 1004 } 1005 1006 gfxRect SVGUtils::GetRelativeRect(uint16_t aUnits, 1007 const SVGAnimatedLength* aXYWH, 1008 const gfxRect& aBBox, nsIFrame* aFrame) { 1009 auto* svgElement = SVGElement::FromNode(aFrame->GetContent()); 1010 if (aUnits == SVG_UNIT_TYPE_OBJECTBOUNDINGBOX) { 1011 return GetBoundingBoxRelativeRect(aXYWH, svgElement, aBBox); 1012 } 1013 if (svgElement) { 1014 return GetRelativeRect(aUnits, aXYWH, aBBox, svgElement, 1015 SVGElementMetrics(svgElement)); 1016 } 1017 return GetRelativeRect(aUnits, aXYWH, aBBox, svgElement, 1018 NonSVGFrameUserSpaceMetrics(aFrame)); 1019 } 1020 1021 bool SVGUtils::CanOptimizeOpacity(const nsIFrame* aFrame) { 1022 if (!aFrame->HasAnyStateBits(NS_FRAME_SVG_LAYOUT)) { 1023 return false; 1024 } 1025 auto* content = aFrame->GetContent(); 1026 if (!content->IsSVGGeometryElement() && 1027 !content->IsSVGElement(nsGkAtoms::image)) { 1028 return false; 1029 } 1030 if (aFrame->StyleEffects()->HasFilters()) { 1031 return false; 1032 } 1033 // XXX The SVG WG is intending to allow fill, stroke and markers on <image> 1034 if (content->IsSVGElement(nsGkAtoms::image)) { 1035 return true; 1036 } 1037 const nsStyleSVG* style = aFrame->StyleSVG(); 1038 if (style->HasMarker() && 1039 static_cast<SVGGeometryElement*>(content)->IsMarkable()) { 1040 return false; 1041 } 1042 1043 if (nsLayoutUtils::HasAnimationOfPropertySet( 1044 aFrame, nsCSSPropertyIDSet::OpacityProperties())) { 1045 return false; 1046 } 1047 1048 return !style->HasFill() || !HasStroke(aFrame); 1049 } 1050 1051 gfxMatrix SVGUtils::AdjustMatrixForUnits(const gfxMatrix& aMatrix, 1052 const SVGAnimatedEnumeration* aUnits, 1053 nsIFrame* aFrame, uint32_t aFlags) { 1054 if (aFrame && aUnits->GetAnimValue() == SVG_UNIT_TYPE_OBJECTBOUNDINGBOX) { 1055 gfxRect bbox = GetBBox(aFrame, aFlags); 1056 gfxMatrix tm = aMatrix; 1057 tm.PreTranslate(gfxPoint(bbox.X(), bbox.Y())); 1058 tm.PreScale(bbox.Width(), bbox.Height()); 1059 return tm; 1060 } 1061 return aMatrix; 1062 } 1063 1064 bool SVGUtils::GetNonScalingStrokeTransform(const nsIFrame* aFrame, 1065 gfxMatrix* aUserToOuterSVG) { 1066 if (aFrame->GetContent()->IsText()) { 1067 aFrame = aFrame->GetParent(); 1068 } 1069 1070 if (!aFrame->StyleSVGReset()->HasNonScalingStroke()) { 1071 return false; 1072 } 1073 1074 MOZ_ASSERT(aFrame->GetContent()->IsSVGElement(), "should be an SVG element"); 1075 1076 SVGElement* content = static_cast<SVGElement*>(aFrame->GetContent()); 1077 *aUserToOuterSVG = 1078 ThebesMatrix(SVGContentUtils::GetNonScalingStrokeCTM(content)); 1079 1080 return aUserToOuterSVG->HasNonTranslation() && !aUserToOuterSVG->IsSingular(); 1081 } 1082 1083 void SVGUtils::UpdateNonScalingStrokeStateBit(nsIFrame* aFrame) { 1084 MOZ_ASSERT(aFrame->HasAnyStateBits(NS_FRAME_SVG_LAYOUT), 1085 "Called on invalid frame type"); 1086 MOZ_ASSERT(aFrame->StyleSVGReset()->HasNonScalingStroke(), 1087 "Expecting initial frame to have non-scaling-stroke style"); 1088 1089 do { 1090 MOZ_ASSERT(aFrame->IsSVGFrame(), "Unexpected frame type"); 1091 aFrame->AddStateBits(NS_STATE_SVG_MAY_CONTAIN_NON_SCALING_STROKE); 1092 if (aFrame->IsSVGOuterSVGFrame()) { 1093 return; 1094 } 1095 } while ((aFrame = aFrame->GetParent())); 1096 } 1097 1098 // The logic here comes from _cairo_stroke_style_max_distance_from_path 1099 static gfxRect PathExtentsToMaxStrokeExtents(const gfxRect& aPathExtents, 1100 const nsIFrame* aFrame, 1101 double aStyleExpansionFactor, 1102 const gfxMatrix& aMatrix) { 1103 double style_expansion = 1104 aStyleExpansionFactor * SVGUtils::GetStrokeWidth(aFrame); 1105 1106 gfxMatrix matrix = aMatrix; 1107 1108 gfxMatrix outerSVGToUser; 1109 if (SVGUtils::GetNonScalingStrokeTransform(aFrame, &outerSVGToUser)) { 1110 outerSVGToUser.Invert(); 1111 matrix.PreMultiply(outerSVGToUser); 1112 } 1113 1114 double dx = style_expansion * (fabs(matrix._11) + fabs(matrix._21)); 1115 double dy = style_expansion * (fabs(matrix._22) + fabs(matrix._12)); 1116 1117 gfxRect strokeExtents = aPathExtents; 1118 strokeExtents.Inflate(dx, dy); 1119 return strokeExtents; 1120 } 1121 1122 /*static*/ 1123 gfxRect SVGUtils::PathExtentsToMaxStrokeExtents(const gfxRect& aPathExtents, 1124 const nsTextFrame* aFrame, 1125 const gfxMatrix& aMatrix) { 1126 NS_ASSERTION(aFrame->IsInSVGTextSubtree(), 1127 "expected an nsTextFrame for SVG text"); 1128 return mozilla::PathExtentsToMaxStrokeExtents(aPathExtents, aFrame, 0.5, 1129 aMatrix); 1130 } 1131 1132 /*static*/ 1133 gfxRect SVGUtils::PathExtentsToMaxStrokeExtents(const gfxRect& aPathExtents, 1134 const SVGGeometryFrame* aFrame, 1135 const gfxMatrix& aMatrix) { 1136 bool strokeMayHaveCorners = 1137 !SVGContentUtils::ShapeTypeHasNoCorners(aFrame->GetContent()); 1138 1139 // For a shape without corners the stroke can only extend half the stroke 1140 // width from the path in the x/y-axis directions. For shapes with corners 1141 // the stroke can extend by sqrt(1/2) (think 45 degree rotated rect, or line 1142 // with stroke-linecaps="square"). 1143 double styleExpansionFactor = strokeMayHaveCorners ? M_SQRT1_2 : 0.5; 1144 1145 // The stroke can extend even further for paths that can be affected by 1146 // stroke-miterlimit. 1147 // We only need to do this if the limit is greater than 1, but it's probably 1148 // not worth optimizing for that. 1149 bool affectedByMiterlimit = aFrame->GetContent()->IsAnyOfSVGElements( 1150 nsGkAtoms::path, nsGkAtoms::polyline, nsGkAtoms::polygon); 1151 1152 if (affectedByMiterlimit) { 1153 const nsStyleSVG* style = aFrame->StyleSVG(); 1154 if (style->mStrokeLinejoin == StyleStrokeLinejoin::Miter && 1155 styleExpansionFactor < style->mStrokeMiterlimit / 2.0) { 1156 styleExpansionFactor = style->mStrokeMiterlimit / 2.0; 1157 } 1158 } 1159 1160 return mozilla::PathExtentsToMaxStrokeExtents(aPathExtents, aFrame, 1161 styleExpansionFactor, aMatrix); 1162 } 1163 1164 // ---------------------------------------------------------------------- 1165 1166 /* static */ 1167 nscolor SVGUtils::GetFallbackOrPaintColor( 1168 const ComputedStyle& aStyle, StyleSVGPaint nsStyleSVG::* aFillOrStroke, 1169 nscolor aDefaultContextFallbackColor) { 1170 const auto& paint = aStyle.StyleSVG()->*aFillOrStroke; 1171 nscolor color; 1172 switch (paint.kind.tag) { 1173 case StyleSVGPaintKind::Tag::PaintServer: 1174 color = paint.fallback.IsColor() 1175 ? paint.fallback.AsColor().CalcColor(aStyle) 1176 : NS_RGBA(0, 0, 0, 0); 1177 break; 1178 case StyleSVGPaintKind::Tag::ContextStroke: 1179 case StyleSVGPaintKind::Tag::ContextFill: 1180 color = paint.fallback.IsColor() 1181 ? paint.fallback.AsColor().CalcColor(aStyle) 1182 : aDefaultContextFallbackColor; 1183 break; 1184 default: 1185 color = paint.kind.AsColor().CalcColor(aStyle); 1186 break; 1187 } 1188 if (const auto* styleIfVisited = aStyle.GetStyleIfVisited()) { 1189 const auto& paintIfVisited = styleIfVisited->StyleSVG()->*aFillOrStroke; 1190 // To prevent Web content from detecting if a user has visited a URL 1191 // (via URL loading triggered by paint servers or performance 1192 // differences between paint servers or between a paint server and a 1193 // color), we do not allow whether links are visited to change which 1194 // paint server is used or switch between paint servers and simple 1195 // colors. A :visited style may only override a simple color with 1196 // another simple color. 1197 if (paintIfVisited.kind.IsColor() && paint.kind.IsColor()) { 1198 nscolor colors[2] = { 1199 color, paintIfVisited.kind.AsColor().CalcColor(*styleIfVisited)}; 1200 return ComputedStyle::CombineVisitedColors(colors, 1201 aStyle.RelevantLinkVisited()); 1202 } 1203 } 1204 return color; 1205 } 1206 1207 /* static */ 1208 void SVGUtils::MakeFillPatternFor(nsIFrame* aFrame, gfxContext* aContext, 1209 GeneralPattern* aOutPattern, 1210 imgDrawingParams& aImgParams, 1211 SVGContextPaint* aContextPaint) { 1212 const nsStyleSVG* style = aFrame->StyleSVG(); 1213 if (style->mFill.kind.IsNone()) { 1214 return; 1215 } 1216 1217 const auto* styleEffects = aFrame->StyleEffects(); 1218 1219 float fillOpacity = GetOpacity(style->mFillOpacity, aContextPaint); 1220 if (!styleEffects->IsOpaque() && SVGUtils::CanOptimizeOpacity(aFrame)) { 1221 // Combine the group opacity into the fill opacity (we will have skipped 1222 // creating an offscreen surface to apply the group opacity). 1223 fillOpacity *= styleEffects->mOpacity; 1224 } 1225 1226 const DrawTarget* dt = aContext->GetDrawTarget(); 1227 1228 SVGPaintServerFrame* ps = 1229 SVGObserverUtils::GetAndObservePaintServer(aFrame, &nsStyleSVG::mFill); 1230 1231 if (ps) { 1232 RefPtr<gfxPattern> pattern = 1233 ps->GetPaintServerPattern(aFrame, dt, aContext->CurrentMatrixDouble(), 1234 &nsStyleSVG::mFill, fillOpacity, aImgParams); 1235 if (pattern) { 1236 pattern->CacheColorStops(dt); 1237 aOutPattern->Init(*pattern->GetPattern(dt)); 1238 return; 1239 } 1240 } 1241 1242 if (aContextPaint) { 1243 RefPtr<gfxPattern> pattern; 1244 switch (style->mFill.kind.tag) { 1245 case StyleSVGPaintKind::Tag::ContextFill: 1246 pattern = aContextPaint->GetFillPattern( 1247 dt, fillOpacity, aContext->CurrentMatrixDouble(), aImgParams); 1248 break; 1249 case StyleSVGPaintKind::Tag::ContextStroke: 1250 pattern = aContextPaint->GetStrokePattern( 1251 dt, fillOpacity, aContext->CurrentMatrixDouble(), aImgParams); 1252 break; 1253 default:; 1254 } 1255 if (pattern) { 1256 aOutPattern->Init(*pattern->GetPattern(dt)); 1257 return; 1258 } 1259 } 1260 1261 if (style->mFill.fallback.IsNone()) { 1262 return; 1263 } 1264 1265 // On failure, use the fallback colour in case we have an 1266 // objectBoundingBox where the width or height of the object is zero. 1267 // See http://www.w3.org/TR/SVG11/coords.html#ObjectBoundingBox 1268 sRGBColor color(sRGBColor::FromABGR(GetFallbackOrPaintColor( 1269 *aFrame->Style(), &nsStyleSVG::mFill, NS_RGB(0, 0, 0)))); 1270 color.a *= fillOpacity; 1271 aOutPattern->InitColorPattern(ToDeviceColor(color)); 1272 } 1273 1274 /* static */ 1275 void SVGUtils::MakeStrokePatternFor(nsIFrame* aFrame, gfxContext* aContext, 1276 GeneralPattern* aOutPattern, 1277 imgDrawingParams& aImgParams, 1278 SVGContextPaint* aContextPaint) { 1279 const nsStyleSVG* style = aFrame->StyleSVG(); 1280 if (style->mStroke.kind.IsNone()) { 1281 return; 1282 } 1283 1284 const auto* styleEffects = aFrame->StyleEffects(); 1285 1286 float strokeOpacity = GetOpacity(style->mStrokeOpacity, aContextPaint); 1287 if (!styleEffects->IsOpaque() && SVGUtils::CanOptimizeOpacity(aFrame)) { 1288 // Combine the group opacity into the stroke opacity (we will have skipped 1289 // creating an offscreen surface to apply the group opacity). 1290 strokeOpacity *= styleEffects->mOpacity; 1291 } 1292 1293 const DrawTarget* dt = aContext->GetDrawTarget(); 1294 1295 SVGPaintServerFrame* ps = 1296 SVGObserverUtils::GetAndObservePaintServer(aFrame, &nsStyleSVG::mStroke); 1297 1298 if (ps) { 1299 RefPtr<gfxPattern> pattern = ps->GetPaintServerPattern( 1300 aFrame, dt, aContext->CurrentMatrixDouble(), &nsStyleSVG::mStroke, 1301 strokeOpacity, aImgParams); 1302 if (pattern) { 1303 pattern->CacheColorStops(dt); 1304 aOutPattern->Init(*pattern->GetPattern(dt)); 1305 return; 1306 } 1307 } 1308 1309 if (aContextPaint) { 1310 RefPtr<gfxPattern> pattern; 1311 switch (style->mStroke.kind.tag) { 1312 case StyleSVGPaintKind::Tag::ContextFill: 1313 pattern = aContextPaint->GetFillPattern( 1314 dt, strokeOpacity, aContext->CurrentMatrixDouble(), aImgParams); 1315 break; 1316 case StyleSVGPaintKind::Tag::ContextStroke: 1317 pattern = aContextPaint->GetStrokePattern( 1318 dt, strokeOpacity, aContext->CurrentMatrixDouble(), aImgParams); 1319 break; 1320 default:; 1321 } 1322 if (pattern) { 1323 aOutPattern->Init(*pattern->GetPattern(dt)); 1324 return; 1325 } 1326 } 1327 1328 if (style->mStroke.fallback.IsNone()) { 1329 return; 1330 } 1331 1332 // On failure, use the fallback colour in case we have an 1333 // objectBoundingBox where the width or height of the object is zero. 1334 // See http://www.w3.org/TR/SVG11/coords.html#ObjectBoundingBox 1335 sRGBColor color(sRGBColor::FromABGR(GetFallbackOrPaintColor( 1336 *aFrame->Style(), &nsStyleSVG::mStroke, NS_RGBA(0, 0, 0, 0)))); 1337 color.a *= strokeOpacity; 1338 aOutPattern->InitColorPattern(ToDeviceColor(color)); 1339 } 1340 1341 /* static */ 1342 float SVGUtils::GetOpacity(const StyleSVGOpacity& aOpacity, 1343 const SVGContextPaint* aContextPaint) { 1344 float opacity = 1.0f; 1345 switch (aOpacity.tag) { 1346 case StyleSVGOpacity::Tag::Opacity: 1347 return aOpacity.AsOpacity(); 1348 case StyleSVGOpacity::Tag::ContextFillOpacity: 1349 if (aContextPaint) { 1350 opacity = aContextPaint->GetFillOpacity(); 1351 } 1352 break; 1353 case StyleSVGOpacity::Tag::ContextStrokeOpacity: 1354 if (aContextPaint) { 1355 opacity = aContextPaint->GetStrokeOpacity(); 1356 } 1357 break; 1358 } 1359 return opacity; 1360 } 1361 1362 bool SVGUtils::HasStroke(const nsIFrame* aFrame, 1363 const SVGContextPaint* aContextPaint) { 1364 const nsStyleSVG* style = aFrame->StyleSVG(); 1365 return style->HasStroke() && GetStrokeWidth(aFrame, aContextPaint) > 0; 1366 } 1367 1368 float SVGUtils::GetStrokeWidth(const nsIFrame* aFrame, 1369 const SVGContextPaint* aContextPaint) { 1370 nsIContent* content = aFrame->GetContent(); 1371 if (content->IsText()) { 1372 content = content->GetParent(); 1373 } 1374 1375 auto* ctx = SVGElement::FromNode(content); 1376 return SVGContentUtils::GetStrokeWidth(ctx, aFrame->Style(), aContextPaint); 1377 } 1378 1379 void SVGUtils::SetupStrokeGeometry(nsIFrame* aFrame, gfxContext* aContext, 1380 SVGContextPaint* aContextPaint) { 1381 MOZ_ASSERT(aFrame->GetContent()->IsSVGElement(), "bad cast"); 1382 SVGContentUtils::AutoStrokeOptions strokeOptions; 1383 SVGContentUtils::GetStrokeOptions(&strokeOptions, 1384 SVGElement::FromNode(aFrame->GetContent()), 1385 aFrame->Style(), aContextPaint); 1386 1387 if (strokeOptions.mLineWidth <= 0) { 1388 return; 1389 } 1390 1391 // SVGContentUtils::GetStrokeOptions gets the stroke options in CSS px; 1392 // convert to device pixels for gfxContext. 1393 float devPxPerCSSPx = aFrame->PresContext()->CSSToDevPixelScale().scale; 1394 1395 aContext->SetLineWidth(strokeOptions.mLineWidth * devPxPerCSSPx); 1396 aContext->SetLineCap(strokeOptions.mLineCap); 1397 aContext->SetMiterLimit(strokeOptions.mMiterLimit); 1398 aContext->SetLineJoin(strokeOptions.mLineJoin); 1399 aContext->SetDash(strokeOptions.mDashPattern, strokeOptions.mDashLength, 1400 strokeOptions.mDashOffset, devPxPerCSSPx); 1401 } 1402 1403 uint16_t SVGUtils::GetGeometryHitTestFlags(const nsIFrame* aFrame) { 1404 uint16_t flags = 0; 1405 1406 switch (aFrame->Style()->PointerEvents()) { 1407 case StylePointerEvents::None: 1408 break; 1409 case StylePointerEvents::Auto: 1410 case StylePointerEvents::Visiblepainted: 1411 if (aFrame->StyleVisibility()->IsVisible()) { 1412 if (!aFrame->StyleSVG()->mFill.kind.IsNone()) { 1413 flags = SVG_HIT_TEST_FILL; 1414 } 1415 if (!aFrame->StyleSVG()->mStroke.kind.IsNone()) { 1416 flags |= SVG_HIT_TEST_STROKE; 1417 } 1418 } 1419 break; 1420 case StylePointerEvents::Visiblefill: 1421 if (aFrame->StyleVisibility()->IsVisible()) { 1422 flags = SVG_HIT_TEST_FILL; 1423 } 1424 break; 1425 case StylePointerEvents::Visiblestroke: 1426 if (aFrame->StyleVisibility()->IsVisible()) { 1427 flags = SVG_HIT_TEST_STROKE; 1428 } 1429 break; 1430 case StylePointerEvents::Visible: 1431 if (aFrame->StyleVisibility()->IsVisible()) { 1432 flags = SVG_HIT_TEST_FILL | SVG_HIT_TEST_STROKE; 1433 } 1434 break; 1435 case StylePointerEvents::Painted: 1436 if (!aFrame->StyleSVG()->mFill.kind.IsNone()) { 1437 flags = SVG_HIT_TEST_FILL; 1438 } 1439 if (!aFrame->StyleSVG()->mStroke.kind.IsNone()) { 1440 flags |= SVG_HIT_TEST_STROKE; 1441 } 1442 break; 1443 case StylePointerEvents::Fill: 1444 flags = SVG_HIT_TEST_FILL; 1445 break; 1446 case StylePointerEvents::Stroke: 1447 flags = SVG_HIT_TEST_STROKE; 1448 break; 1449 case StylePointerEvents::All: 1450 flags = SVG_HIT_TEST_FILL | SVG_HIT_TEST_STROKE; 1451 break; 1452 default: 1453 NS_ERROR("not reached"); 1454 break; 1455 } 1456 1457 return flags; 1458 } 1459 1460 void SVGUtils::PaintSVGGlyph(Element* aElement, gfxContext* aContext) { 1461 nsIFrame* frame = aElement->GetPrimaryFrame(); 1462 ISVGDisplayableFrame* svgFrame = do_QueryFrame(frame); 1463 if (!svgFrame) { 1464 return; 1465 } 1466 gfxMatrix m; 1467 if (frame->GetContent()->IsSVGElement()) { 1468 // PaintSVG() expects the passed transform to be the transform to its own 1469 // SVG user space, so we need to account for any 'transform' attribute: 1470 m = SVGUtils::GetTransformMatrixInUserSpace(frame); 1471 } 1472 1473 // SVG-in-OpenType is not allowed to paint external resources, so we can 1474 // just pass a dummy params into PatintSVG. 1475 imgDrawingParams dummy; 1476 svgFrame->PaintSVG(*aContext, m, dummy); 1477 } 1478 1479 bool SVGUtils::GetSVGGlyphExtents(const Element* aElement, 1480 const gfxMatrix& aSVGToAppSpace, 1481 gfxRect* aResult) { 1482 nsIFrame* frame = aElement->GetPrimaryFrame(); 1483 ISVGDisplayableFrame* svgFrame = do_QueryFrame(frame); 1484 if (!svgFrame) { 1485 return false; 1486 } 1487 1488 gfxMatrix transform = aSVGToAppSpace; 1489 if (auto* svg = SVGElement::FromNode(frame->GetContent())) { 1490 transform = svg->ChildToUserSpaceTransform() * transform; 1491 } 1492 1493 *aResult = 1494 svgFrame 1495 ->GetBBoxContribution(gfx::ToMatrix(transform), 1496 SVGUtils::eBBoxIncludeFill | 1497 SVGUtils::eBBoxIncludeFillGeometry | 1498 SVGUtils::eBBoxIncludeStroke | 1499 SVGUtils::eBBoxIncludeStrokeGeometry | 1500 SVGUtils::eBBoxIncludeMarkers) 1501 .ToThebesRect(); 1502 return true; 1503 } 1504 1505 nsRect SVGUtils::ToCanvasBounds(const gfxRect& aUserspaceRect, 1506 const gfxMatrix& aToCanvas, 1507 const nsPresContext* presContext) { 1508 return nsLayoutUtils::RoundGfxRectToAppRect( 1509 aToCanvas.TransformBounds(aUserspaceRect), 1510 presContext->AppUnitsPerDevPixel()); 1511 } 1512 1513 gfxMatrix SVGUtils::GetCSSPxToDevPxMatrix(const nsIFrame* aNonSVGFrame) { 1514 float devPxPerCSSPx = aNonSVGFrame->PresContext()->CSSToDevPixelScale().scale; 1515 1516 return gfxMatrix(devPxPerCSSPx, 0.0, 0.0, devPxPerCSSPx, 0.0, 0.0); 1517 } 1518 1519 gfxMatrix SVGUtils::GetTransformMatrixInUserSpace(const nsIFrame* aFrame) { 1520 // We check element instead of aFrame directly because SVG element 1521 // may have non-SVG frame, <tspan> for example. 1522 MOZ_ASSERT(aFrame->GetContent() && aFrame->GetContent()->IsSVGElement(), 1523 "Only use this wrapper for SVG elements"); 1524 1525 if (!aFrame->IsTransformed()) { 1526 return {}; 1527 } 1528 1529 nsStyleTransformMatrix::TransformReferenceBox refBox(aFrame); 1530 nsDisplayTransform::FrameTransformProperties properties{ 1531 aFrame, refBox, AppUnitsPerCSSPixel()}; 1532 1533 // SVG elements can have x/y offset, their default transform origin 1534 // is the origin of user space, not the top left point of the frame. 1535 Point3D svgTransformOrigin{ 1536 properties.mToTransformOrigin.x - CSSPixel::FromAppUnits(refBox.X()), 1537 properties.mToTransformOrigin.y - CSSPixel::FromAppUnits(refBox.Y()), 1538 properties.mToTransformOrigin.z}; 1539 1540 Matrix svgTransform; 1541 Matrix4x4 trans; 1542 if (properties.HasTransform()) { 1543 trans = nsStyleTransformMatrix::ReadTransforms( 1544 properties.mTranslate, properties.mRotate, properties.mScale, 1545 properties.mMotion.ptrOr(nullptr), properties.mTransform, refBox, 1546 AppUnitsPerCSSPixel()); 1547 } 1548 1549 trans.ChangeBasis(svgTransformOrigin); 1550 1551 Matrix mm; 1552 trans.ProjectTo2D(); 1553 (void)trans.CanDraw2D(&mm); 1554 1555 return ThebesMatrix(mm); 1556 } 1557 1558 } // namespace mozilla