Bug 1814091 - Move CanvasContext.getPreferredFormat to GPU.getPreferredCanvasFormat...
[gecko.git] / layout / svg / SVGUtils.cpp
blob3bf7e0bd57161286e7d0c99a39f556eb0d793114
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/. */
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 #include <algorithm>
12 // Keep others in (case-insensitive) order:
13 #include "gfx2DGlue.h"
14 #include "gfxContext.h"
15 #include "gfxMatrix.h"
16 #include "gfxPlatform.h"
17 #include "gfxRect.h"
18 #include "gfxUtils.h"
19 #include "nsCSSFrameConstructor.h"
20 #include "nsDisplayList.h"
21 #include "nsFrameList.h"
22 #include "nsGkAtoms.h"
23 #include "nsIContent.h"
24 #include "nsIFrame.h"
25 #include "nsIFrameInlines.h"
26 #include "nsLayoutUtils.h"
27 #include "nsPresContext.h"
28 #include "nsStyleStruct.h"
29 #include "nsStyleTransformMatrix.h"
30 #include "SVGAnimatedLength.h"
31 #include "SVGPaintServerFrame.h"
32 #include "nsTextFrame.h"
33 #include "mozilla/CSSClipPathInstance.h"
34 #include "mozilla/FilterInstance.h"
35 #include "mozilla/ISVGDisplayableFrame.h"
36 #include "mozilla/Preferences.h"
37 #include "mozilla/StaticPrefs_svg.h"
38 #include "mozilla/SVGClipPathFrame.h"
39 #include "mozilla/SVGContainerFrame.h"
40 #include "mozilla/SVGContentUtils.h"
41 #include "mozilla/SVGContextPaint.h"
42 #include "mozilla/SVGForeignObjectFrame.h"
43 #include "mozilla/SVGIntegrationUtils.h"
44 #include "mozilla/SVGGeometryFrame.h"
45 #include "mozilla/SVGMaskFrame.h"
46 #include "mozilla/SVGObserverUtils.h"
47 #include "mozilla/SVGOuterSVGFrame.h"
48 #include "mozilla/SVGTextFrame.h"
49 #include "mozilla/Unused.h"
50 #include "mozilla/gfx/2D.h"
51 #include "mozilla/gfx/PatternHelpers.h"
52 #include "mozilla/dom/Document.h"
53 #include "mozilla/dom/SVGClipPathElement.h"
54 #include "mozilla/dom/SVGGeometryElement.h"
55 #include "mozilla/dom/SVGPathElement.h"
56 #include "mozilla/dom/SVGUnitTypesBinding.h"
57 #include "mozilla/dom/SVGViewportElement.h"
59 using namespace mozilla::dom;
60 using namespace mozilla::dom::SVGUnitTypes_Binding;
61 using namespace mozilla::gfx;
62 using namespace mozilla::image;
64 bool NS_SVGNewGetBBoxEnabled() {
65 return mozilla::StaticPrefs::svg_new_getBBox_enabled();
68 namespace mozilla {
70 // we only take the address of this:
71 static gfx::UserDataKey sSVGAutoRenderStateKey;
73 SVGAutoRenderState::SVGAutoRenderState(DrawTarget* aDrawTarget)
74 : mDrawTarget(aDrawTarget),
75 mOriginalRenderState(nullptr),
76 mPaintingToWindow(false) {
77 mOriginalRenderState = aDrawTarget->RemoveUserData(&sSVGAutoRenderStateKey);
78 // We always remove ourselves from aContext before it dies, so
79 // passing nullptr as the destroy function is okay.
80 aDrawTarget->AddUserData(&sSVGAutoRenderStateKey, this, nullptr);
83 SVGAutoRenderState::~SVGAutoRenderState() {
84 mDrawTarget->RemoveUserData(&sSVGAutoRenderStateKey);
85 if (mOriginalRenderState) {
86 mDrawTarget->AddUserData(&sSVGAutoRenderStateKey, mOriginalRenderState,
87 nullptr);
91 void SVGAutoRenderState::SetPaintingToWindow(bool aPaintingToWindow) {
92 mPaintingToWindow = aPaintingToWindow;
95 /* static */
96 bool SVGAutoRenderState::IsPaintingToWindow(DrawTarget* aDrawTarget) {
97 void* state = aDrawTarget->GetUserData(&sSVGAutoRenderStateKey);
98 if (state) {
99 return static_cast<SVGAutoRenderState*>(state)->mPaintingToWindow;
101 return false;
104 // Unlike containers, leaf frames do not include GetPosition() in
105 // GetCanvasTM().
106 static bool FrameDoesNotIncludePositionInTM(const nsIFrame* aFrame) {
107 return aFrame->IsSVGGeometryFrame() || aFrame->IsSVGImageFrame() ||
108 aFrame->IsInSVGTextSubtree();
111 nsRect SVGUtils::GetPostFilterInkOverflowRect(nsIFrame* aFrame,
112 const nsRect& aPreFilterRect) {
113 MOZ_ASSERT(aFrame->HasAnyStateBits(NS_FRAME_SVG_LAYOUT),
114 "Called on invalid frame type");
116 // Note: we do not return here for eHasNoRefs since we must still handle any
117 // CSS filter functions.
118 // TODO: We currently pass nullptr instead of an nsTArray* here, but we
119 // actually should get the filter frames and then pass them into
120 // GetPostFilterBounds below! See bug 1494263.
121 // TODO: we should really return an empty rect for eHasRefsSomeInvalid since
122 // in that case we disable painting of the element.
123 if (!aFrame->StyleEffects()->HasFilters() ||
124 SVGObserverUtils::GetAndObserveFilters(aFrame, nullptr) ==
125 SVGObserverUtils::eHasRefsSomeInvalid) {
126 return aPreFilterRect;
129 return FilterInstance::GetPostFilterBounds(aFrame, nullptr, &aPreFilterRect)
130 .valueOr(aPreFilterRect);
133 bool SVGUtils::OuterSVGIsCallingReflowSVG(nsIFrame* aFrame) {
134 return GetOuterSVGFrame(aFrame)->IsCallingReflowSVG();
137 bool SVGUtils::AnyOuterSVGIsCallingReflowSVG(nsIFrame* aFrame) {
138 SVGOuterSVGFrame* outer = GetOuterSVGFrame(aFrame);
139 do {
140 if (outer->IsCallingReflowSVG()) {
141 return true;
143 outer = GetOuterSVGFrame(outer->GetParent());
144 } while (outer);
145 return false;
148 void SVGUtils::ScheduleReflowSVG(nsIFrame* aFrame) {
149 MOZ_ASSERT(aFrame->IsFrameOfType(nsIFrame::eSVG), "Passed bad frame!");
151 // If this is triggered, the callers should be fixed to call us before
152 // ReflowSVG is called. If we try to mark dirty bits on frames while we're
153 // in the process of removing them, things will get messed up.
154 MOZ_ASSERT(!OuterSVGIsCallingReflowSVG(aFrame),
155 "Do not call under ISVGDisplayableFrame::ReflowSVG!");
157 // We don't call SVGObserverUtils::InvalidateRenderingObservers here because
158 // we should only be called under InvalidateAndScheduleReflowSVG (which
159 // calls InvalidateBounds) or SVGDisplayContainerFrame::InsertFrames
160 // (at which point the frame has no observers).
162 if (aFrame->HasAnyStateBits(NS_FRAME_IS_NONDISPLAY)) {
163 return;
166 if (aFrame->HasAnyStateBits(NS_FRAME_IS_DIRTY | NS_FRAME_FIRST_REFLOW)) {
167 // Nothing to do if we're already dirty, or if the outer-<svg>
168 // hasn't yet had its initial reflow.
169 return;
172 SVGOuterSVGFrame* outerSVGFrame = nullptr;
174 // We must not add dirty bits to the SVGOuterSVGFrame or else
175 // PresShell::FrameNeedsReflow won't work when we pass it in below.
176 if (aFrame->IsSVGOuterSVGFrame()) {
177 outerSVGFrame = static_cast<SVGOuterSVGFrame*>(aFrame);
178 } else {
179 aFrame->MarkSubtreeDirty();
181 nsIFrame* f = aFrame->GetParent();
182 while (f && !f->IsSVGOuterSVGFrame()) {
183 if (f->HasAnyStateBits(NS_FRAME_IS_DIRTY | NS_FRAME_HAS_DIRTY_CHILDREN)) {
184 return;
186 f->AddStateBits(NS_FRAME_HAS_DIRTY_CHILDREN);
187 f = f->GetParent();
188 MOZ_ASSERT(f->IsFrameOfType(nsIFrame::eSVG),
189 "IsSVGOuterSVGFrame check above not valid!");
192 outerSVGFrame = static_cast<SVGOuterSVGFrame*>(f);
194 MOZ_ASSERT(outerSVGFrame && outerSVGFrame->IsSVGOuterSVGFrame(),
195 "Did not find SVGOuterSVGFrame!");
198 if (outerSVGFrame->HasAnyStateBits(NS_FRAME_IN_REFLOW)) {
199 // We're currently under an SVGOuterSVGFrame::Reflow call so there is no
200 // need to call PresShell::FrameNeedsReflow, since we have an
201 // SVGOuterSVGFrame::DidReflow call pending.
202 return;
205 nsFrameState dirtyBit =
206 (outerSVGFrame == aFrame ? NS_FRAME_IS_DIRTY
207 : NS_FRAME_HAS_DIRTY_CHILDREN);
209 aFrame->PresShell()->FrameNeedsReflow(outerSVGFrame, IntrinsicDirty::None,
210 dirtyBit);
213 bool SVGUtils::NeedsReflowSVG(const nsIFrame* aFrame) {
214 MOZ_ASSERT(aFrame->IsFrameOfType(nsIFrame::eSVG),
215 "SVG uses bits differently!");
217 // The flags we test here may change, hence why we have this separate
218 // function.
219 return aFrame->IsSubtreeDirty();
222 Size SVGUtils::GetContextSize(const nsIFrame* aFrame) {
223 Size size;
225 MOZ_ASSERT(aFrame->GetContent()->IsSVGElement(), "bad cast");
226 const SVGElement* element = static_cast<SVGElement*>(aFrame->GetContent());
228 SVGViewportElement* ctx = element->GetCtx();
229 if (ctx) {
230 size.width = ctx->GetLength(SVGContentUtils::X);
231 size.height = ctx->GetLength(SVGContentUtils::Y);
233 return size;
236 float SVGUtils::ObjectSpace(const gfxRect& aRect,
237 const SVGAnimatedLength* aLength) {
238 float axis;
240 switch (aLength->GetCtxType()) {
241 case SVGContentUtils::X:
242 axis = aRect.Width();
243 break;
244 case SVGContentUtils::Y:
245 axis = aRect.Height();
246 break;
247 case SVGContentUtils::XY:
248 axis = float(SVGContentUtils::ComputeNormalizedHypotenuse(
249 aRect.Width(), aRect.Height()));
250 break;
251 default:
252 MOZ_ASSERT_UNREACHABLE("unexpected ctx type");
253 axis = 0.0f;
254 break;
256 if (aLength->IsPercentage()) {
257 // Multiply first to avoid precision errors:
258 return axis * aLength->GetAnimValInSpecifiedUnits() / 100;
260 return aLength->GetAnimValue(static_cast<SVGViewportElement*>(nullptr)) *
261 axis;
264 float SVGUtils::UserSpace(SVGElement* aSVGElement,
265 const SVGAnimatedLength* aLength) {
266 return aLength->GetAnimValue(aSVGElement);
269 float SVGUtils::UserSpace(nsIFrame* aNonSVGContext,
270 const SVGAnimatedLength* aLength) {
271 return aLength->GetAnimValue(aNonSVGContext);
274 float SVGUtils::UserSpace(const UserSpaceMetrics& aMetrics,
275 const SVGAnimatedLength* aLength) {
276 return aLength->GetAnimValue(aMetrics);
279 SVGOuterSVGFrame* SVGUtils::GetOuterSVGFrame(nsIFrame* aFrame) {
280 while (aFrame) {
281 if (aFrame->IsSVGOuterSVGFrame()) {
282 return static_cast<SVGOuterSVGFrame*>(aFrame);
284 aFrame = aFrame->GetParent();
287 return nullptr;
290 nsIFrame* SVGUtils::GetOuterSVGFrameAndCoveredRegion(nsIFrame* aFrame,
291 nsRect* aRect) {
292 ISVGDisplayableFrame* svg = do_QueryFrame(aFrame);
293 if (!svg) {
294 return nullptr;
296 SVGOuterSVGFrame* outer = GetOuterSVGFrame(aFrame);
297 if (outer == svg) {
298 return nullptr;
301 if (aFrame->HasAnyStateBits(NS_FRAME_IS_NONDISPLAY)) {
302 *aRect = nsRect(0, 0, 0, 0);
303 } else {
304 uint32_t flags = SVGUtils::eForGetClientRects | SVGUtils::eBBoxIncludeFill |
305 SVGUtils::eBBoxIncludeStroke |
306 SVGUtils::eBBoxIncludeMarkers;
308 auto ctm = nsLayoutUtils::GetTransformToAncestor(RelativeTo{aFrame},
309 RelativeTo{outer});
311 float initPositionX = NSAppUnitsToFloatPixels(aFrame->GetPosition().x,
312 AppUnitsPerCSSPixel()),
313 initPositionY = NSAppUnitsToFloatPixels(aFrame->GetPosition().y,
314 AppUnitsPerCSSPixel());
316 Matrix mm;
317 ctm.ProjectTo2D();
318 ctm.CanDraw2D(&mm);
319 gfxMatrix m = ThebesMatrix(mm);
321 float appUnitsPerDevPixel = aFrame->PresContext()->AppUnitsPerDevPixel();
322 float devPixelPerCSSPixel =
323 float(AppUnitsPerCSSPixel()) / appUnitsPerDevPixel;
325 // The matrix that GetBBox accepts should operate on "user space",
326 // i.e. with CSS pixel unit.
327 m = m.PreScale(devPixelPerCSSPixel, devPixelPerCSSPixel);
329 // Both SVGUtils::GetBBox and nsLayoutUtils::GetTransformToAncestor
330 // will count this displacement, we should remove it here to avoid
331 // double-counting.
332 m = m.PreTranslate(-initPositionX, -initPositionY);
334 gfxRect bbox = SVGUtils::GetBBox(aFrame, flags, &m);
335 *aRect = nsLayoutUtils::RoundGfxRectToAppRect(bbox, appUnitsPerDevPixel);
338 return outer;
341 gfxMatrix SVGUtils::GetCanvasTM(nsIFrame* aFrame) {
342 // XXX yuck, we really need a common interface for GetCanvasTM
344 if (!aFrame->HasAnyStateBits(NS_FRAME_SVG_LAYOUT)) {
345 return GetCSSPxToDevPxMatrix(aFrame);
348 if (aFrame->IsSVGForeignObjectFrame()) {
349 return static_cast<SVGForeignObjectFrame*>(aFrame)->GetCanvasTM();
352 if (SVGContainerFrame* containerFrame = do_QueryFrame(aFrame)) {
353 return containerFrame->GetCanvasTM();
356 MOZ_ASSERT(aFrame->GetParent()->IsFrameOfType(nsIFrame::eSVGContainer));
358 auto* parent = static_cast<SVGContainerFrame*>(aFrame->GetParent());
359 auto* content = static_cast<SVGElement*>(aFrame->GetContent());
361 return content->PrependLocalTransformsTo(parent->GetCanvasTM());
364 bool SVGUtils::IsSVGTransformed(const nsIFrame* aFrame,
365 gfx::Matrix* aOwnTransform,
366 gfx::Matrix* aFromParentTransform) {
367 MOZ_ASSERT(aFrame->HasAllStateBits(NS_FRAME_SVG_LAYOUT |
368 NS_FRAME_MAY_BE_TRANSFORMED),
369 "Expecting an SVG frame that can be transformed");
370 bool foundTransform = false;
372 // Check if our parent has children-only transforms:
373 if (SVGContainerFrame* parent = do_QueryFrame(aFrame->GetParent())) {
374 foundTransform = parent->HasChildrenOnlyTransform(aFromParentTransform);
377 if (auto* content = SVGElement::FromNode(aFrame->GetContent())) {
378 auto* transformList = content->GetAnimatedTransformList();
379 if ((transformList && transformList->HasTransform()) ||
380 content->GetAnimateMotionTransform()) {
381 if (aOwnTransform) {
382 *aOwnTransform = gfx::ToMatrix(
383 content->PrependLocalTransformsTo(gfxMatrix(), eUserSpaceToParent));
385 foundTransform = true;
388 return foundTransform;
391 void SVGUtils::NotifyChildrenOfSVGChange(nsIFrame* aFrame, uint32_t aFlags) {
392 for (nsIFrame* kid : aFrame->PrincipalChildList()) {
393 ISVGDisplayableFrame* SVGFrame = do_QueryFrame(kid);
394 if (SVGFrame) {
395 SVGFrame->NotifySVGChanged(aFlags);
396 } else {
397 NS_ASSERTION(
398 kid->IsFrameOfType(nsIFrame::eSVG) || kid->IsInSVGTextSubtree(),
399 "SVG frame expected");
400 // recurse into the children of container frames e.g. <clipPath>, <mask>
401 // in case they have child frames with transformation matrices
402 if (kid->IsFrameOfType(nsIFrame::eSVG)) {
403 NotifyChildrenOfSVGChange(kid, aFlags);
409 // ************************************************************
411 float SVGUtils::ComputeOpacity(const nsIFrame* aFrame, bool aHandleOpacity) {
412 float opacity = aFrame->StyleEffects()->mOpacity;
414 if (opacity != 1.0f &&
415 (SVGUtils::CanOptimizeOpacity(aFrame) || !aHandleOpacity)) {
416 return 1.0f;
419 return opacity;
422 void SVGUtils::DetermineMaskUsage(const nsIFrame* aFrame, bool aHandleOpacity,
423 MaskUsage& aUsage) {
424 using ClipPathType = StyleClipPath::Tag;
426 aUsage.opacity = ComputeOpacity(aFrame, aHandleOpacity);
428 nsIFrame* firstFrame =
429 nsLayoutUtils::FirstContinuationOrIBSplitSibling(aFrame);
431 const nsStyleSVGReset* svgReset = firstFrame->StyleSVGReset();
433 nsTArray<SVGMaskFrame*> maskFrames;
434 // XXX check return value?
435 SVGObserverUtils::GetAndObserveMasks(firstFrame, &maskFrames);
436 aUsage.shouldGenerateMaskLayer = (maskFrames.Length() > 0);
438 SVGClipPathFrame* clipPathFrame;
439 // XXX check return value?
440 SVGObserverUtils::GetAndObserveClipPath(firstFrame, &clipPathFrame);
441 MOZ_ASSERT(!clipPathFrame || svgReset->mClipPath.IsUrl());
443 switch (svgReset->mClipPath.tag) {
444 case ClipPathType::Url:
445 if (clipPathFrame) {
446 if (clipPathFrame->IsTrivial()) {
447 aUsage.shouldApplyClipPath = true;
448 } else {
449 aUsage.shouldGenerateClipMaskLayer = true;
452 break;
453 case ClipPathType::Shape:
454 case ClipPathType::Box:
455 case ClipPathType::Path:
456 aUsage.shouldApplyBasicShapeOrPath = true;
457 break;
458 case ClipPathType::None:
459 MOZ_ASSERT(!aUsage.shouldGenerateClipMaskLayer &&
460 !aUsage.shouldApplyClipPath &&
461 !aUsage.shouldApplyBasicShapeOrPath);
462 break;
463 default:
464 MOZ_ASSERT_UNREACHABLE("Unsupported clip-path type.");
465 break;
469 class MixModeBlender {
470 public:
471 using Factory = gfx::Factory;
473 MixModeBlender(nsIFrame* aFrame, gfxContext* aContext)
474 : mFrame(aFrame), mSourceCtx(aContext) {
475 MOZ_ASSERT(mFrame && mSourceCtx);
478 bool ShouldCreateDrawTargetForBlend() const {
479 return mFrame->StyleEffects()->mMixBlendMode != StyleBlend::Normal;
482 gfxContext* CreateBlendTarget(const gfxMatrix& aTransform) {
483 MOZ_ASSERT(ShouldCreateDrawTargetForBlend());
485 // Create a temporary context to draw to so we can blend it back with
486 // another operator.
487 IntRect drawRect = ComputeClipExtsInDeviceSpace(aTransform);
488 if (drawRect.IsEmpty()) {
489 return nullptr;
492 RefPtr<DrawTarget> targetDT =
493 mSourceCtx->GetDrawTarget()->CreateSimilarDrawTarget(
494 drawRect.Size(), SurfaceFormat::B8G8R8A8);
495 if (!targetDT || !targetDT->IsValid()) {
496 return nullptr;
499 MOZ_ASSERT(!mTargetCtx,
500 "CreateBlendTarget is designed to be used once only.");
502 mTargetCtx = gfxContext::CreateOrNull(targetDT);
503 MOZ_ASSERT(mTargetCtx); // already checked the draw target above
504 mTargetCtx->SetMatrix(mSourceCtx->CurrentMatrix() *
505 Matrix::Translation(-drawRect.TopLeft()));
507 mTargetOffset = drawRect.TopLeft();
509 return mTargetCtx.get();
512 void BlendToTarget() {
513 MOZ_ASSERT(ShouldCreateDrawTargetForBlend());
514 MOZ_ASSERT(mTargetCtx,
515 "BlendToTarget should be used after CreateBlendTarget.");
517 RefPtr<SourceSurface> targetSurf = mTargetCtx->GetDrawTarget()->Snapshot();
519 gfxContextAutoSaveRestore save(mSourceCtx);
520 mSourceCtx->SetMatrix(Matrix()); // This will be restored right after.
521 RefPtr<gfxPattern> pattern = new gfxPattern(
522 targetSurf, Matrix::Translation(mTargetOffset.x, mTargetOffset.y));
523 mSourceCtx->SetPattern(pattern);
524 mSourceCtx->Paint();
527 private:
528 MixModeBlender() = delete;
530 IntRect ComputeClipExtsInDeviceSpace(const gfxMatrix& aTransform) {
531 // These are used if we require a temporary surface for a custom blend
532 // mode. Clip the source context first, so that we can generate a smaller
533 // temporary surface. (Since we will clip this context in
534 // SetupContextMatrix, a pair of save/restore is needed.)
535 gfxContextAutoSaveRestore saver;
537 if (!mFrame->HasAnyStateBits(NS_FRAME_IS_NONDISPLAY)) {
538 saver.SetContext(mSourceCtx);
539 // aFrame has a valid ink overflow rect, so clip to it before calling
540 // PushGroup() to minimize the size of the surfaces we'll composite:
541 gfxContextMatrixAutoSaveRestore matrixAutoSaveRestore(mSourceCtx);
542 mSourceCtx->Multiply(aTransform);
543 nsRect overflowRect = mFrame->InkOverflowRectRelativeToSelf();
544 if (FrameDoesNotIncludePositionInTM(mFrame)) {
545 overflowRect = overflowRect + mFrame->GetPosition();
547 mSourceCtx->Clip(NSRectToSnappedRect(
548 overflowRect, mFrame->PresContext()->AppUnitsPerDevPixel(),
549 *mSourceCtx->GetDrawTarget()));
552 // Get the clip extents in device space.
553 gfxRect clippedFrameSurfaceRect =
554 mSourceCtx->GetClipExtents(gfxContext::eDeviceSpace);
555 clippedFrameSurfaceRect.RoundOut();
557 IntRect result;
558 ToRect(clippedFrameSurfaceRect).ToIntRect(&result);
560 return Factory::CheckSurfaceSize(result.Size()) ? result : IntRect();
563 nsIFrame* mFrame;
564 gfxContext* mSourceCtx;
565 UniquePtr<gfxContext> mTargetCtx;
566 IntPoint mTargetOffset;
569 void SVGUtils::PaintFrameWithEffects(nsIFrame* aFrame, gfxContext& aContext,
570 const gfxMatrix& aTransform,
571 imgDrawingParams& aImgParams,
572 const nsIntRect* aDirtyRect) {
573 NS_ASSERTION(aFrame->HasAnyStateBits(NS_FRAME_IS_NONDISPLAY) ||
574 aFrame->PresContext()->Document()->IsSVGGlyphsDocument(),
575 "Only painting of non-display SVG should take this code path");
577 ISVGDisplayableFrame* svgFrame = do_QueryFrame(aFrame);
578 if (!svgFrame) {
579 return;
582 MaskUsage maskUsage;
583 DetermineMaskUsage(aFrame, true, maskUsage);
584 if (maskUsage.opacity == 0.0f) {
585 return;
588 if (auto* svg = SVGElement::FromNode(aFrame->GetContent())) {
589 if (!svg->HasValidDimensions()) {
590 return;
594 if (aDirtyRect && !aFrame->HasAnyStateBits(NS_FRAME_IS_NONDISPLAY)) {
595 // Here we convert aFrame's paint bounds to outer-<svg> device space,
596 // compare it to aDirtyRect, and return early if they don't intersect.
597 // We don't do this optimization for nondisplay SVG since nondisplay
598 // SVG doesn't maintain bounds/overflow rects.
599 nsRect overflowRect = aFrame->InkOverflowRectRelativeToSelf();
600 if (FrameDoesNotIncludePositionInTM(aFrame)) {
601 overflowRect = overflowRect + aFrame->GetPosition();
603 int32_t appUnitsPerDevPx = aFrame->PresContext()->AppUnitsPerDevPixel();
604 gfxMatrix tm = aTransform;
605 if (SVGContainerFrame* container = do_QueryFrame(aFrame)) {
606 gfx::Matrix childrenOnlyTM;
607 if (container->HasChildrenOnlyTransform(&childrenOnlyTM)) {
608 // Undo the children-only transform:
609 if (!childrenOnlyTM.Invert()) {
610 return;
612 tm = ThebesMatrix(childrenOnlyTM) * tm;
615 nsIntRect bounds =
616 TransformFrameRectToOuterSVG(overflowRect, tm, aFrame->PresContext())
617 .ToOutsidePixels(appUnitsPerDevPx);
618 if (!aDirtyRect->Intersects(bounds)) {
619 return;
623 /* SVG defines the following rendering model:
625 * 1. Render fill
626 * 2. Render stroke
627 * 3. Render markers
628 * 4. Apply filter
629 * 5. Apply clipping, masking, group opacity
631 * We follow this, but perform a couple of optimizations:
633 * + Use cairo's clipPath when representable natively (single object
634 * clip region).
636 * + Merge opacity and masking if both used together.
639 /* Properties are added lazily and may have been removed by a restyle,
640 so make sure all applicable ones are set again. */
641 SVGClipPathFrame* clipPathFrame;
642 nsTArray<SVGMaskFrame*> maskFrames;
643 // TODO: We currently pass nullptr instead of an nsTArray* here, but we
644 // actually should get the filter frames and then pass them into
645 // PaintFilteredFrame below! See bug 1494263.
646 const bool hasInvalidFilter =
647 SVGObserverUtils::GetAndObserveFilters(aFrame, nullptr) ==
648 SVGObserverUtils::eHasRefsSomeInvalid;
649 if (SVGObserverUtils::GetAndObserveClipPath(aFrame, &clipPathFrame) ==
650 SVGObserverUtils::eHasRefsSomeInvalid ||
651 SVGObserverUtils::GetAndObserveMasks(aFrame, &maskFrames) ==
652 SVGObserverUtils::eHasRefsSomeInvalid) {
653 // Some resource is invalid. We shouldn't paint anything.
654 return;
657 SVGMaskFrame* maskFrame = maskFrames.IsEmpty() ? nullptr : maskFrames[0];
659 MixModeBlender blender(aFrame, &aContext);
660 gfxContext* target = blender.ShouldCreateDrawTargetForBlend()
661 ? blender.CreateBlendTarget(aTransform)
662 : &aContext;
664 if (!target) {
665 return;
668 /* Check if we need to do additional operations on this child's
669 * rendering, which necessitates rendering into another surface. */
670 bool shouldGenerateMask =
671 (maskUsage.opacity != 1.0f || maskUsage.shouldGenerateClipMaskLayer ||
672 maskUsage.shouldGenerateMaskLayer);
673 bool shouldPushMask = false;
675 if (shouldGenerateMask) {
676 RefPtr<SourceSurface> maskSurface;
678 // maskFrame can be nullptr even if maskUsage.shouldGenerateMaskLayer is
679 // true. That happens when a user gives an unresolvable mask-id, such as
680 // mask:url()
681 // mask:url(#id-which-does-not-exist)
682 // Since we only uses SVGUtils with SVG elements, not like mask on an
683 // HTML element, we should treat an unresolvable mask as no-mask here.
684 if (maskUsage.shouldGenerateMaskLayer && maskFrame) {
685 StyleMaskMode maskMode =
686 aFrame->StyleSVGReset()->mMask.mLayers[0].mMaskMode;
687 SVGMaskFrame::MaskParams params(aContext.GetDrawTarget(), aFrame,
688 aTransform, maskUsage.opacity, maskMode,
689 aImgParams);
691 maskSurface = maskFrame->GetMaskForMaskedFrame(params);
693 if (!maskSurface) {
694 // Either entire surface is clipped out, or gfx buffer allocation
695 // failure in SVGMaskFrame::GetMaskForMaskedFrame.
696 return;
698 shouldPushMask = true;
701 if (maskUsage.shouldGenerateClipMaskLayer) {
702 RefPtr<SourceSurface> clipMaskSurface =
703 clipPathFrame->GetClipMask(aContext, aFrame, aTransform, maskSurface);
704 if (clipMaskSurface) {
705 maskSurface = clipMaskSurface;
706 } else {
707 // Either entire surface is clipped out, or gfx buffer allocation
708 // failure in SVGClipPathFrame::GetClipMask.
709 return;
711 shouldPushMask = true;
714 if (!maskUsage.shouldGenerateClipMaskLayer &&
715 !maskUsage.shouldGenerateMaskLayer) {
716 shouldPushMask = true;
719 // SVG mask multiply opacity into maskSurface already, so we do not bother
720 // to apply opacity again.
721 if (shouldPushMask) {
722 // We want the mask to be untransformed so use the inverse of the
723 // current transform as the maskTransform to compensate.
724 Matrix maskTransform = aContext.CurrentMatrix();
725 maskTransform.Invert();
726 target->PushGroupForBlendBack(gfxContentType::COLOR_ALPHA,
727 maskFrame ? 1.0 : maskUsage.opacity,
728 maskSurface, maskTransform);
732 /* If this frame has only a trivial clipPath, set up cairo's clipping now so
733 * we can just do normal painting and get it clipped appropriately.
735 if (maskUsage.shouldApplyClipPath || maskUsage.shouldApplyBasicShapeOrPath) {
736 if (maskUsage.shouldApplyClipPath) {
737 clipPathFrame->ApplyClipPath(aContext, aFrame, aTransform);
738 } else {
739 CSSClipPathInstance::ApplyBasicShapeOrPathClip(aContext, aFrame,
740 aTransform);
744 /* Paint the child */
746 // Invalid filters should render the unfiltered contents per spec.
747 if (aFrame->StyleEffects()->HasFilters() && !hasInvalidFilter) {
748 nsRegion* dirtyRegion = nullptr;
749 nsRegion tmpDirtyRegion;
750 if (aDirtyRect) {
751 // aDirtyRect is in outer-<svg> device pixels, but the filter code needs
752 // it in frame space.
753 gfxMatrix userToDeviceSpace = aTransform;
754 if (userToDeviceSpace.IsSingular()) {
755 return;
757 gfxMatrix deviceToUserSpace = userToDeviceSpace;
758 deviceToUserSpace.Invert();
759 gfxRect dirtyBounds = deviceToUserSpace.TransformBounds(gfxRect(
760 aDirtyRect->x, aDirtyRect->y, aDirtyRect->width, aDirtyRect->height));
761 tmpDirtyRegion = nsLayoutUtils::RoundGfxRectToAppRect(
762 dirtyBounds, AppUnitsPerCSSPixel()) -
763 aFrame->GetPosition();
764 dirtyRegion = &tmpDirtyRegion;
767 gfxContextMatrixAutoSaveRestore autoSR(target);
769 // 'target' is currently scaled such that its user space units are CSS
770 // pixels (SVG user space units). But PaintFilteredFrame expects it to be
771 // scaled in such a way that its user space units are device pixels. So we
772 // have to adjust the scale.
773 gfxMatrix reverseScaleMatrix = SVGUtils::GetCSSPxToDevPxMatrix(aFrame);
774 DebugOnly<bool> invertible = reverseScaleMatrix.Invert();
775 target->SetMatrixDouble(reverseScaleMatrix * aTransform *
776 target->CurrentMatrixDouble());
778 auto callback = [&](gfxContext& aContext, imgDrawingParams& aImgParams,
779 const gfxMatrix* aFilterTransform,
780 const nsIntRect* aDirtyRect) {
781 nsIntRect* dirtyRect = nullptr;
782 nsIntRect tmpDirtyRect;
784 // aDirtyRect is in user-space pixels, we need to convert to
785 // outer-SVG-frame-relative device pixels.
786 if (aDirtyRect) {
787 MOZ_ASSERT(aFilterTransform);
788 gfxMatrix userToDeviceSpace = *aFilterTransform;
789 if (userToDeviceSpace.IsSingular()) {
790 return;
792 gfxRect dirtyBounds = userToDeviceSpace.TransformBounds(
793 gfxRect(aDirtyRect->x, aDirtyRect->y, aDirtyRect->width,
794 aDirtyRect->height));
795 dirtyBounds.RoundOut();
796 if (gfxUtils::GfxRectToIntRect(dirtyBounds, &tmpDirtyRect)) {
797 dirtyRect = &tmpDirtyRect;
801 svgFrame->PaintSVG(aContext,
802 aFilterTransform
803 ? SVGUtils::GetCSSPxToDevPxMatrix(aFrame)
804 : aTransform,
805 aImgParams, aFilterTransform ? dirtyRect : aDirtyRect);
807 FilterInstance::PaintFilteredFrame(
808 aFrame, aFrame->StyleEffects()->mFilters.AsSpan(), target, callback,
809 dirtyRegion, aImgParams);
810 } else {
811 svgFrame->PaintSVG(*target, aTransform, aImgParams, aDirtyRect);
814 if (maskUsage.shouldApplyClipPath || maskUsage.shouldApplyBasicShapeOrPath) {
815 aContext.PopClip();
818 if (shouldPushMask) {
819 target->PopGroupAndBlend();
822 if (blender.ShouldCreateDrawTargetForBlend()) {
823 MOZ_ASSERT(target != &aContext);
824 blender.BlendToTarget();
828 bool SVGUtils::HitTestClip(nsIFrame* aFrame, const gfxPoint& aPoint) {
829 const nsStyleSVGReset* svgReset = aFrame->StyleSVGReset();
830 if (!svgReset->HasClipPath()) {
831 return true;
833 if (svgReset->mClipPath.IsUrl()) {
834 // If the clip-path property references non-existent or invalid clipPath
835 // element(s) we ignore it.
836 SVGClipPathFrame* clipPathFrame;
837 SVGObserverUtils::GetAndObserveClipPath(aFrame, &clipPathFrame);
838 return !clipPathFrame ||
839 clipPathFrame->PointIsInsideClipPath(aFrame, aPoint);
841 return CSSClipPathInstance::HitTestBasicShapeOrPathClip(aFrame, aPoint);
844 nsIFrame* SVGUtils::HitTestChildren(SVGDisplayContainerFrame* aFrame,
845 const gfxPoint& aPoint) {
846 // First we transform aPoint into the coordinate space established by aFrame
847 // for its children (e.g. take account of any 'viewBox' attribute):
848 gfxPoint point = aPoint;
849 if (auto* svg = SVGElement::FromNode(aFrame->GetContent())) {
850 gfxMatrix m = svg->PrependLocalTransformsTo(gfxMatrix(), eChildToUserSpace);
851 if (!m.IsIdentity()) {
852 if (!m.Invert()) {
853 return nullptr;
855 point = m.TransformPoint(point);
859 // Traverse the list in reverse order, so that if we get a hit we know that's
860 // the topmost frame that intersects the point; then we can just return it.
861 nsIFrame* result = nullptr;
862 for (nsIFrame* current = aFrame->PrincipalChildList().LastChild(); current;
863 current = current->GetPrevSibling()) {
864 ISVGDisplayableFrame* SVGFrame = do_QueryFrame(current);
865 if (SVGFrame) {
866 const nsIContent* content = current->GetContent();
867 if (auto* svg = SVGElement::FromNode(content)) {
868 if (!svg->HasValidDimensions()) {
869 continue;
872 // GetFrameForPoint() expects a point in its frame's SVG user space, so
873 // we need to convert to that space:
874 gfxPoint p = point;
875 if (auto* svg = SVGElement::FromNode(content)) {
876 gfxMatrix m =
877 svg->PrependLocalTransformsTo(gfxMatrix(), eUserSpaceToParent);
878 if (!m.IsIdentity()) {
879 if (!m.Invert()) {
880 continue;
882 p = m.TransformPoint(p);
885 result = SVGFrame->GetFrameForPoint(p);
886 if (result) break;
890 if (result && !HitTestClip(aFrame, aPoint)) result = nullptr;
892 return result;
895 nsRect SVGUtils::TransformFrameRectToOuterSVG(const nsRect& aRect,
896 const gfxMatrix& aMatrix,
897 nsPresContext* aPresContext) {
898 gfxRect r(aRect.x, aRect.y, aRect.width, aRect.height);
899 r.Scale(1.0 / AppUnitsPerCSSPixel());
900 return nsLayoutUtils::RoundGfxRectToAppRect(
901 aMatrix.TransformBounds(r), aPresContext->AppUnitsPerDevPixel());
904 IntSize SVGUtils::ConvertToSurfaceSize(const gfxSize& aSize,
905 bool* aResultOverflows) {
906 IntSize surfaceSize(ClampToInt(ceil(aSize.width)),
907 ClampToInt(ceil(aSize.height)));
909 *aResultOverflows = surfaceSize.width != ceil(aSize.width) ||
910 surfaceSize.height != ceil(aSize.height);
912 if (!Factory::AllowedSurfaceSize(surfaceSize)) {
913 surfaceSize.width =
914 std::min(NS_SVG_OFFSCREEN_MAX_DIMENSION, surfaceSize.width);
915 surfaceSize.height =
916 std::min(NS_SVG_OFFSCREEN_MAX_DIMENSION, surfaceSize.height);
917 *aResultOverflows = true;
920 return surfaceSize;
923 bool SVGUtils::HitTestRect(const gfx::Matrix& aMatrix, float aRX, float aRY,
924 float aRWidth, float aRHeight, float aX, float aY) {
925 gfx::Rect rect(aRX, aRY, aRWidth, aRHeight);
926 if (rect.IsEmpty() || aMatrix.IsSingular()) {
927 return false;
929 gfx::Matrix toRectSpace = aMatrix;
930 toRectSpace.Invert();
931 gfx::Point p = toRectSpace.TransformPoint(gfx::Point(aX, aY));
932 return rect.x <= p.x && p.x <= rect.XMost() && rect.y <= p.y &&
933 p.y <= rect.YMost();
936 gfxRect SVGUtils::GetClipRectForFrame(const nsIFrame* aFrame, float aX,
937 float aY, float aWidth, float aHeight) {
938 const nsStyleDisplay* disp = aFrame->StyleDisplay();
939 const nsStyleEffects* effects = aFrame->StyleEffects();
941 bool clipApplies = disp->mOverflowX == StyleOverflow::Hidden ||
942 disp->mOverflowY == StyleOverflow::Hidden;
944 if (!clipApplies || effects->mClip.IsAuto()) {
945 return gfxRect(aX, aY, aWidth, aHeight);
948 const auto& rect = effects->mClip.AsRect();
949 nsRect coordClipRect = rect.ToLayoutRect();
950 nsIntRect clipPxRect = coordClipRect.ToOutsidePixels(
951 aFrame->PresContext()->AppUnitsPerDevPixel());
952 gfxRect clipRect =
953 gfxRect(clipPxRect.x, clipPxRect.y, clipPxRect.width, clipPxRect.height);
954 if (rect.right.IsAuto()) {
955 clipRect.width = aWidth - clipRect.X();
957 if (rect.bottom.IsAuto()) {
958 clipRect.height = aHeight - clipRect.Y();
960 if (disp->mOverflowX != StyleOverflow::Hidden) {
961 clipRect.x = aX;
962 clipRect.width = aWidth;
964 if (disp->mOverflowY != StyleOverflow::Hidden) {
965 clipRect.y = aY;
966 clipRect.height = aHeight;
968 return clipRect;
971 void SVGUtils::SetClipRect(gfxContext* aContext, const gfxMatrix& aCTM,
972 const gfxRect& aRect) {
973 if (aCTM.IsSingular()) {
974 return;
977 gfxContextMatrixAutoSaveRestore matrixAutoSaveRestore(aContext);
978 aContext->Multiply(aCTM);
979 aContext->Clip(aRect);
982 gfxRect SVGUtils::GetBBox(nsIFrame* aFrame, uint32_t aFlags,
983 const gfxMatrix* aToBoundsSpace) {
984 if (aFrame->IsTextFrame()) {
985 aFrame = aFrame->GetParent();
988 if (aFrame->IsInSVGTextSubtree()) {
989 // It is possible to apply a gradient, pattern, clipping path, mask or
990 // filter to text. When one of these facilities is applied to text
991 // the bounding box is the entire text element in all cases.
992 aFrame =
993 nsLayoutUtils::GetClosestFrameOfType(aFrame, LayoutFrameType::SVGText);
996 ISVGDisplayableFrame* svg = do_QueryFrame(aFrame);
997 const bool hasSVGLayout = aFrame->HasAnyStateBits(NS_FRAME_SVG_LAYOUT);
998 if (hasSVGLayout && !svg) {
999 // An SVG frame, but not one that can be displayed directly (for
1000 // example, nsGradientFrame). These can't contribute to the bbox.
1001 return gfxRect();
1004 const bool isOuterSVG = svg && !hasSVGLayout;
1005 MOZ_ASSERT(!isOuterSVG || aFrame->IsSVGOuterSVGFrame());
1006 if (!svg || (isOuterSVG && (aFlags & eUseFrameBoundsForOuterSVG))) {
1007 // An HTML element or an SVG outer frame.
1008 MOZ_ASSERT(!hasSVGLayout);
1009 bool onlyCurrentFrame = aFlags & eIncludeOnlyCurrentFrameForNonSVGElement;
1010 return SVGIntegrationUtils::GetSVGBBoxForNonSVGFrame(
1011 aFrame,
1012 /* aUnionContinuations = */ !onlyCurrentFrame);
1015 MOZ_ASSERT(svg);
1017 if (auto* element = SVGElement::FromNodeOrNull(aFrame->GetContent())) {
1018 if (!element->HasValidDimensions()) {
1019 return gfxRect();
1023 // Clean out flags which have no effects on returning bbox from now, so that
1024 // we can cache and reuse ObjectBoundingBoxProperty() in the code below.
1025 aFlags &=
1026 ~(eIncludeOnlyCurrentFrameForNonSVGElement | eUseFrameBoundsForOuterSVG);
1027 if (!aFrame->IsSVGUseFrame()) {
1028 aFlags &= ~eUseUserSpaceOfUseElement;
1031 if (aFlags == eBBoxIncludeFillGeometry &&
1032 // We only cache bbox in element's own user space
1033 !aToBoundsSpace) {
1034 gfxRect* prop = aFrame->GetProperty(ObjectBoundingBoxProperty());
1035 if (prop) {
1036 return *prop;
1040 gfxMatrix matrix;
1041 if (aToBoundsSpace) {
1042 matrix = *aToBoundsSpace;
1045 if (aFrame->IsSVGForeignObjectFrame() ||
1046 aFlags & SVGUtils::eUseUserSpaceOfUseElement) {
1047 // The spec says getBBox "Returns the tight bounding box in *current user
1048 // space*". So we should really be doing this for all elements, but that
1049 // needs investigation to check that we won't break too much content.
1050 // NOTE: When changing this to apply to other frame types, make sure to
1051 // also update SVGUtils::FrameSpaceInCSSPxToUserSpaceOffset.
1052 MOZ_ASSERT(aFrame->GetContent()->IsSVGElement(), "bad cast");
1053 SVGElement* element = static_cast<SVGElement*>(aFrame->GetContent());
1054 matrix = element->PrependLocalTransformsTo(matrix, eChildToUserSpace);
1056 gfxRect bbox =
1057 svg->GetBBoxContribution(ToMatrix(matrix), aFlags).ToThebesRect();
1058 // Account for 'clipped'.
1059 if (aFlags & SVGUtils::eBBoxIncludeClipped) {
1060 gfxRect clipRect;
1061 float x, y, width, height;
1062 gfxRect fillBBox =
1063 svg->GetBBoxContribution({}, SVGUtils::eBBoxIncludeFill).ToThebesRect();
1064 x = fillBBox.x;
1065 y = fillBBox.y;
1066 width = fillBBox.width;
1067 height = fillBBox.height;
1068 bool hasClip = aFrame->StyleDisplay()->IsScrollableOverflow();
1069 if (hasClip) {
1070 clipRect = SVGUtils::GetClipRectForFrame(aFrame, x, y, width, height);
1071 if (aFrame->IsSVGForeignObjectFrame() || aFrame->IsSVGUseFrame()) {
1072 clipRect = matrix.TransformBounds(clipRect);
1075 SVGClipPathFrame* clipPathFrame;
1076 if (SVGObserverUtils::GetAndObserveClipPath(aFrame, &clipPathFrame) ==
1077 SVGObserverUtils::eHasRefsSomeInvalid) {
1078 bbox = gfxRect(0, 0, 0, 0);
1079 } else {
1080 if (clipPathFrame) {
1081 SVGClipPathElement* clipContent =
1082 static_cast<SVGClipPathElement*>(clipPathFrame->GetContent());
1083 if (clipContent->IsUnitsObjectBoundingBox()) {
1084 matrix.PreTranslate(gfxPoint(x, y));
1085 matrix.PreScale(width, height);
1086 } else if (aFrame->IsSVGForeignObjectFrame()) {
1087 matrix = gfxMatrix();
1089 matrix *= SVGUtils::GetTransformMatrixInUserSpace(clipPathFrame);
1091 bbox = clipPathFrame->GetBBoxForClipPathFrame(bbox, matrix, aFlags)
1092 .ToThebesRect();
1095 if (hasClip) {
1096 bbox = bbox.Intersect(clipRect);
1099 if (bbox.IsEmpty()) {
1100 bbox = gfxRect(0, 0, 0, 0);
1105 if (aFlags == eBBoxIncludeFillGeometry &&
1106 // We only cache bbox in element's own user space
1107 !aToBoundsSpace) {
1108 // Obtaining the bbox for objectBoundingBox calculations is common so we
1109 // cache the result for future calls, since calculation can be expensive:
1110 aFrame->SetProperty(ObjectBoundingBoxProperty(), new gfxRect(bbox));
1113 return bbox;
1116 gfxPoint SVGUtils::FrameSpaceInCSSPxToUserSpaceOffset(const nsIFrame* aFrame) {
1117 if (!aFrame->HasAnyStateBits(NS_FRAME_SVG_LAYOUT)) {
1118 // The user space for non-SVG frames is defined as the bounding box of the
1119 // frame's border-box rects over all continuations.
1120 return gfxPoint();
1123 // Leaf frames apply their own offset inside their user space.
1124 if (FrameDoesNotIncludePositionInTM(aFrame)) {
1125 return nsLayoutUtils::RectToGfxRect(aFrame->GetRect(),
1126 AppUnitsPerCSSPixel())
1127 .TopLeft();
1130 // For foreignObject frames, SVGUtils::GetBBox applies their local
1131 // transform, so we need to do the same here.
1132 if (aFrame->IsSVGForeignObjectFrame()) {
1133 gfxMatrix transform =
1134 static_cast<SVGElement*>(aFrame->GetContent())
1135 ->PrependLocalTransformsTo(gfxMatrix(), eChildToUserSpace);
1136 NS_ASSERTION(!transform.HasNonTranslation(),
1137 "we're relying on this being an offset-only transform");
1138 return transform.GetTranslation();
1141 return gfxPoint();
1144 static gfxRect GetBoundingBoxRelativeRect(const SVGAnimatedLength* aXYWH,
1145 const gfxRect& aBBox) {
1146 return gfxRect(aBBox.x + SVGUtils::ObjectSpace(aBBox, &aXYWH[0]),
1147 aBBox.y + SVGUtils::ObjectSpace(aBBox, &aXYWH[1]),
1148 SVGUtils::ObjectSpace(aBBox, &aXYWH[2]),
1149 SVGUtils::ObjectSpace(aBBox, &aXYWH[3]));
1152 gfxRect SVGUtils::GetRelativeRect(uint16_t aUnits,
1153 const SVGAnimatedLength* aXYWH,
1154 const gfxRect& aBBox,
1155 const UserSpaceMetrics& aMetrics) {
1156 if (aUnits == SVG_UNIT_TYPE_OBJECTBOUNDINGBOX) {
1157 return GetBoundingBoxRelativeRect(aXYWH, aBBox);
1159 return gfxRect(UserSpace(aMetrics, &aXYWH[0]), UserSpace(aMetrics, &aXYWH[1]),
1160 UserSpace(aMetrics, &aXYWH[2]),
1161 UserSpace(aMetrics, &aXYWH[3]));
1164 gfxRect SVGUtils::GetRelativeRect(uint16_t aUnits,
1165 const SVGAnimatedLength* aXYWH,
1166 const gfxRect& aBBox, nsIFrame* aFrame) {
1167 if (aUnits == SVG_UNIT_TYPE_OBJECTBOUNDINGBOX) {
1168 return GetBoundingBoxRelativeRect(aXYWH, aBBox);
1170 if (SVGElement* svgElement = SVGElement::FromNode(aFrame->GetContent())) {
1171 return GetRelativeRect(aUnits, aXYWH, aBBox, SVGElementMetrics(svgElement));
1173 return GetRelativeRect(aUnits, aXYWH, aBBox,
1174 NonSVGFrameUserSpaceMetrics(aFrame));
1177 bool SVGUtils::CanOptimizeOpacity(const nsIFrame* aFrame) {
1178 if (!aFrame->HasAnyStateBits(NS_FRAME_SVG_LAYOUT)) {
1179 return false;
1181 auto* content = aFrame->GetContent();
1182 if (!content->IsSVGGeometryElement() &&
1183 !content->IsSVGElement(nsGkAtoms::image)) {
1184 return false;
1186 if (aFrame->StyleEffects()->HasFilters()) {
1187 return false;
1189 // XXX The SVG WG is intending to allow fill, stroke and markers on <image>
1190 if (content->IsSVGElement(nsGkAtoms::image)) {
1191 return true;
1193 const nsStyleSVG* style = aFrame->StyleSVG();
1194 if (style->HasMarker() &&
1195 static_cast<SVGGeometryElement*>(content)->IsMarkable()) {
1196 return false;
1199 if (nsLayoutUtils::HasAnimationOfPropertySet(
1200 aFrame, nsCSSPropertyIDSet::OpacityProperties())) {
1201 return false;
1204 return !style->HasFill() || !HasStroke(aFrame);
1207 gfxMatrix SVGUtils::AdjustMatrixForUnits(const gfxMatrix& aMatrix,
1208 const SVGAnimatedEnumeration* aUnits,
1209 nsIFrame* aFrame, uint32_t aFlags) {
1210 if (aFrame && aUnits->GetAnimValue() == SVG_UNIT_TYPE_OBJECTBOUNDINGBOX) {
1211 gfxRect bbox = GetBBox(aFrame, aFlags);
1212 gfxMatrix tm = aMatrix;
1213 tm.PreTranslate(gfxPoint(bbox.X(), bbox.Y()));
1214 tm.PreScale(bbox.Width(), bbox.Height());
1215 return tm;
1217 return aMatrix;
1220 bool SVGUtils::GetNonScalingStrokeTransform(const nsIFrame* aFrame,
1221 gfxMatrix* aUserToOuterSVG) {
1222 if (aFrame->GetContent()->IsText()) {
1223 aFrame = aFrame->GetParent();
1226 if (!aFrame->StyleSVGReset()->HasNonScalingStroke()) {
1227 return false;
1230 MOZ_ASSERT(aFrame->GetContent()->IsSVGElement(), "should be an SVG element");
1232 *aUserToOuterSVG = ThebesMatrix(SVGContentUtils::GetCTM(
1233 static_cast<SVGElement*>(aFrame->GetContent()), true));
1235 return aUserToOuterSVG->HasNonTranslation();
1238 // The logic here comes from _cairo_stroke_style_max_distance_from_path
1239 static gfxRect PathExtentsToMaxStrokeExtents(const gfxRect& aPathExtents,
1240 const nsIFrame* aFrame,
1241 double aStyleExpansionFactor,
1242 const gfxMatrix& aMatrix) {
1243 double style_expansion =
1244 aStyleExpansionFactor * SVGUtils::GetStrokeWidth(aFrame);
1246 gfxMatrix matrix = aMatrix;
1248 gfxMatrix outerSVGToUser;
1249 if (SVGUtils::GetNonScalingStrokeTransform(aFrame, &outerSVGToUser)) {
1250 outerSVGToUser.Invert();
1251 matrix.PreMultiply(outerSVGToUser);
1254 double dx = style_expansion * (fabs(matrix._11) + fabs(matrix._21));
1255 double dy = style_expansion * (fabs(matrix._22) + fabs(matrix._12));
1257 gfxRect strokeExtents = aPathExtents;
1258 strokeExtents.Inflate(dx, dy);
1259 return strokeExtents;
1262 /*static*/
1263 gfxRect SVGUtils::PathExtentsToMaxStrokeExtents(const gfxRect& aPathExtents,
1264 const nsTextFrame* aFrame,
1265 const gfxMatrix& aMatrix) {
1266 NS_ASSERTION(aFrame->IsInSVGTextSubtree(),
1267 "expected an nsTextFrame for SVG text");
1268 return mozilla::PathExtentsToMaxStrokeExtents(aPathExtents, aFrame, 0.5,
1269 aMatrix);
1272 /*static*/
1273 gfxRect SVGUtils::PathExtentsToMaxStrokeExtents(const gfxRect& aPathExtents,
1274 const SVGGeometryFrame* aFrame,
1275 const gfxMatrix& aMatrix) {
1276 bool strokeMayHaveCorners =
1277 !SVGContentUtils::ShapeTypeHasNoCorners(aFrame->GetContent());
1279 // For a shape without corners the stroke can only extend half the stroke
1280 // width from the path in the x/y-axis directions. For shapes with corners
1281 // the stroke can extend by sqrt(1/2) (think 45 degree rotated rect, or line
1282 // with stroke-linecaps="square").
1283 double styleExpansionFactor = strokeMayHaveCorners ? M_SQRT1_2 : 0.5;
1285 // The stroke can extend even further for paths that can be affected by
1286 // stroke-miterlimit.
1287 // We only need to do this if the limit is greater than 1, but it's probably
1288 // not worth optimizing for that.
1289 bool affectedByMiterlimit = aFrame->GetContent()->IsAnyOfSVGElements(
1290 nsGkAtoms::path, nsGkAtoms::polyline, nsGkAtoms::polygon);
1292 if (affectedByMiterlimit) {
1293 const nsStyleSVG* style = aFrame->StyleSVG();
1294 if (style->mStrokeLinejoin == StyleStrokeLinejoin::Miter &&
1295 styleExpansionFactor < style->mStrokeMiterlimit / 2.0) {
1296 styleExpansionFactor = style->mStrokeMiterlimit / 2.0;
1300 return mozilla::PathExtentsToMaxStrokeExtents(aPathExtents, aFrame,
1301 styleExpansionFactor, aMatrix);
1304 // ----------------------------------------------------------------------
1306 /* static */
1307 nscolor SVGUtils::GetFallbackOrPaintColor(
1308 const ComputedStyle& aStyle, StyleSVGPaint nsStyleSVG::*aFillOrStroke,
1309 nscolor aDefaultContextFallbackColor) {
1310 const auto& paint = aStyle.StyleSVG()->*aFillOrStroke;
1311 nscolor color;
1312 switch (paint.kind.tag) {
1313 case StyleSVGPaintKind::Tag::PaintServer:
1314 color = paint.fallback.IsColor()
1315 ? paint.fallback.AsColor().CalcColor(aStyle)
1316 : NS_RGBA(0, 0, 0, 0);
1317 break;
1318 case StyleSVGPaintKind::Tag::ContextStroke:
1319 case StyleSVGPaintKind::Tag::ContextFill:
1320 color = paint.fallback.IsColor()
1321 ? paint.fallback.AsColor().CalcColor(aStyle)
1322 : aDefaultContextFallbackColor;
1323 break;
1324 default:
1325 color = paint.kind.AsColor().CalcColor(aStyle);
1326 break;
1328 if (const auto* styleIfVisited = aStyle.GetStyleIfVisited()) {
1329 const auto& paintIfVisited = styleIfVisited->StyleSVG()->*aFillOrStroke;
1330 // To prevent Web content from detecting if a user has visited a URL
1331 // (via URL loading triggered by paint servers or performance
1332 // differences between paint servers or between a paint server and a
1333 // color), we do not allow whether links are visited to change which
1334 // paint server is used or switch between paint servers and simple
1335 // colors. A :visited style may only override a simple color with
1336 // another simple color.
1337 if (paintIfVisited.kind.IsColor() && paint.kind.IsColor()) {
1338 nscolor colors[2] = {
1339 color, paintIfVisited.kind.AsColor().CalcColor(*styleIfVisited)};
1340 return ComputedStyle::CombineVisitedColors(colors,
1341 aStyle.RelevantLinkVisited());
1344 return color;
1347 /* static */
1348 void SVGUtils::MakeFillPatternFor(nsIFrame* aFrame, gfxContext* aContext,
1349 GeneralPattern* aOutPattern,
1350 imgDrawingParams& aImgParams,
1351 SVGContextPaint* aContextPaint) {
1352 const nsStyleSVG* style = aFrame->StyleSVG();
1353 if (style->mFill.kind.IsNone()) {
1354 return;
1357 const float opacity = aFrame->StyleEffects()->mOpacity;
1359 float fillOpacity = GetOpacity(style->mFillOpacity, aContextPaint);
1360 if (opacity < 1.0f && SVGUtils::CanOptimizeOpacity(aFrame)) {
1361 // Combine the group opacity into the fill opacity (we will have skipped
1362 // creating an offscreen surface to apply the group opacity).
1363 fillOpacity *= opacity;
1366 const DrawTarget* dt = aContext->GetDrawTarget();
1368 SVGPaintServerFrame* ps =
1369 SVGObserverUtils::GetAndObservePaintServer(aFrame, &nsStyleSVG::mFill);
1371 if (ps) {
1372 RefPtr<gfxPattern> pattern =
1373 ps->GetPaintServerPattern(aFrame, dt, aContext->CurrentMatrixDouble(),
1374 &nsStyleSVG::mFill, fillOpacity, aImgParams);
1375 if (pattern) {
1376 pattern->CacheColorStops(dt);
1377 aOutPattern->Init(*pattern->GetPattern(dt));
1378 return;
1382 if (aContextPaint) {
1383 RefPtr<gfxPattern> pattern;
1384 switch (style->mFill.kind.tag) {
1385 case StyleSVGPaintKind::Tag::ContextFill:
1386 pattern = aContextPaint->GetFillPattern(
1387 dt, fillOpacity, aContext->CurrentMatrixDouble(), aImgParams);
1388 break;
1389 case StyleSVGPaintKind::Tag::ContextStroke:
1390 pattern = aContextPaint->GetStrokePattern(
1391 dt, fillOpacity, aContext->CurrentMatrixDouble(), aImgParams);
1392 break;
1393 default:;
1395 if (pattern) {
1396 aOutPattern->Init(*pattern->GetPattern(dt));
1397 return;
1401 if (style->mFill.fallback.IsNone()) {
1402 return;
1405 // On failure, use the fallback colour in case we have an
1406 // objectBoundingBox where the width or height of the object is zero.
1407 // See http://www.w3.org/TR/SVG11/coords.html#ObjectBoundingBox
1408 sRGBColor color(sRGBColor::FromABGR(GetFallbackOrPaintColor(
1409 *aFrame->Style(), &nsStyleSVG::mFill, NS_RGB(0, 0, 0))));
1410 color.a *= fillOpacity;
1411 aOutPattern->InitColorPattern(ToDeviceColor(color));
1414 /* static */
1415 void SVGUtils::MakeStrokePatternFor(nsIFrame* aFrame, gfxContext* aContext,
1416 GeneralPattern* aOutPattern,
1417 imgDrawingParams& aImgParams,
1418 SVGContextPaint* aContextPaint) {
1419 const nsStyleSVG* style = aFrame->StyleSVG();
1420 if (style->mStroke.kind.IsNone()) {
1421 return;
1424 const float opacity = aFrame->StyleEffects()->mOpacity;
1426 float strokeOpacity = GetOpacity(style->mStrokeOpacity, aContextPaint);
1427 if (opacity < 1.0f && SVGUtils::CanOptimizeOpacity(aFrame)) {
1428 // Combine the group opacity into the stroke opacity (we will have skipped
1429 // creating an offscreen surface to apply the group opacity).
1430 strokeOpacity *= opacity;
1433 const DrawTarget* dt = aContext->GetDrawTarget();
1435 SVGPaintServerFrame* ps =
1436 SVGObserverUtils::GetAndObservePaintServer(aFrame, &nsStyleSVG::mStroke);
1438 if (ps) {
1439 RefPtr<gfxPattern> pattern = ps->GetPaintServerPattern(
1440 aFrame, dt, aContext->CurrentMatrixDouble(), &nsStyleSVG::mStroke,
1441 strokeOpacity, aImgParams);
1442 if (pattern) {
1443 pattern->CacheColorStops(dt);
1444 aOutPattern->Init(*pattern->GetPattern(dt));
1445 return;
1449 if (aContextPaint) {
1450 RefPtr<gfxPattern> pattern;
1451 switch (style->mStroke.kind.tag) {
1452 case StyleSVGPaintKind::Tag::ContextFill:
1453 pattern = aContextPaint->GetFillPattern(
1454 dt, strokeOpacity, aContext->CurrentMatrixDouble(), aImgParams);
1455 break;
1456 case StyleSVGPaintKind::Tag::ContextStroke:
1457 pattern = aContextPaint->GetStrokePattern(
1458 dt, strokeOpacity, aContext->CurrentMatrixDouble(), aImgParams);
1459 break;
1460 default:;
1462 if (pattern) {
1463 aOutPattern->Init(*pattern->GetPattern(dt));
1464 return;
1468 if (style->mStroke.fallback.IsNone()) {
1469 return;
1472 // On failure, use the fallback colour in case we have an
1473 // objectBoundingBox where the width or height of the object is zero.
1474 // See http://www.w3.org/TR/SVG11/coords.html#ObjectBoundingBox
1475 sRGBColor color(sRGBColor::FromABGR(GetFallbackOrPaintColor(
1476 *aFrame->Style(), &nsStyleSVG::mStroke, NS_RGBA(0, 0, 0, 0))));
1477 color.a *= strokeOpacity;
1478 aOutPattern->InitColorPattern(ToDeviceColor(color));
1481 /* static */
1482 float SVGUtils::GetOpacity(const StyleSVGOpacity& aOpacity,
1483 const SVGContextPaint* aContextPaint) {
1484 float opacity = 1.0f;
1485 switch (aOpacity.tag) {
1486 case StyleSVGOpacity::Tag::Opacity:
1487 return aOpacity.AsOpacity();
1488 case StyleSVGOpacity::Tag::ContextFillOpacity:
1489 if (aContextPaint) {
1490 opacity = aContextPaint->GetFillOpacity();
1492 break;
1493 case StyleSVGOpacity::Tag::ContextStrokeOpacity:
1494 if (aContextPaint) {
1495 opacity = aContextPaint->GetStrokeOpacity();
1497 break;
1499 return opacity;
1502 bool SVGUtils::HasStroke(const nsIFrame* aFrame,
1503 const SVGContextPaint* aContextPaint) {
1504 const nsStyleSVG* style = aFrame->StyleSVG();
1505 return style->HasStroke() && GetStrokeWidth(aFrame, aContextPaint) > 0;
1508 float SVGUtils::GetStrokeWidth(const nsIFrame* aFrame,
1509 const SVGContextPaint* aContextPaint) {
1510 nsIContent* content = aFrame->GetContent();
1511 if (content->IsText()) {
1512 content = content->GetParent();
1515 auto* ctx = SVGElement::FromNode(content);
1516 return SVGContentUtils::GetStrokeWidth(ctx, aFrame->Style(), aContextPaint);
1519 void SVGUtils::SetupStrokeGeometry(nsIFrame* aFrame, gfxContext* aContext,
1520 SVGContextPaint* aContextPaint) {
1521 MOZ_ASSERT(aFrame->GetContent()->IsSVGElement(), "bad cast");
1522 SVGContentUtils::AutoStrokeOptions strokeOptions;
1523 SVGContentUtils::GetStrokeOptions(&strokeOptions,
1524 SVGElement::FromNode(aFrame->GetContent()),
1525 aFrame->Style(), aContextPaint);
1527 if (strokeOptions.mLineWidth <= 0) {
1528 return;
1531 // SVGContentUtils::GetStrokeOptions gets the stroke options in CSS px;
1532 // convert to device pixels for gfxContext.
1533 float devPxPerCSSPx = aFrame->PresContext()->CSSToDevPixelScale().scale;
1535 aContext->SetLineWidth(strokeOptions.mLineWidth * devPxPerCSSPx);
1536 aContext->SetLineCap(strokeOptions.mLineCap);
1537 aContext->SetMiterLimit(strokeOptions.mMiterLimit);
1538 aContext->SetLineJoin(strokeOptions.mLineJoin);
1539 aContext->SetDash(strokeOptions.mDashPattern, strokeOptions.mDashLength,
1540 strokeOptions.mDashOffset, devPxPerCSSPx);
1543 uint16_t SVGUtils::GetGeometryHitTestFlags(const nsIFrame* aFrame) {
1544 uint16_t flags = 0;
1546 switch (aFrame->Style()->PointerEvents()) {
1547 case StylePointerEvents::None:
1548 break;
1549 case StylePointerEvents::Auto:
1550 case StylePointerEvents::Visiblepainted:
1551 if (aFrame->StyleVisibility()->IsVisible()) {
1552 if (!aFrame->StyleSVG()->mFill.kind.IsNone())
1553 flags |= SVG_HIT_TEST_FILL;
1554 if (!aFrame->StyleSVG()->mStroke.kind.IsNone())
1555 flags |= SVG_HIT_TEST_STROKE;
1556 if (!aFrame->StyleSVG()->mStrokeOpacity.IsOpacity() ||
1557 aFrame->StyleSVG()->mStrokeOpacity.AsOpacity() > 0)
1558 flags |= SVG_HIT_TEST_CHECK_MRECT;
1560 break;
1561 case StylePointerEvents::Visiblefill:
1562 if (aFrame->StyleVisibility()->IsVisible()) {
1563 flags |= SVG_HIT_TEST_FILL;
1565 break;
1566 case StylePointerEvents::Visiblestroke:
1567 if (aFrame->StyleVisibility()->IsVisible()) {
1568 flags |= SVG_HIT_TEST_STROKE;
1570 break;
1571 case StylePointerEvents::Visible:
1572 if (aFrame->StyleVisibility()->IsVisible()) {
1573 flags |= SVG_HIT_TEST_FILL | SVG_HIT_TEST_STROKE;
1575 break;
1576 case StylePointerEvents::Painted:
1577 if (!aFrame->StyleSVG()->mFill.kind.IsNone()) flags |= SVG_HIT_TEST_FILL;
1578 if (!aFrame->StyleSVG()->mStroke.kind.IsNone())
1579 flags |= SVG_HIT_TEST_STROKE;
1580 if (!aFrame->StyleSVG()->mStrokeOpacity.IsOpacity() ||
1581 aFrame->StyleSVG()->mStrokeOpacity.AsOpacity() > 0) {
1582 flags |= SVG_HIT_TEST_CHECK_MRECT;
1584 break;
1585 case StylePointerEvents::Fill:
1586 flags |= SVG_HIT_TEST_FILL;
1587 break;
1588 case StylePointerEvents::Stroke:
1589 flags |= SVG_HIT_TEST_STROKE;
1590 break;
1591 case StylePointerEvents::All:
1592 flags |= SVG_HIT_TEST_FILL | SVG_HIT_TEST_STROKE;
1593 break;
1594 default:
1595 NS_ERROR("not reached");
1596 break;
1599 return flags;
1602 void SVGUtils::PaintSVGGlyph(Element* aElement, gfxContext* aContext) {
1603 nsIFrame* frame = aElement->GetPrimaryFrame();
1604 ISVGDisplayableFrame* svgFrame = do_QueryFrame(frame);
1605 if (!svgFrame) {
1606 return;
1608 gfxMatrix m;
1609 if (frame->GetContent()->IsSVGElement()) {
1610 // PaintSVG() expects the passed transform to be the transform to its own
1611 // SVG user space, so we need to account for any 'transform' attribute:
1612 m = SVGUtils::GetTransformMatrixInUserSpace(frame);
1615 // SVG-in-OpenType is not allowed to paint external resources, so we can
1616 // just pass a dummy params into PatintSVG.
1617 imgDrawingParams dummy;
1618 svgFrame->PaintSVG(*aContext, m, dummy);
1621 bool SVGUtils::GetSVGGlyphExtents(const Element* aElement,
1622 const gfxMatrix& aSVGToAppSpace,
1623 gfxRect* aResult) {
1624 nsIFrame* frame = aElement->GetPrimaryFrame();
1625 ISVGDisplayableFrame* svgFrame = do_QueryFrame(frame);
1626 if (!svgFrame) {
1627 return false;
1630 gfxMatrix transform(aSVGToAppSpace);
1631 if (auto* svg = SVGElement::FromNode(frame->GetContent())) {
1632 transform = svg->PrependLocalTransformsTo(aSVGToAppSpace);
1635 *aResult =
1636 svgFrame
1637 ->GetBBoxContribution(gfx::ToMatrix(transform),
1638 SVGUtils::eBBoxIncludeFill |
1639 SVGUtils::eBBoxIncludeFillGeometry |
1640 SVGUtils::eBBoxIncludeStroke |
1641 SVGUtils::eBBoxIncludeStrokeGeometry |
1642 SVGUtils::eBBoxIncludeMarkers)
1643 .ToThebesRect();
1644 return true;
1647 nsRect SVGUtils::ToCanvasBounds(const gfxRect& aUserspaceRect,
1648 const gfxMatrix& aToCanvas,
1649 const nsPresContext* presContext) {
1650 return nsLayoutUtils::RoundGfxRectToAppRect(
1651 aToCanvas.TransformBounds(aUserspaceRect),
1652 presContext->AppUnitsPerDevPixel());
1655 gfxMatrix SVGUtils::GetCSSPxToDevPxMatrix(const nsIFrame* aNonSVGFrame) {
1656 float devPxPerCSSPx = aNonSVGFrame->PresContext()->CSSToDevPixelScale().scale;
1658 return gfxMatrix(devPxPerCSSPx, 0.0, 0.0, devPxPerCSSPx, 0.0, 0.0);
1661 gfxMatrix SVGUtils::GetTransformMatrixInUserSpace(const nsIFrame* aFrame) {
1662 // We check element instead of aFrame directly because SVG element
1663 // may have non-SVG frame, <tspan> for example.
1664 MOZ_ASSERT(aFrame->GetContent() && aFrame->GetContent()->IsSVGElement(),
1665 "Only use this wrapper for SVG elements");
1667 if (!aFrame->IsTransformed()) {
1668 return {};
1671 nsStyleTransformMatrix::TransformReferenceBox refBox(aFrame);
1672 nsDisplayTransform::FrameTransformProperties properties{
1673 aFrame, refBox, AppUnitsPerCSSPixel()};
1675 // SVG elements can have x/y offset, their default transform origin
1676 // is the origin of user space, not the top left point of the frame.
1677 Point3D svgTransformOrigin{
1678 properties.mToTransformOrigin.x - CSSPixel::FromAppUnits(refBox.X()),
1679 properties.mToTransformOrigin.y - CSSPixel::FromAppUnits(refBox.Y()),
1680 properties.mToTransformOrigin.z};
1682 Matrix svgTransform;
1683 Matrix4x4 trans;
1684 (void)aFrame->IsSVGTransformed(&svgTransform);
1686 if (properties.HasTransform()) {
1687 trans = nsStyleTransformMatrix::ReadTransforms(
1688 properties.mTranslate, properties.mRotate, properties.mScale,
1689 properties.mMotion, properties.mTransform, refBox,
1690 AppUnitsPerCSSPixel());
1691 } else {
1692 trans = Matrix4x4::From2D(svgTransform);
1695 trans.ChangeBasis(svgTransformOrigin);
1697 Matrix mm;
1698 trans.ProjectTo2D();
1699 (void)trans.CanDraw2D(&mm);
1701 return ThebesMatrix(mm);
1704 } // namespace mozilla