SVGGeometryFrame.cpp (31217B)
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 "SVGGeometryFrame.h" 9 10 // Keep others in (case-insensitive) order: 11 #include "SVGAnimatedTransformList.h" 12 #include "SVGMarkerFrame.h" 13 #include "gfx2DGlue.h" 14 #include "gfxContext.h" 15 #include "gfxPlatform.h" 16 #include "gfxUtils.h" 17 #include "mozilla/PresShell.h" 18 #include "mozilla/RefPtr.h" 19 #include "mozilla/SVGContentUtils.h" 20 #include "mozilla/SVGContextPaint.h" 21 #include "mozilla/SVGObserverUtils.h" 22 #include "mozilla/SVGUtils.h" 23 #include "mozilla/StaticPrefs_svg.h" 24 #include "mozilla/dom/SVGGeometryElement.h" 25 #include "mozilla/dom/SVGGraphicsElement.h" 26 #include "mozilla/gfx/2D.h" 27 #include "mozilla/gfx/Helpers.h" 28 #include "nsGkAtoms.h" 29 #include "nsLayoutUtils.h" 30 31 using namespace mozilla::dom; 32 using namespace mozilla::gfx; 33 using namespace mozilla::image; 34 35 //---------------------------------------------------------------------- 36 // Implementation 37 38 nsIFrame* NS_NewSVGGeometryFrame(mozilla::PresShell* aPresShell, 39 mozilla::ComputedStyle* aStyle) { 40 return new (aPresShell) 41 mozilla::SVGGeometryFrame(aStyle, aPresShell->GetPresContext()); 42 } 43 44 namespace mozilla { 45 46 NS_IMPL_FRAMEARENA_HELPERS(SVGGeometryFrame) 47 48 //---------------------------------------------------------------------- 49 // nsQueryFrame methods 50 51 NS_QUERYFRAME_HEAD(SVGGeometryFrame) 52 NS_QUERYFRAME_ENTRY(ISVGDisplayableFrame) 53 NS_QUERYFRAME_ENTRY(SVGGeometryFrame) 54 NS_QUERYFRAME_TAIL_INHERITING(nsIFrame) 55 56 //---------------------------------------------------------------------- 57 // nsIFrame methods 58 59 void SVGGeometryFrame::Init(nsIContent* aContent, nsContainerFrame* aParent, 60 nsIFrame* aPrevInFlow) { 61 AddStateBits(aParent->GetStateBits() & NS_STATE_SVG_CLIPPATH_CHILD); 62 nsIFrame::Init(aContent, aParent, aPrevInFlow); 63 } 64 65 nsresult SVGGeometryFrame::AttributeChanged(int32_t aNameSpaceID, 66 nsAtom* aAttribute, AttrModType) { 67 // We don't invalidate for transform changes (the layers code does that). 68 // Also note that SVGTransformableElement::GetAttributeChangeHint will 69 // return nsChangeHint_UpdateOverflow for "transform" attribute changes 70 // and cause DoApplyRenderingChangeToTree to make the SchedulePaint call. 71 72 if (aNameSpaceID == kNameSpaceID_None && 73 (static_cast<SVGGeometryElement*>(GetContent()) 74 ->AttributeDefinesGeometry(aAttribute))) { 75 nsLayoutUtils::PostRestyleEvent(mContent->AsElement(), RestyleHint{0}, 76 nsChangeHint_InvalidateRenderingObservers); 77 SVGUtils::ScheduleReflowSVG(this); 78 } 79 return NS_OK; 80 } 81 82 /* virtual */ 83 void SVGGeometryFrame::DidSetComputedStyle(ComputedStyle* aOldComputedStyle) { 84 nsIFrame::DidSetComputedStyle(aOldComputedStyle); 85 if (StyleSVGReset()->HasNonScalingStroke() && 86 (!aOldComputedStyle || 87 !aOldComputedStyle->StyleSVGReset()->HasNonScalingStroke())) { 88 SVGUtils::UpdateNonScalingStrokeStateBit(this); 89 } 90 auto* element = static_cast<SVGGeometryElement*>(GetContent()); 91 if (!aOldComputedStyle) { 92 element->ClearAnyCachedPath(); 93 return; 94 } 95 96 const auto* oldStyleSVG = aOldComputedStyle->StyleSVG(); 97 if (!SVGContentUtils::ShapeTypeHasNoCorners(GetContent())) { 98 if (StyleSVG()->mStrokeLinecap != oldStyleSVG->mStrokeLinecap && 99 element->IsSVGElement(nsGkAtoms::path)) { 100 // If the stroke-linecap changes to or from "butt" then our element 101 // needs to update its cached Moz2D Path, since SVGPathData::BuildPath 102 // decides whether or not to insert little lines into the path for zero 103 // length subpaths base on that property. 104 element->ClearAnyCachedPath(); 105 } else if (HasAnyStateBits(NS_STATE_SVG_CLIPPATH_CHILD)) { 106 if (StyleSVG()->mClipRule != oldStyleSVG->mClipRule) { 107 // Moz2D Path objects are fill-rule specific. 108 // For clipPath we use clip-rule as the path's fill-rule. 109 element->ClearAnyCachedPath(); 110 } 111 } else if (StyleSVG()->mFillRule != oldStyleSVG->mFillRule) { 112 // Moz2D Path objects are fill-rule specific. 113 element->ClearAnyCachedPath(); 114 } 115 } 116 117 if (StyleDisplay()->CalcTransformPropertyDifference( 118 *aOldComputedStyle->StyleDisplay())) { 119 NotifySVGChanged(ChangeFlag::TransformChanged); 120 } 121 122 if (element->IsGeometryChangedViaCSS(*Style(), *aOldComputedStyle) || 123 aOldComputedStyle->EffectiveZoom() != Style()->EffectiveZoom()) { 124 element->ClearAnyCachedPath(); 125 SVGObserverUtils::InvalidateRenderingObservers(this); 126 } 127 } 128 129 bool SVGGeometryFrame::DoGetParentSVGTransforms( 130 gfx::Matrix* aFromParentTransform) const { 131 return SVGUtils::GetParentSVGTransforms(this, aFromParentTransform); 132 } 133 134 void SVGGeometryFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder, 135 const nsDisplayListSet& aLists) { 136 if (!static_cast<const SVGElement*>(GetContent())->HasValidDimensions()) { 137 return; 138 } 139 140 if (aBuilder->IsForPainting()) { 141 if (!IsVisibleForPainting()) { 142 return; 143 } 144 if (StyleEffects()->IsTransparent() && SVGUtils::CanOptimizeOpacity(this)) { 145 return; 146 } 147 const auto* styleSVG = StyleSVG(); 148 if (styleSVG->mFill.kind.IsNone() && styleSVG->mStroke.kind.IsNone() && 149 !styleSVG->HasMarker()) { 150 return; 151 } 152 153 aBuilder->BuildCompositorHitTestInfoIfNeeded(this, 154 aLists.BorderBackground()); 155 } 156 157 DisplayOutline(aBuilder, aLists); 158 aLists.Content()->AppendNewToTop<DisplaySVGGeometry>(aBuilder, this); 159 } 160 161 //---------------------------------------------------------------------- 162 // ISVGDisplayableFrame methods 163 164 void SVGGeometryFrame::PaintSVG(gfxContext& aContext, 165 const gfxMatrix& aTransform, 166 imgDrawingParams& aImgParams) { 167 if (!StyleVisibility()->IsVisible()) { 168 return; 169 } 170 171 // Matrix to the geometry's user space: 172 gfxMatrix newMatrix = 173 aContext.CurrentMatrixDouble().PreMultiply(aTransform).NudgeToIntegers(); 174 if (newMatrix.IsSingular()) { 175 return; 176 } 177 178 uint32_t paintOrder = StyleSVG()->mPaintOrder; 179 if (!paintOrder) { 180 Render(&aContext, RenderFlags(RenderFlag::Fill, RenderFlag::Stroke), 181 newMatrix, aImgParams); 182 PaintMarkers(aContext, aTransform, aImgParams); 183 } else { 184 while (paintOrder) { 185 auto component = StylePaintOrder(paintOrder & kPaintOrderMask); 186 switch (component) { 187 case StylePaintOrder::Fill: 188 Render(&aContext, RenderFlag::Fill, newMatrix, aImgParams); 189 break; 190 case StylePaintOrder::Stroke: 191 Render(&aContext, RenderFlag::Stroke, newMatrix, aImgParams); 192 break; 193 case StylePaintOrder::Markers: 194 PaintMarkers(aContext, aTransform, aImgParams); 195 break; 196 default: 197 MOZ_FALLTHROUGH_ASSERT("Unknown paint-order variant, how?"); 198 case StylePaintOrder::Normal: 199 break; 200 } 201 paintOrder >>= kPaintOrderShift; 202 } 203 } 204 } 205 206 nsIFrame* SVGGeometryFrame::GetFrameForPoint(const gfxPoint& aPoint) { 207 FillRule fillRule; 208 uint16_t hitTestFlags; 209 if (HasAnyStateBits(NS_STATE_SVG_CLIPPATH_CHILD)) { 210 hitTestFlags = SVG_HIT_TEST_FILL; 211 fillRule = SVGUtils::ToFillRule(StyleSVG()->mClipRule); 212 } else { 213 hitTestFlags = SVGUtils::GetGeometryHitTestFlags(this); 214 if (!hitTestFlags) { 215 return nullptr; 216 } 217 fillRule = SVGUtils::ToFillRule(StyleSVG()->mFillRule); 218 } 219 220 bool isHit = false; 221 222 SVGGeometryElement* content = static_cast<SVGGeometryElement*>(GetContent()); 223 224 // Using ScreenReferenceDrawTarget() opens us to Moz2D backend specific hit- 225 // testing bugs. Maybe we should use a BackendType::CAIRO DT for hit-testing 226 // so that we get more consistent/backwards compatible results? 227 RefPtr<DrawTarget> drawTarget = 228 gfxPlatform::GetPlatform()->ScreenReferenceDrawTarget(); 229 RefPtr<Path> path = content->GetOrBuildPath(drawTarget, fillRule); 230 if (!path) { 231 return nullptr; // no path, so we don't paint anything that can be hit 232 } 233 234 if (hitTestFlags & SVG_HIT_TEST_FILL) { 235 isHit = path->ContainsPoint(ToPoint(aPoint), {}); 236 } 237 if (!isHit && (hitTestFlags & SVG_HIT_TEST_STROKE)) { 238 Point point = ToPoint(aPoint); 239 SVGContentUtils::AutoStrokeOptions stroke; 240 SVGContentUtils::GetStrokeOptions(&stroke, content, Style(), nullptr); 241 gfxMatrix userToOuterSVG; 242 if (SVGUtils::GetNonScalingStrokeTransform(this, &userToOuterSVG)) { 243 // We need to transform the path back into the appropriate ancestor 244 // coordinate system in order for non-scaled stroke to be correct. 245 // Naturally we also need to transform the point into the same 246 // coordinate system in order to hit-test against the path. 247 point = ToMatrix(userToOuterSVG).TransformPoint(point); 248 Path::TransformAndSetFillRule(path, ToMatrix(userToOuterSVG), fillRule); 249 } 250 isHit = path->StrokeContainsPoint(stroke, point, {}); 251 } 252 253 if (isHit && SVGUtils::HitTestClip(this, aPoint)) { 254 return this; 255 } 256 257 return nullptr; 258 } 259 260 void SVGGeometryFrame::ReflowSVG() { 261 NS_ASSERTION(SVGUtils::OuterSVGIsCallingReflowSVG(this), 262 "This call is probably a wasteful mistake"); 263 264 MOZ_ASSERT(!HasAnyStateBits(NS_FRAME_IS_NONDISPLAY), 265 "ReflowSVG mechanism not designed for this"); 266 267 if (!SVGUtils::NeedsReflowSVG(this)) { 268 return; 269 } 270 271 uint32_t flags = SVGUtils::eBBoxIncludeFillGeometry | 272 SVGUtils::eBBoxIncludeStroke | SVGUtils::eBBoxIncludeMarkers; 273 274 // Our "visual" overflow rect needs to be valid for building display lists 275 // for hit testing, which means that for certain values of 'pointer-events' 276 // it needs to include the geometry of the fill or stroke even when the fill/ 277 // stroke don't actually render (e.g. when stroke="none" or 278 // stroke-opacity="0"). GetGeometryHitTestFlags() accounts for 279 // 'pointer-events'. 280 uint16_t hitTestFlags = SVGUtils::GetGeometryHitTestFlags(this); 281 if (hitTestFlags & SVG_HIT_TEST_FILL) { 282 flags |= SVGUtils::eBBoxIncludeFillGeometry; 283 } 284 if (hitTestFlags & SVG_HIT_TEST_STROKE) { 285 flags |= SVGUtils::eBBoxIncludeStrokeGeometry; 286 } 287 288 SVGBBox extent = GetBBoxContribution({}, flags).ToThebesRect(); 289 mRect = nsLayoutUtils::RoundGfxRectToAppRect((const Rect&)extent, 290 AppUnitsPerCSSPixel()); 291 292 if (HasAnyStateBits(NS_FRAME_FIRST_REFLOW)) { 293 // Make sure we have our filter property (if any) before calling 294 // FinishAndStoreOverflow (subsequent filter changes are handled off 295 // nsChangeHint_UpdateEffects): 296 SVGObserverUtils::UpdateEffects(this); 297 } 298 299 nsRect overflow = nsRect(nsPoint(0, 0), mRect.Size()); 300 OverflowAreas overflowAreas(overflow, overflow); 301 FinishAndStoreOverflow(overflowAreas, mRect.Size()); 302 303 RemoveStateBits(NS_FRAME_FIRST_REFLOW | NS_FRAME_IS_DIRTY | 304 NS_FRAME_HAS_DIRTY_CHILDREN); 305 306 // Invalidate, but only if this is not our first reflow (since if it is our 307 // first reflow then we haven't had our first paint yet). 308 if (!GetParent()->HasAnyStateBits(NS_FRAME_FIRST_REFLOW)) { 309 InvalidateFrame(); 310 } 311 } 312 313 void SVGGeometryFrame::NotifySVGChanged(ChangeFlags aFlags) { 314 MOZ_ASSERT(aFlags.contains(ChangeFlag::TransformChanged) || 315 aFlags.contains(ChangeFlag::CoordContextChanged), 316 "Invalidation logic may need adjusting"); 317 318 // Changes to our ancestors may affect how we render when we are rendered as 319 // part of our ancestor (specifically, if our coordinate context changes size 320 // and we have percentage lengths defining our geometry, then we need to be 321 // reflowed). However, ancestor changes cannot affect how we render when we 322 // are rendered as part of any rendering observers that we may have. 323 // Therefore no need to notify rendering observers here. 324 325 // Don't try to be too smart trying to avoid the ScheduleReflowSVG calls 326 // for the stroke properties examined below. Checking HasStroke() is not 327 // enough, since what we care about is whether we include the stroke in our 328 // overflow rects or not, and we sometimes deliberately include stroke 329 // when it's not visible. See the complexities of GetBBoxContribution. 330 331 if (aFlags.contains(ChangeFlag::CoordContextChanged)) { 332 auto* geom = static_cast<SVGGeometryElement*>(GetContent()); 333 // Stroke currently contributes to our mRect, which is why we have to take 334 // account of stroke-width here. Note that we do not need to take account 335 // of stroke-dashoffset since, although that can have a percentage value 336 // that is resolved against our coordinate context, it does not affect our 337 // mRect. 338 const auto& strokeWidth = StyleSVG()->mStrokeWidth; 339 if (geom->GeometryDependsOnCoordCtx() || 340 (strokeWidth.IsLengthPercentage() && 341 strokeWidth.AsLengthPercentage().HasPercent())) { 342 geom->ClearAnyCachedPath(); 343 SVGUtils::ScheduleReflowSVG(this); 344 } 345 } 346 347 if (aFlags.contains(ChangeFlag::TransformChanged) && 348 StyleSVGReset()->HasNonScalingStroke()) { 349 // Stroke currently contributes to our mRect, and our stroke depends on 350 // the transform to our outer-<svg> if |vector-effect:non-scaling-stroke|. 351 SVGUtils::ScheduleReflowSVG(this); 352 } 353 } 354 355 SVGBBox SVGGeometryFrame::GetBBoxContribution(const Matrix& aToBBoxUserspace, 356 uint32_t aFlags) { 357 SVGBBox bbox; 358 359 if (aToBBoxUserspace.IsSingular()) { 360 // XXX ReportToConsole 361 return bbox; 362 } 363 364 if ((aFlags & SVGUtils::eForGetClientRects) && 365 aToBBoxUserspace.PreservesAxisAlignedRectangles()) { 366 if (!mRect.IsEmpty()) { 367 Rect rect = NSRectToRect(mRect, AppUnitsPerCSSPixel()); 368 bbox = aToBBoxUserspace.TransformBounds(rect); 369 } 370 return bbox; 371 } 372 373 SVGGeometryElement* element = static_cast<SVGGeometryElement*>(GetContent()); 374 375 const bool getFill = (aFlags & SVGUtils::eBBoxIncludeFillGeometry) || 376 ((aFlags & SVGUtils::eBBoxIncludeFill) && 377 !StyleSVG()->mFill.kind.IsNone()); 378 379 const bool getStroke = 380 ((aFlags & SVGUtils::eBBoxIncludeStrokeGeometry) || 381 ((aFlags & SVGUtils::eBBoxIncludeStroke) && 382 SVGUtils::HasStroke(this))) && 383 // If this frame has non-scaling-stroke and we would like to compute its 384 // stroke, it may cause a potential cyclical dependency if the caller is 385 // for transform. In this case, we have to fall back to fill-box, so make 386 // |getStroke| be false. 387 // https://github.com/w3c/csswg-drafts/issues/9640 388 // 389 // Note: 390 // 1. We don't care about the computation of the markers below in this 391 // function because we know the callers don't set 392 // SVGUtils::eBBoxIncludeMarkers. 393 // See nsStyleTransformMatrix::GetSVGBox() and 394 // MotionPathUtils::GetRayContainReferenceSize() for more details. 395 // 2. We have to break the dependency here *again* because the geometry 396 // frame may be in the subtree of a SVGContainerFrame, which may not 397 // set non-scaling-stroke. 398 !(StyleSVGReset()->HasNonScalingStroke() && 399 (aFlags & SVGUtils::eAvoidCycleIfNonScalingStroke)); 400 401 SVGContentUtils::AutoStrokeOptions strokeOptions; 402 if (getStroke) { 403 SVGContentUtils::GetStrokeOptions( 404 &strokeOptions, element, Style(), nullptr, 405 SVGContentUtils::StrokeOptionFlag::IgnoreStrokeDashing); 406 } else { 407 // Override the default line width of 1.f so that when we call 408 // GetGeometryBounds below the result doesn't include stroke bounds. 409 strokeOptions.mLineWidth = 0.f; 410 } 411 412 Rect simpleBounds; 413 bool gotSimpleBounds = false; 414 gfxMatrix userToOuterSVG; 415 if (getStroke && 416 SVGUtils::GetNonScalingStrokeTransform(this, &userToOuterSVG)) { 417 Matrix moz2dUserToOuterSVG = ToMatrix(userToOuterSVG); 418 if (moz2dUserToOuterSVG.IsSingular()) { 419 return bbox; 420 } 421 gotSimpleBounds = element->GetGeometryBounds( 422 &simpleBounds, strokeOptions, aToBBoxUserspace, &moz2dUserToOuterSVG); 423 } else if (getFill || getStroke) { 424 gotSimpleBounds = element->GetGeometryBounds(&simpleBounds, strokeOptions, 425 aToBBoxUserspace); 426 } 427 428 if (gotSimpleBounds) { 429 bbox = simpleBounds; 430 } else { 431 RefPtr<Path> pathInBBoxSpace; 432 RefPtr<Path> pathInUserSpace; 433 if (getFill || getStroke) { 434 // Get the bounds using a Moz2D Path object (more expensive): 435 RefPtr<DrawTarget> tmpDT; 436 tmpDT = gfxPlatform::GetPlatform()->ScreenReferenceDrawTarget(); 437 438 FillRule fillRule = SVGUtils::ToFillRule( 439 HasAnyStateBits(NS_STATE_SVG_CLIPPATH_CHILD) ? StyleSVG()->mClipRule 440 : StyleSVG()->mFillRule); 441 pathInUserSpace = element->GetOrBuildPath(tmpDT, fillRule); 442 if (!pathInUserSpace) { 443 return bbox; 444 } 445 if (aToBBoxUserspace.IsIdentity()) { 446 pathInBBoxSpace = pathInUserSpace; 447 } else { 448 RefPtr<PathBuilder> builder = pathInUserSpace->TransformedCopyToBuilder( 449 aToBBoxUserspace, fillRule); 450 pathInBBoxSpace = builder->Finish(); 451 if (!pathInBBoxSpace) { 452 return bbox; 453 } 454 } 455 } 456 457 // Account for fill: 458 if (getFill && !getStroke) { 459 Rect pathBBoxExtents = pathInBBoxSpace->GetBounds(); 460 if (!pathBBoxExtents.IsFinite()) { 461 // This can happen in the case that we only have a move-to command in 462 // the path commands, in which case we know nothing gets rendered. 463 return bbox; 464 } 465 bbox = pathBBoxExtents; 466 } 467 468 // Account for stroke: 469 if (getStroke) { 470 // Be careful when replacing the following logic to get the fill and 471 // stroke extents independently. 472 // You may think that you can just use the stroke extents if 473 // there is both a fill and a stroke. In reality it may be necessary to 474 // calculate both the fill and stroke extents. 475 // There are two reasons for this: 476 // 477 // # Due to stroke dashing, in certain cases the fill extents could 478 // actually extend outside the stroke extents. 479 // # If the stroke is very thin, cairo won't paint any stroke, and so the 480 // stroke bounds that it will return will be empty. 481 482 Rect strokeBBoxExtents; 483 if (StaticPrefs::svg_Moz2D_strokeBounds_enabled()) { 484 gfxMatrix userToOuterSVG; 485 if (SVGUtils::GetNonScalingStrokeTransform(this, &userToOuterSVG)) { 486 Matrix outerSVGToUser = ToMatrix(userToOuterSVG); 487 outerSVGToUser.Invert(); 488 Matrix outerSVGToBBox = aToBBoxUserspace * outerSVGToUser; 489 RefPtr<PathBuilder> builder = 490 pathInUserSpace->TransformedCopyToBuilder( 491 ToMatrix(userToOuterSVG)); 492 RefPtr<Path> pathInOuterSVGSpace = builder->Finish(); 493 strokeBBoxExtents = pathInOuterSVGSpace->GetStrokedBounds( 494 strokeOptions, outerSVGToBBox); 495 } else { 496 strokeBBoxExtents = pathInUserSpace->GetStrokedBounds( 497 strokeOptions, aToBBoxUserspace); 498 } 499 if (strokeBBoxExtents.IsEmpty() && getFill) { 500 strokeBBoxExtents = pathInBBoxSpace->GetBounds(); 501 if (!strokeBBoxExtents.IsFinite()) { 502 return bbox; 503 } 504 } 505 } else { 506 Rect pathBBoxExtents = pathInBBoxSpace->GetBounds(); 507 if (!pathBBoxExtents.IsFinite()) { 508 return bbox; 509 } 510 strokeBBoxExtents = ToRect(SVGUtils::PathExtentsToMaxStrokeExtents( 511 ThebesRect(pathBBoxExtents), this, ThebesMatrix(aToBBoxUserspace))); 512 } 513 MOZ_ASSERT(strokeBBoxExtents.IsFinite(), "bbox is about to go bad"); 514 bbox.UnionEdges(strokeBBoxExtents); 515 } 516 } 517 518 // Account for markers: 519 if ((aFlags & SVGUtils::eBBoxIncludeMarkers) && element->IsMarkable()) { 520 SVGMarkerFrame* markerFrames[SVGMark::eTypeCount]; 521 if (SVGObserverUtils::GetAndObserveMarkers(this, &markerFrames)) { 522 nsTArray<SVGMark> marks; 523 element->GetMarkPoints(&marks); 524 if (uint32_t num = marks.Length()) { 525 float strokeWidth = SVGUtils::GetStrokeWidth(this); 526 for (uint32_t i = 0; i < num; i++) { 527 const SVGMark& mark = marks[i]; 528 SVGMarkerFrame* frame = markerFrames[mark.type]; 529 if (frame) { 530 SVGBBox mbbox = frame->GetMarkBBoxContribution( 531 aToBBoxUserspace, aFlags, this, mark, strokeWidth); 532 MOZ_ASSERT(mbbox.IsFinite(), "bbox is about to go bad"); 533 bbox.UnionEdges(mbbox); 534 } 535 } 536 } 537 } 538 } 539 540 return bbox; 541 } 542 543 //---------------------------------------------------------------------- 544 // SVGGeometryFrame methods: 545 546 gfxMatrix SVGGeometryFrame::GetCanvasTM() { 547 NS_ASSERTION(GetParent(), "null parent"); 548 549 auto* parent = static_cast<SVGContainerFrame*>(GetParent()); 550 auto* content = static_cast<SVGGraphicsElement*>(GetContent()); 551 return content->ChildToUserSpaceTransform() * parent->GetCanvasTM(); 552 } 553 554 void SVGGeometryFrame::Render(gfxContext* aContext, 555 RenderFlags aRenderComponents, 556 const gfxMatrix& aTransform, 557 imgDrawingParams& aImgParams) { 558 MOZ_ASSERT(!aTransform.IsSingular()); 559 560 DrawTarget* drawTarget = aContext->GetDrawTarget(); 561 562 MOZ_ASSERT(drawTarget); 563 if (!drawTarget->IsValid()) { 564 return; 565 } 566 567 FillRule fillRule = SVGUtils::ToFillRule( 568 HasAnyStateBits(NS_STATE_SVG_CLIPPATH_CHILD) ? StyleSVG()->mClipRule 569 : StyleSVG()->mFillRule); 570 571 SVGGeometryElement* element = static_cast<SVGGeometryElement*>(GetContent()); 572 573 AntialiasMode aaMode = SVGUtils::ToAntialiasMode(StyleSVG()->mShapeRendering); 574 575 // We wait as late as possible before setting the transform so that we don't 576 // set it unnecessarily if we return early (it's an expensive operation for 577 // some backends). 578 gfxContextMatrixAutoSaveRestore autoRestoreTransform(aContext); 579 aContext->SetMatrixDouble(aTransform); 580 581 if (HasAnyStateBits(NS_STATE_SVG_CLIPPATH_CHILD)) { 582 // We don't complicate this code with GetAsSimplePath since the cost of 583 // masking will dwarf Path creation overhead anyway. 584 RefPtr<Path> path = element->GetOrBuildPath(drawTarget, fillRule); 585 if (path) { 586 ColorPattern white(ToDeviceColor(sRGBColor(1.0f, 1.0f, 1.0f, 1.0f))); 587 drawTarget->Fill(path, white, 588 DrawOptions(1.0f, CompositionOp::OP_OVER, aaMode)); 589 } 590 return; 591 } 592 593 SVGGeometryElement::SimplePath simplePath; 594 RefPtr<Path> path; 595 596 element->GetAsSimplePath(&simplePath); 597 if (!simplePath.IsPath()) { 598 path = element->GetOrBuildPath(drawTarget, fillRule); 599 if (!path) { 600 return; 601 } 602 } 603 604 SVGContextPaint* contextPaint = 605 SVGContextPaint::GetContextPaint(GetContent()); 606 607 if (aRenderComponents.contains(RenderFlag::Fill)) { 608 GeneralPattern fillPattern; 609 SVGUtils::MakeFillPatternFor(this, aContext, &fillPattern, aImgParams, 610 contextPaint); 611 612 if (fillPattern.GetPattern()) { 613 DrawOptions drawOptions(1.0f, CompositionOp::OP_OVER, aaMode); 614 if (simplePath.IsRect()) { 615 drawTarget->FillRect(simplePath.AsRect(), fillPattern, drawOptions); 616 } else if (path) { 617 drawTarget->Fill(path, fillPattern, drawOptions); 618 } 619 } 620 } 621 622 if (aRenderComponents.contains(RenderFlag::Stroke) && 623 SVGUtils::HasStroke(this, contextPaint)) { 624 // Account for vector-effect:non-scaling-stroke: 625 gfxMatrix userToOuterSVG; 626 if (SVGUtils::GetNonScalingStrokeTransform(this, &userToOuterSVG)) { 627 // A simple Rect can't be transformed with rotate/skew, so let's switch 628 // to using a real path: 629 if (!path) { 630 path = element->GetOrBuildPath(drawTarget, fillRule); 631 if (!path) { 632 return; 633 } 634 simplePath.Reset(); 635 } 636 // We need to transform the path back into the appropriate ancestor 637 // coordinate system, and paint it it that coordinate system, in order 638 // for non-scaled stroke to paint correctly. 639 gfxMatrix outerSVGToUser = userToOuterSVG; 640 outerSVGToUser.Invert(); 641 aContext->Multiply(outerSVGToUser); 642 Path::TransformAndSetFillRule(path, ToMatrix(userToOuterSVG), fillRule); 643 } 644 GeneralPattern strokePattern; 645 SVGUtils::MakeStrokePatternFor(this, aContext, &strokePattern, aImgParams, 646 contextPaint); 647 648 if (strokePattern.GetPattern()) { 649 SVGContentUtils::AutoStrokeOptions strokeOptions; 650 SVGContentUtils::GetStrokeOptions(&strokeOptions, 651 static_cast<SVGElement*>(GetContent()), 652 Style(), contextPaint); 653 // GetStrokeOptions may set the line width to zero as an optimization 654 if (strokeOptions.mLineWidth <= 0) { 655 return; 656 } 657 DrawOptions drawOptions(1.0f, CompositionOp::OP_OVER, aaMode); 658 if (simplePath.IsRect()) { 659 drawTarget->StrokeRect(simplePath.AsRect(), strokePattern, 660 strokeOptions, drawOptions); 661 } else if (simplePath.IsLine()) { 662 drawTarget->StrokeLine(simplePath.Point1(), simplePath.Point2(), 663 strokePattern, strokeOptions, drawOptions); 664 } else { 665 drawTarget->Stroke(path, strokePattern, strokeOptions, drawOptions); 666 } 667 } 668 } 669 } 670 671 bool SVGGeometryFrame::IsInvisible() const { 672 if (!StyleVisibility()->IsVisible()) { 673 return true; 674 } 675 676 // Anything below will round to zero later down the pipeline. 677 constexpr float opacity_threshold = 1.0 / 128.0; 678 679 if (StyleEffects()->mOpacity <= opacity_threshold && 680 SVGUtils::CanOptimizeOpacity(this)) { 681 return true; 682 } 683 684 const nsStyleSVG* style = StyleSVG(); 685 SVGContextPaint* contextPaint = 686 SVGContextPaint::GetContextPaint(GetContent()); 687 688 if (!style->mFill.kind.IsNone()) { 689 float opacity = SVGUtils::GetOpacity(style->mFillOpacity, contextPaint); 690 if (opacity > opacity_threshold) { 691 return false; 692 } 693 } 694 695 if (!style->mStroke.kind.IsNone()) { 696 float opacity = SVGUtils::GetOpacity(style->mStrokeOpacity, contextPaint); 697 if (opacity > opacity_threshold) { 698 return false; 699 } 700 } 701 702 if (style->HasMarker()) { 703 return false; 704 } 705 706 return true; 707 } 708 709 bool SVGGeometryFrame::CreateWebRenderCommands( 710 mozilla::wr::DisplayListBuilder& aBuilder, 711 mozilla::wr::IpcResourceUpdateQueue& aResources, 712 const mozilla::layers::StackingContextHelper& aSc, 713 mozilla::layers::RenderRootStateManager* aManager, 714 nsDisplayListBuilder* aDisplayListBuilder, DisplaySVGGeometry* aItem, 715 bool aDryRun) { 716 MOZ_ASSERT(StyleVisibility()->IsVisible()); 717 718 SVGGeometryElement* element = static_cast<SVGGeometryElement*>(GetContent()); 719 720 SVGGeometryElement::SimplePath simplePath; 721 element->GetAsSimplePath(&simplePath); 722 723 if (!simplePath.IsRect()) { 724 return false; 725 } 726 727 const nsStyleSVG* style = StyleSVG(); 728 MOZ_ASSERT(style); 729 730 if (!style->mFill.kind.IsColor()) { 731 return false; 732 } 733 734 switch (style->mFill.kind.tag) { 735 case StyleSVGPaintKind::Tag::Color: 736 break; 737 default: 738 return false; 739 } 740 741 if (!style->mStroke.kind.IsNone()) { 742 return false; 743 } 744 745 if (StyleEffects()->HasMixBlendMode()) { 746 // FIXME: not implemented 747 return false; 748 } 749 750 if (style->HasMarker() && element->IsMarkable()) { 751 // Markers aren't suppported yet. 752 return false; 753 } 754 755 if (!aDryRun) { 756 auto appUnitsPerDevPx = PresContext()->AppUnitsPerDevPixel(); 757 float scale = (float)AppUnitsPerCSSPixel() / (float)appUnitsPerDevPx; 758 759 auto rect = simplePath.AsRect(); 760 rect.Scale(scale); 761 762 auto offset = LayoutDevicePoint::FromAppUnits( 763 aItem->ToReferenceFrame() - GetPosition(), appUnitsPerDevPx); 764 rect.MoveBy(offset.x, offset.y); 765 766 auto wrRect = wr::ToLayoutRect(rect); 767 768 SVGContextPaint* contextPaint = 769 SVGContextPaint::GetContextPaint(GetContent()); 770 // At the moment this code path doesn't support strokes so it fine to 771 // combine the rectangle's opacity (which has to be applied on the result) 772 // of (filling + stroking) with the fill opacity. 773 774 float elemOpacity = 1.0f; 775 if (SVGUtils::CanOptimizeOpacity(this)) { 776 elemOpacity = StyleEffects()->mOpacity; 777 } 778 779 float fillOpacity = SVGUtils::GetOpacity(style->mFillOpacity, contextPaint); 780 float opacity = elemOpacity * fillOpacity; 781 782 auto color = wr::ToColorF( 783 ToDeviceColor(StyleSVG()->mFill.kind.AsColor().CalcColor(this))); 784 color.a *= opacity; 785 aBuilder.PushRect(wrRect, wrRect, !aItem->BackfaceIsHidden(), true, false, 786 color); 787 } 788 789 return true; 790 } 791 792 void SVGGeometryFrame::PaintMarkers(gfxContext& aContext, 793 const gfxMatrix& aTransform, 794 imgDrawingParams& aImgParams) { 795 auto* element = static_cast<SVGGeometryElement*>(GetContent()); 796 if (!element->IsMarkable()) { 797 return; 798 } 799 SVGMarkerFrame* markerFrames[SVGMark::eTypeCount]; 800 if (!SVGObserverUtils::GetAndObserveMarkers(this, &markerFrames)) { 801 return; 802 } 803 nsTArray<SVGMark> marks; 804 element->GetMarkPoints(&marks); 805 if (marks.IsEmpty()) { 806 return; 807 } 808 float strokeWidth = GetStrokeWidthForMarkers(); 809 for (const SVGMark& mark : marks) { 810 if (auto* frame = markerFrames[mark.type]) { 811 frame->PaintMark(aContext, aTransform, this, mark, strokeWidth, 812 aImgParams); 813 } 814 } 815 } 816 817 float SVGGeometryFrame::GetStrokeWidthForMarkers() { 818 float strokeWidth = SVGUtils::GetStrokeWidth( 819 this, SVGContextPaint::GetContextPaint(GetContent())); 820 gfxMatrix userToOuterSVG; 821 if (SVGUtils::GetNonScalingStrokeTransform(this, &userToOuterSVG)) { 822 // We're not interested in any translation here so we can treat this as 823 // Singular Value Decomposition (SVD) of a 2 x 2 matrix. That would give us 824 // sx and sy values as the X and Y scales. The value we want is the XY 825 // scale i.e. the normalised hypotenuse, which is sqrt(sx^2 + sy^2) / 826 // sqrt(2). If we use the formulae from 827 // https://scicomp.stackexchange.com/a/14103, we discover that the 828 // normalised hypotenuse is simply the square root of the sum of the squares 829 // of all the 2D matrix elements divided by sqrt(2). 830 // 831 // Note that this may need adjusting to support 3D transforms properly. 832 833 strokeWidth /= float(sqrt(userToOuterSVG._11 * userToOuterSVG._11 + 834 userToOuterSVG._12 * userToOuterSVG._12 + 835 userToOuterSVG._21 * userToOuterSVG._21 + 836 userToOuterSVG._22 * userToOuterSVG._22) / 837 M_SQRT2); 838 } 839 return strokeWidth; 840 } 841 842 } // namespace mozilla