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/. */
8 // This is also necessary to ensure our definition of M_SQRT1_2 is picked up
9 #include "nsSVGUtils.h"
12 // Keep others in (case-insensitive) order:
13 #include "gfx2DGlue.h"
14 #include "gfxContext.h"
15 #include "gfxMatrix.h"
16 #include "gfxPlatform.h"
19 #include "nsCSSClipPathInstance.h"
20 #include "nsCSSFrameConstructor.h"
21 #include "nsDisplayList.h"
22 #include "nsFilterInstance.h"
23 #include "nsFrameList.h"
24 #include "nsGkAtoms.h"
25 #include "nsIContent.h"
27 #include "nsLayoutUtils.h"
28 #include "nsPresContext.h"
29 #include "nsStyleStruct.h"
30 #include "nsStyleTransformMatrix.h"
31 #include "SVGAnimatedLength.h"
32 #include "nsSVGClipPathFrame.h"
33 #include "nsSVGContainerFrame.h"
34 #include "SVGContentUtils.h"
35 #include "nsSVGDisplayableFrame.h"
36 #include "nsSVGFilterPaintCallback.h"
37 #include "nsSVGForeignObjectFrame.h"
38 #include "SVGGeometryFrame.h"
39 #include "nsSVGInnerSVGFrame.h"
40 #include "nsSVGIntegrationUtils.h"
41 #include "nsSVGMaskFrame.h"
42 #include "SVGObserverUtils.h"
43 #include "nsSVGOuterSVGFrame.h"
44 #include "nsSVGPaintServerFrame.h"
45 #include "SVGTextFrame.h"
46 #include "nsTextFrame.h"
47 #include "mozilla/Preferences.h"
48 #include "mozilla/StaticPrefs_svg.h"
49 #include "mozilla/SVGContextPaint.h"
50 #include "mozilla/Unused.h"
51 #include "mozilla/gfx/2D.h"
52 #include "mozilla/gfx/PatternHelpers.h"
53 #include "mozilla/dom/Document.h"
54 #include "mozilla/dom/SVGClipPathElement.h"
55 #include "mozilla/dom/SVGGeometryElement.h"
56 #include "mozilla/dom/SVGPathElement.h"
57 #include "mozilla/dom/SVGUnitTypesBinding.h"
58 #include "mozilla/dom/SVGViewportElement.h"
60 using namespace mozilla
;
61 using namespace mozilla::dom
;
62 using namespace mozilla::dom::SVGUnitTypes_Binding
;
63 using namespace mozilla::gfx
;
64 using namespace mozilla::image
;
66 bool NS_SVGDisplayListHitTestingEnabled() {
67 return StaticPrefs::svg_display_lists_hit_testing_enabled();
70 bool NS_SVGDisplayListPaintingEnabled() {
71 return StaticPrefs::svg_display_lists_painting_enabled();
74 bool NS_SVGNewGetBBoxEnabled() {
75 return StaticPrefs::svg_new_getBBox_enabled();
78 // we only take the address of this:
79 static mozilla::gfx::UserDataKey sSVGAutoRenderStateKey
;
81 SVGAutoRenderState::SVGAutoRenderState(
82 DrawTarget
* aDrawTarget MOZ_GUARD_OBJECT_NOTIFIER_PARAM_IN_IMPL
)
83 : mDrawTarget(aDrawTarget
),
84 mOriginalRenderState(nullptr),
85 mPaintingToWindow(false) {
86 MOZ_GUARD_OBJECT_NOTIFIER_INIT
;
87 mOriginalRenderState
= aDrawTarget
->RemoveUserData(&sSVGAutoRenderStateKey
);
88 // We always remove ourselves from aContext before it dies, so
89 // passing nullptr as the destroy function is okay.
90 aDrawTarget
->AddUserData(&sSVGAutoRenderStateKey
, this, nullptr);
93 SVGAutoRenderState::~SVGAutoRenderState() {
94 mDrawTarget
->RemoveUserData(&sSVGAutoRenderStateKey
);
95 if (mOriginalRenderState
) {
96 mDrawTarget
->AddUserData(&sSVGAutoRenderStateKey
, mOriginalRenderState
,
101 void SVGAutoRenderState::SetPaintingToWindow(bool aPaintingToWindow
) {
102 mPaintingToWindow
= aPaintingToWindow
;
106 bool SVGAutoRenderState::IsPaintingToWindow(DrawTarget
* aDrawTarget
) {
107 void* state
= aDrawTarget
->GetUserData(&sSVGAutoRenderStateKey
);
109 return static_cast<SVGAutoRenderState
*>(state
)->mPaintingToWindow
;
114 nsRect
nsSVGUtils::GetPostFilterVisualOverflowRect(
115 nsIFrame
* aFrame
, const nsRect
& aPreFilterRect
) {
116 MOZ_ASSERT(aFrame
->GetStateBits() & NS_FRAME_SVG_LAYOUT
,
117 "Called on invalid frame type");
119 // Note: we do not return here for eHasNoRefs since we must still handle any
120 // CSS filter functions.
121 // TODO: We currently pass nullptr instead of an nsTArray* here, but we
122 // actually should get the filter frames and then pass them into
123 // GetPostFilterBounds below! See bug 1494263.
124 // TODO: we should really return an empty rect for eHasRefsSomeInvalid since
125 // in that case we disable painting of the element.
126 if (!aFrame
->StyleEffects()->HasFilters() ||
127 SVGObserverUtils::GetAndObserveFilters(aFrame
, nullptr) ==
128 SVGObserverUtils::eHasRefsSomeInvalid
) {
129 return aPreFilterRect
;
132 return nsFilterInstance::GetPostFilterBounds(aFrame
, nullptr,
136 bool nsSVGUtils::OuterSVGIsCallingReflowSVG(nsIFrame
* aFrame
) {
137 return GetOuterSVGFrame(aFrame
)->IsCallingReflowSVG();
140 bool nsSVGUtils::AnyOuterSVGIsCallingReflowSVG(nsIFrame
* aFrame
) {
141 nsSVGOuterSVGFrame
* outer
= GetOuterSVGFrame(aFrame
);
143 if (outer
->IsCallingReflowSVG()) {
146 outer
= GetOuterSVGFrame(outer
->GetParent());
151 void nsSVGUtils::ScheduleReflowSVG(nsIFrame
* aFrame
) {
152 MOZ_ASSERT(aFrame
->IsFrameOfType(nsIFrame::eSVG
), "Passed bad frame!");
154 // If this is triggered, the callers should be fixed to call us before
155 // ReflowSVG is called. If we try to mark dirty bits on frames while we're
156 // in the process of removing them, things will get messed up.
157 NS_ASSERTION(!OuterSVGIsCallingReflowSVG(aFrame
),
158 "Do not call under nsSVGDisplayableFrame::ReflowSVG!");
160 // We don't call SVGObserverUtils::InvalidateRenderingObservers here because
161 // we should only be called under InvalidateAndScheduleReflowSVG (which
162 // calls InvalidateBounds) or nsSVGDisplayContainerFrame::InsertFrames
163 // (at which point the frame has no observers).
165 if (aFrame
->GetStateBits() & NS_FRAME_IS_NONDISPLAY
) {
169 if (aFrame
->GetStateBits() & (NS_FRAME_IS_DIRTY
| NS_FRAME_FIRST_REFLOW
)) {
170 // Nothing to do if we're already dirty, or if the outer-<svg>
171 // hasn't yet had its initial reflow.
175 nsSVGOuterSVGFrame
* outerSVGFrame
= nullptr;
177 // We must not add dirty bits to the nsSVGOuterSVGFrame or else
178 // PresShell::FrameNeedsReflow won't work when we pass it in below.
179 if (aFrame
->IsSVGOuterSVGFrame()) {
180 outerSVGFrame
= static_cast<nsSVGOuterSVGFrame
*>(aFrame
);
182 aFrame
->MarkSubtreeDirty();
184 nsIFrame
* f
= aFrame
->GetParent();
185 while (f
&& !f
->IsSVGOuterSVGFrame()) {
186 if (f
->GetStateBits() &
187 (NS_FRAME_IS_DIRTY
| NS_FRAME_HAS_DIRTY_CHILDREN
)) {
190 f
->AddStateBits(NS_FRAME_HAS_DIRTY_CHILDREN
);
192 MOZ_ASSERT(f
->IsFrameOfType(nsIFrame::eSVG
),
193 "IsSVGOuterSVGFrame check above not valid!");
196 outerSVGFrame
= static_cast<nsSVGOuterSVGFrame
*>(f
);
198 MOZ_ASSERT(outerSVGFrame
&& outerSVGFrame
->IsSVGOuterSVGFrame(),
199 "Did not find nsSVGOuterSVGFrame!");
202 if (outerSVGFrame
->GetStateBits() & NS_FRAME_IN_REFLOW
) {
203 // We're currently under an nsSVGOuterSVGFrame::Reflow call so there is no
204 // need to call PresShell::FrameNeedsReflow, since we have an
205 // nsSVGOuterSVGFrame::DidReflow call pending.
209 nsFrameState dirtyBit
=
210 (outerSVGFrame
== aFrame
? NS_FRAME_IS_DIRTY
211 : NS_FRAME_HAS_DIRTY_CHILDREN
);
213 aFrame
->PresShell()->FrameNeedsReflow(outerSVGFrame
, IntrinsicDirty::Resize
,
217 bool nsSVGUtils::NeedsReflowSVG(nsIFrame
* aFrame
) {
218 MOZ_ASSERT(aFrame
->IsFrameOfType(nsIFrame::eSVG
),
219 "SVG uses bits differently!");
221 // The flags we test here may change, hence why we have this separate
223 return NS_SUBTREE_DIRTY(aFrame
);
226 Size
nsSVGUtils::GetContextSize(const nsIFrame
* aFrame
) {
229 MOZ_ASSERT(aFrame
->GetContent()->IsSVGElement(), "bad cast");
230 const SVGElement
* element
= static_cast<SVGElement
*>(aFrame
->GetContent());
232 SVGViewportElement
* ctx
= element
->GetCtx();
234 size
.width
= ctx
->GetLength(SVGContentUtils::X
);
235 size
.height
= ctx
->GetLength(SVGContentUtils::Y
);
240 float nsSVGUtils::ObjectSpace(const gfxRect
& aRect
,
241 const SVGAnimatedLength
* aLength
) {
244 switch (aLength
->GetCtxType()) {
245 case SVGContentUtils::X
:
246 axis
= aRect
.Width();
248 case SVGContentUtils::Y
:
249 axis
= aRect
.Height();
251 case SVGContentUtils::XY
:
252 axis
= float(SVGContentUtils::ComputeNormalizedHypotenuse(
253 aRect
.Width(), aRect
.Height()));
256 MOZ_ASSERT_UNREACHABLE("unexpected ctx type");
260 if (aLength
->IsPercentage()) {
261 // Multiply first to avoid precision errors:
262 return axis
* aLength
->GetAnimValInSpecifiedUnits() / 100;
264 return aLength
->GetAnimValue(static_cast<SVGViewportElement
*>(nullptr)) *
268 float nsSVGUtils::UserSpace(SVGElement
* aSVGElement
,
269 const SVGAnimatedLength
* aLength
) {
270 return aLength
->GetAnimValue(aSVGElement
);
273 float nsSVGUtils::UserSpace(nsIFrame
* aNonSVGContext
,
274 const SVGAnimatedLength
* aLength
) {
275 return aLength
->GetAnimValue(aNonSVGContext
);
278 float nsSVGUtils::UserSpace(const UserSpaceMetrics
& aMetrics
,
279 const SVGAnimatedLength
* aLength
) {
280 return aLength
->GetAnimValue(aMetrics
);
283 nsSVGOuterSVGFrame
* nsSVGUtils::GetOuterSVGFrame(nsIFrame
* aFrame
) {
285 if (aFrame
->IsSVGOuterSVGFrame()) {
286 return static_cast<nsSVGOuterSVGFrame
*>(aFrame
);
288 aFrame
= aFrame
->GetParent();
294 nsIFrame
* nsSVGUtils::GetOuterSVGFrameAndCoveredRegion(nsIFrame
* aFrame
,
296 nsSVGDisplayableFrame
* svg
= do_QueryFrame(aFrame
);
300 nsSVGOuterSVGFrame
* outer
= GetOuterSVGFrame(aFrame
);
305 if (aFrame
->GetStateBits() & NS_FRAME_IS_NONDISPLAY
) {
306 *aRect
= nsRect(0, 0, 0, 0);
309 nsSVGUtils::eForGetClientRects
| nsSVGUtils::eBBoxIncludeFill
|
310 nsSVGUtils::eBBoxIncludeStroke
| nsSVGUtils::eBBoxIncludeMarkers
;
312 auto ctm
= nsLayoutUtils::GetTransformToAncestor(aFrame
, outer
);
314 float initPositionX
= NSAppUnitsToFloatPixels(aFrame
->GetPosition().x
,
315 AppUnitsPerCSSPixel()),
316 initPositionY
= NSAppUnitsToFloatPixels(aFrame
->GetPosition().y
,
317 AppUnitsPerCSSPixel());
322 gfxMatrix m
= ThebesMatrix(mm
);
324 float appUnitsPerDevPixel
= aFrame
->PresContext()->AppUnitsPerDevPixel();
325 float devPixelPerCSSPixel
=
326 float(AppUnitsPerCSSPixel()) / appUnitsPerDevPixel
;
328 // The matrix that GetBBox accepts should operate on "user space",
329 // i.e. with CSS pixel unit.
330 m
= m
.PreScale(devPixelPerCSSPixel
, devPixelPerCSSPixel
);
332 // Both nsSVGUtils::GetBBox and nsLayoutUtils::GetTransformToAncestor
333 // will count this displacement, we should remove it here to avoid
335 m
= m
.PreTranslate(-initPositionX
, -initPositionY
);
337 SVGBBox bbox
= nsSVGUtils::GetBBox(aFrame
, flags
, &m
);
338 *aRect
= nsLayoutUtils::RoundGfxRectToAppRect(bbox
, appUnitsPerDevPixel
);
344 gfxMatrix
nsSVGUtils::GetCanvasTM(nsIFrame
* aFrame
) {
345 // XXX yuck, we really need a common interface for GetCanvasTM
347 if (!aFrame
->IsFrameOfType(nsIFrame::eSVG
)) {
348 return GetCSSPxToDevPxMatrix(aFrame
);
351 LayoutFrameType type
= aFrame
->Type();
352 if (type
== LayoutFrameType::SVGForeignObject
) {
353 return static_cast<nsSVGForeignObjectFrame
*>(aFrame
)->GetCanvasTM();
355 if (type
== LayoutFrameType::SVGOuterSVG
) {
356 return GetCSSPxToDevPxMatrix(aFrame
);
359 nsSVGContainerFrame
* containerFrame
= do_QueryFrame(aFrame
);
360 if (containerFrame
) {
361 return containerFrame
->GetCanvasTM();
364 return static_cast<SVGGeometryFrame
*>(aFrame
)->GetCanvasTM();
367 void nsSVGUtils::NotifyChildrenOfSVGChange(nsIFrame
* aFrame
, uint32_t aFlags
) {
368 for (nsIFrame
* kid
: aFrame
->PrincipalChildList()) {
369 nsSVGDisplayableFrame
* SVGFrame
= do_QueryFrame(kid
);
371 SVGFrame
->NotifySVGChanged(aFlags
);
373 NS_ASSERTION(kid
->IsFrameOfType(nsIFrame::eSVG
) ||
374 nsSVGUtils::IsInSVGTextSubtree(kid
),
375 "SVG frame expected");
376 // recurse into the children of container frames e.g. <clipPath>, <mask>
377 // in case they have child frames with transformation matrices
378 if (kid
->IsFrameOfType(nsIFrame::eSVG
)) {
379 NotifyChildrenOfSVGChange(kid
, aFlags
);
385 // ************************************************************
387 class SVGPaintCallback
: public nsSVGFilterPaintCallback
{
389 virtual void Paint(gfxContext
& aContext
, nsIFrame
* aTarget
,
390 const gfxMatrix
& aTransform
, const nsIntRect
* aDirtyRect
,
391 imgDrawingParams
& aImgParams
) override
{
392 nsSVGDisplayableFrame
* svgFrame
= do_QueryFrame(aTarget
);
393 NS_ASSERTION(svgFrame
, "Expected SVG frame here");
395 nsIntRect
* dirtyRect
= nullptr;
396 nsIntRect tmpDirtyRect
;
398 // aDirtyRect is in user-space pixels, we need to convert to
399 // outer-SVG-frame-relative device pixels.
401 gfxMatrix userToDeviceSpace
= aTransform
;
402 if (userToDeviceSpace
.IsSingular()) {
405 gfxRect dirtyBounds
= userToDeviceSpace
.TransformBounds(gfxRect(
406 aDirtyRect
->x
, aDirtyRect
->y
, aDirtyRect
->width
, aDirtyRect
->height
));
407 dirtyBounds
.RoundOut();
408 if (gfxUtils::GfxRectToIntRect(dirtyBounds
, &tmpDirtyRect
)) {
409 dirtyRect
= &tmpDirtyRect
;
413 svgFrame
->PaintSVG(aContext
, nsSVGUtils::GetCSSPxToDevPxMatrix(aTarget
),
414 aImgParams
, dirtyRect
);
418 float nsSVGUtils::ComputeOpacity(nsIFrame
* aFrame
, bool aHandleOpacity
) {
419 float opacity
= aFrame
->StyleEffects()->mOpacity
;
421 if (opacity
!= 1.0f
&&
422 (nsSVGUtils::CanOptimizeOpacity(aFrame
) || !aHandleOpacity
)) {
429 void nsSVGUtils::DetermineMaskUsage(nsIFrame
* aFrame
, bool aHandleOpacity
,
431 aUsage
.opacity
= ComputeOpacity(aFrame
, aHandleOpacity
);
433 nsIFrame
* firstFrame
=
434 nsLayoutUtils::FirstContinuationOrIBSplitSibling(aFrame
);
436 const nsStyleSVGReset
* svgReset
= firstFrame
->StyleSVGReset();
438 nsTArray
<nsSVGMaskFrame
*> maskFrames
;
439 // XXX check return value?
440 SVGObserverUtils::GetAndObserveMasks(firstFrame
, &maskFrames
);
441 aUsage
.shouldGenerateMaskLayer
= (maskFrames
.Length() > 0);
443 nsSVGClipPathFrame
* clipPathFrame
;
444 // XXX check return value?
445 SVGObserverUtils::GetAndObserveClipPath(firstFrame
, &clipPathFrame
);
446 MOZ_ASSERT(!clipPathFrame
||
447 svgReset
->mClipPath
.GetType() == StyleShapeSourceType::Image
);
449 switch (svgReset
->mClipPath
.GetType()) {
450 case StyleShapeSourceType::Image
:
452 if (clipPathFrame
->IsTrivial()) {
453 aUsage
.shouldApplyClipPath
= true;
455 aUsage
.shouldGenerateClipMaskLayer
= true;
459 case StyleShapeSourceType::Shape
:
460 case StyleShapeSourceType::Box
:
461 case StyleShapeSourceType::Path
:
462 aUsage
.shouldApplyBasicShapeOrPath
= true;
464 case StyleShapeSourceType::None
:
465 MOZ_ASSERT(!aUsage
.shouldGenerateClipMaskLayer
&&
466 !aUsage
.shouldApplyClipPath
&&
467 !aUsage
.shouldApplyBasicShapeOrPath
);
470 MOZ_ASSERT_UNREACHABLE("Unsupported clip-path type.");
475 class MixModeBlender
{
477 typedef mozilla::gfx::Factory Factory
;
479 MixModeBlender(nsIFrame
* aFrame
, gfxContext
* aContext
)
480 : mFrame(aFrame
), mSourceCtx(aContext
) {
481 MOZ_ASSERT(mFrame
&& mSourceCtx
);
484 bool ShouldCreateDrawTargetForBlend() const {
485 return mFrame
->StyleEffects()->mMixBlendMode
!= NS_STYLE_BLEND_NORMAL
;
488 gfxContext
* CreateBlendTarget(const gfxMatrix
& aTransform
) {
489 MOZ_ASSERT(ShouldCreateDrawTargetForBlend());
491 // Create a temporary context to draw to so we can blend it back with
493 IntRect drawRect
= ComputeClipExtsInDeviceSpace(aTransform
);
495 RefPtr
<DrawTarget
> targetDT
=
496 mSourceCtx
->GetDrawTarget()->CreateSimilarDrawTarget(
497 drawRect
.Size(), SurfaceFormat::B8G8R8A8
);
498 if (!targetDT
|| !targetDT
->IsValid()) {
502 MOZ_ASSERT(!mTargetCtx
,
503 "CreateBlendTarget is designed to be used once only.");
505 mTargetCtx
= gfxContext::CreateOrNull(targetDT
);
506 MOZ_ASSERT(mTargetCtx
); // already checked the draw target above
507 mTargetCtx
->SetMatrix(mSourceCtx
->CurrentMatrix() *
508 Matrix::Translation(-drawRect
.TopLeft()));
510 mTargetOffset
= drawRect
.TopLeft();
515 void BlendToTarget() {
516 MOZ_ASSERT(ShouldCreateDrawTargetForBlend());
517 MOZ_ASSERT(mTargetCtx
,
518 "BlendToTarget should be used after CreateBlendTarget.");
520 RefPtr
<SourceSurface
> targetSurf
= mTargetCtx
->GetDrawTarget()->Snapshot();
522 gfxContextAutoSaveRestore
save(mSourceCtx
);
523 mSourceCtx
->SetMatrix(Matrix()); // This will be restored right after.
524 RefPtr
<gfxPattern
> pattern
= new gfxPattern(
525 targetSurf
, Matrix::Translation(mTargetOffset
.x
, mTargetOffset
.y
));
526 mSourceCtx
->SetPattern(pattern
);
531 MixModeBlender() = delete;
533 IntRect
ComputeClipExtsInDeviceSpace(const gfxMatrix
& aTransform
) {
534 // These are used if we require a temporary surface for a custom blend
535 // mode. Clip the source context first, so that we can generate a smaller
536 // temporary surface. (Since we will clip this context in
537 // SetupContextMatrix, a pair of save/restore is needed.)
538 gfxContextAutoSaveRestore
saver(mSourceCtx
);
540 if (!(mFrame
->GetStateBits() & NS_FRAME_IS_NONDISPLAY
)) {
541 // aFrame has a valid visual overflow rect, so clip to it before calling
542 // PushGroup() to minimize the size of the surfaces we'll composite:
543 gfxContextMatrixAutoSaveRestore
matrixAutoSaveRestore(mSourceCtx
);
544 mSourceCtx
->Multiply(aTransform
);
545 nsRect overflowRect
= mFrame
->GetVisualOverflowRectRelativeToSelf();
546 if (mFrame
->IsFrameOfType(nsIFrame::eSVGGeometry
) ||
547 nsSVGUtils::IsInSVGTextSubtree(mFrame
)) {
548 // Unlike containers, leaf frames do not include GetPosition() in
550 overflowRect
= overflowRect
+ mFrame
->GetPosition();
552 mSourceCtx
->Clip(NSRectToSnappedRect(
553 overflowRect
, mFrame
->PresContext()->AppUnitsPerDevPixel(),
554 *mSourceCtx
->GetDrawTarget()));
557 // Get the clip extents in device space.
558 gfxRect clippedFrameSurfaceRect
=
559 mSourceCtx
->GetClipExtents(gfxContext::eDeviceSpace
);
560 clippedFrameSurfaceRect
.RoundOut();
563 ToRect(clippedFrameSurfaceRect
).ToIntRect(&result
);
565 return Factory::CheckSurfaceSize(result
.Size()) ? result
: IntRect();
569 gfxContext
* mSourceCtx
;
570 RefPtr
<gfxContext
> mTargetCtx
;
571 IntPoint mTargetOffset
;
574 void nsSVGUtils::PaintFrameWithEffects(nsIFrame
* aFrame
, gfxContext
& aContext
,
575 const gfxMatrix
& aTransform
,
576 imgDrawingParams
& aImgParams
,
577 const nsIntRect
* aDirtyRect
) {
578 NS_ASSERTION(!NS_SVGDisplayListPaintingEnabled() ||
579 (aFrame
->GetStateBits() & NS_FRAME_IS_NONDISPLAY
) ||
580 aFrame
->PresContext()->Document()->IsSVGGlyphsDocument(),
581 "If display lists are enabled, only painting of non-display "
582 "SVG should take this code path");
584 nsSVGDisplayableFrame
* svgFrame
= do_QueryFrame(aFrame
);
590 DetermineMaskUsage(aFrame
, true, maskUsage
);
591 if (maskUsage
.opacity
== 0.0f
) {
595 const nsIContent
* content
= aFrame
->GetContent();
596 if (content
->IsSVGElement() &&
597 !static_cast<const SVGElement
*>(content
)->HasValidDimensions()) {
601 if (aDirtyRect
&& !(aFrame
->GetStateBits() & NS_FRAME_IS_NONDISPLAY
)) {
602 // Here we convert aFrame's paint bounds to outer-<svg> device space,
603 // compare it to aDirtyRect, and return early if they don't intersect.
604 // We don't do this optimization for nondisplay SVG since nondisplay
605 // SVG doesn't maintain bounds/overflow rects.
606 nsRect overflowRect
= aFrame
->GetVisualOverflowRectRelativeToSelf();
607 if (aFrame
->IsFrameOfType(nsIFrame::eSVGGeometry
) ||
608 nsSVGUtils::IsInSVGTextSubtree(aFrame
)) {
609 // Unlike containers, leaf frames do not include GetPosition() in
611 overflowRect
= overflowRect
+ aFrame
->GetPosition();
613 int32_t appUnitsPerDevPx
= aFrame
->PresContext()->AppUnitsPerDevPixel();
614 gfxMatrix tm
= aTransform
;
615 if (aFrame
->IsFrameOfType(nsIFrame::eSVG
| nsIFrame::eSVGContainer
)) {
616 gfx::Matrix childrenOnlyTM
;
617 if (static_cast<nsSVGContainerFrame
*>(aFrame
)->HasChildrenOnlyTransform(
619 // Undo the children-only transform:
620 if (!childrenOnlyTM
.Invert()) {
623 tm
= ThebesMatrix(childrenOnlyTM
) * tm
;
627 TransformFrameRectToOuterSVG(overflowRect
, tm
, aFrame
->PresContext())
628 .ToOutsidePixels(appUnitsPerDevPx
);
629 if (!aDirtyRect
->Intersects(bounds
)) {
634 /* SVG defines the following rendering model:
640 * 5. Apply clipping, masking, group opacity
642 * We follow this, but perform a couple of optimizations:
644 * + Use cairo's clipPath when representable natively (single object
647 * + Merge opacity and masking if both used together.
650 /* Properties are added lazily and may have been removed by a restyle,
651 so make sure all applicable ones are set again. */
652 nsSVGClipPathFrame
* clipPathFrame
;
653 nsTArray
<nsSVGMaskFrame
*> maskFrames
;
654 // TODO: We currently pass nullptr instead of an nsTArray* here, but we
655 // actually should get the filter frames and then pass them into
656 // PaintFilteredFrame below! See bug 1494263.
657 if (SVGObserverUtils::GetAndObserveFilters(aFrame
, nullptr) ==
658 SVGObserverUtils::eHasRefsSomeInvalid
||
659 SVGObserverUtils::GetAndObserveClipPath(aFrame
, &clipPathFrame
) ==
660 SVGObserverUtils::eHasRefsSomeInvalid
||
661 SVGObserverUtils::GetAndObserveMasks(aFrame
, &maskFrames
) ==
662 SVGObserverUtils::eHasRefsSomeInvalid
) {
663 // Some resource is invalid. We shouldn't paint anything.
667 nsSVGMaskFrame
* maskFrame
= maskFrames
.IsEmpty() ? nullptr : maskFrames
[0];
669 MixModeBlender
blender(aFrame
, &aContext
);
670 gfxContext
* target
= blender
.ShouldCreateDrawTargetForBlend()
671 ? blender
.CreateBlendTarget(aTransform
)
678 /* Check if we need to do additional operations on this child's
679 * rendering, which necessitates rendering into another surface. */
680 bool shouldGenerateMask
=
681 (maskUsage
.opacity
!= 1.0f
|| maskUsage
.shouldGenerateClipMaskLayer
||
682 maskUsage
.shouldGenerateMaskLayer
);
683 bool shouldPushMask
= false;
685 if (shouldGenerateMask
) {
686 Matrix maskTransform
;
687 RefPtr
<SourceSurface
> maskSurface
;
689 // maskFrame can be nullptr even if maskUsage.shouldGenerateMaskLayer is
690 // true. That happens when a user gives an unresolvable mask-id, such as
692 // mask:url(#id-which-does-not-exist)
693 // Since we only uses nsSVGUtils with SVG elements, not like mask on an
694 // HTML element, we should treat an unresolvable mask as no-mask here.
695 if (maskUsage
.shouldGenerateMaskLayer
&& maskFrame
) {
696 StyleMaskMode maskMode
=
697 aFrame
->StyleSVGReset()->mMask
.mLayers
[0].mMaskMode
;
698 nsSVGMaskFrame::MaskParams
params(&aContext
, aFrame
, aTransform
,
699 maskUsage
.opacity
, maskMode
,
701 // We want the mask to be untransformed so use the inverse of the current
702 // transform as the maskTransform to compensate.
703 maskTransform
= aContext
.CurrentMatrix();
704 maskTransform
.Invert();
706 maskSurface
= maskFrame
->GetMaskForMaskedFrame(params
);
709 // Either entire surface is clipped out, or gfx buffer allocation
710 // failure in nsSVGMaskFrame::GetMaskForMaskedFrame.
713 shouldPushMask
= true;
716 if (maskUsage
.shouldGenerateClipMaskLayer
) {
717 RefPtr
<SourceSurface
> clipMaskSurface
= clipPathFrame
->GetClipMask(
718 aContext
, aFrame
, aTransform
, maskSurface
, maskTransform
);
719 if (clipMaskSurface
) {
720 // We want the mask to be untransformed so use the inverse of the
721 // current transform as the maskTransform to compensate.
722 maskTransform
= aContext
.CurrentMatrix();
723 maskTransform
.Invert();
724 maskSurface
= clipMaskSurface
;
726 // Either entire surface is clipped out, or gfx buffer allocation
727 // failure in nsSVGClipPathFrame::GetClipMask.
730 shouldPushMask
= true;
733 if (!maskUsage
.shouldGenerateClipMaskLayer
&&
734 !maskUsage
.shouldGenerateMaskLayer
) {
735 shouldPushMask
= true;
738 // SVG mask multiply opacity into maskSurface already, so we do not bother
739 // to apply opacity again.
740 if (shouldPushMask
) {
741 target
->PushGroupForBlendBack(gfxContentType::COLOR_ALPHA
,
742 maskFrame
? 1.0 : maskUsage
.opacity
,
743 maskSurface
, maskTransform
);
747 /* If this frame has only a trivial clipPath, set up cairo's clipping now so
748 * we can just do normal painting and get it clipped appropriately.
750 if (maskUsage
.shouldApplyClipPath
|| maskUsage
.shouldApplyBasicShapeOrPath
) {
751 if (maskUsage
.shouldApplyClipPath
) {
752 clipPathFrame
->ApplyClipPath(aContext
, aFrame
, aTransform
);
754 nsCSSClipPathInstance::ApplyBasicShapeOrPathClip(aContext
, aFrame
,
759 /* Paint the child */
761 // We know we don't have eHasRefsSomeInvalid due to the check above. We
762 // don't test for eHasNoRefs here though since even if we have that we may
763 // still have CSS filter functions to handle. We have to check the style.
764 if (aFrame
->StyleEffects()->HasFilters()) {
765 nsRegion
* dirtyRegion
= nullptr;
766 nsRegion tmpDirtyRegion
;
768 // aDirtyRect is in outer-<svg> device pixels, but the filter code needs
769 // it in frame space.
770 gfxMatrix userToDeviceSpace
= aTransform
;
771 if (userToDeviceSpace
.IsSingular()) {
774 gfxMatrix deviceToUserSpace
= userToDeviceSpace
;
775 deviceToUserSpace
.Invert();
776 gfxRect dirtyBounds
= deviceToUserSpace
.TransformBounds(gfxRect(
777 aDirtyRect
->x
, aDirtyRect
->y
, aDirtyRect
->width
, aDirtyRect
->height
));
778 tmpDirtyRegion
= nsLayoutUtils::RoundGfxRectToAppRect(
779 dirtyBounds
, AppUnitsPerCSSPixel()) -
780 aFrame
->GetPosition();
781 dirtyRegion
= &tmpDirtyRegion
;
784 gfxContextMatrixAutoSaveRestore
autoSR(target
);
786 // 'target' is currently scaled such that its user space units are CSS
787 // pixels (SVG user space units). But PaintFilteredFrame expects it to be
788 // scaled in such a way that its user space units are device pixels. So we
789 // have to adjust the scale.
790 gfxMatrix reverseScaleMatrix
= nsSVGUtils::GetCSSPxToDevPxMatrix(aFrame
);
791 DebugOnly
<bool> invertible
= reverseScaleMatrix
.Invert();
792 target
->SetMatrixDouble(reverseScaleMatrix
* aTransform
*
793 target
->CurrentMatrixDouble());
795 SVGPaintCallback paintCallback
;
796 nsFilterInstance::PaintFilteredFrame(aFrame
, target
, &paintCallback
,
797 dirtyRegion
, aImgParams
);
799 svgFrame
->PaintSVG(*target
, aTransform
, aImgParams
, aDirtyRect
);
802 if (maskUsage
.shouldApplyClipPath
|| maskUsage
.shouldApplyBasicShapeOrPath
) {
806 if (shouldPushMask
) {
807 target
->PopGroupAndBlend();
810 if (blender
.ShouldCreateDrawTargetForBlend()) {
811 MOZ_ASSERT(target
!= &aContext
);
812 blender
.BlendToTarget();
816 bool nsSVGUtils::HitTestClip(nsIFrame
* aFrame
, const gfxPoint
& aPoint
) {
817 // If the clip-path property references non-existent or invalid clipPath
818 // element(s) we ignore it.
819 nsSVGClipPathFrame
* clipPathFrame
;
820 SVGObserverUtils::GetAndObserveClipPath(aFrame
, &clipPathFrame
);
822 return clipPathFrame
->PointIsInsideClipPath(aFrame
, aPoint
);
824 if (aFrame
->StyleSVGReset()->HasClipPath()) {
825 return nsCSSClipPathInstance::HitTestBasicShapeOrPathClip(aFrame
, aPoint
);
830 nsIFrame
* nsSVGUtils::HitTestChildren(nsSVGDisplayContainerFrame
* aFrame
,
831 const gfxPoint
& aPoint
) {
832 // First we transform aPoint into the coordinate space established by aFrame
833 // for its children (e.g. take account of any 'viewBox' attribute):
834 gfxPoint point
= aPoint
;
835 if (aFrame
->GetContent()->IsSVGElement()) { // must check before cast
837 static_cast<const SVGElement
*>(aFrame
->GetContent())
838 ->PrependLocalTransformsTo(gfxMatrix(), eChildToUserSpace
);
839 if (!m
.IsIdentity()) {
843 point
= m
.TransformPoint(point
);
847 // Traverse the list in reverse order, so that if we get a hit we know that's
848 // the topmost frame that intersects the point; then we can just return it.
849 nsIFrame
* result
= nullptr;
850 for (nsIFrame
* current
= aFrame
->PrincipalChildList().LastChild(); current
;
851 current
= current
->GetPrevSibling()) {
852 nsSVGDisplayableFrame
* SVGFrame
= do_QueryFrame(current
);
854 const nsIContent
* content
= current
->GetContent();
855 if (content
->IsSVGElement() &&
856 !static_cast<const SVGElement
*>(content
)->HasValidDimensions()) {
859 // GetFrameForPoint() expects a point in its frame's SVG user space, so
860 // we need to convert to that space:
862 if (content
->IsSVGElement()) { // must check before cast
864 static_cast<const SVGElement
*>(content
)->PrependLocalTransformsTo(
865 gfxMatrix(), eUserSpaceToParent
);
866 if (!m
.IsIdentity()) {
870 p
= m
.TransformPoint(p
);
873 result
= SVGFrame
->GetFrameForPoint(p
);
878 if (result
&& !HitTestClip(aFrame
, aPoint
)) result
= nullptr;
883 nsRect
nsSVGUtils::TransformFrameRectToOuterSVG(const nsRect
& aRect
,
884 const gfxMatrix
& aMatrix
,
885 nsPresContext
* aPresContext
) {
886 gfxRect
r(aRect
.x
, aRect
.y
, aRect
.width
, aRect
.height
);
887 r
.Scale(1.0 / AppUnitsPerCSSPixel());
888 return nsLayoutUtils::RoundGfxRectToAppRect(
889 aMatrix
.TransformBounds(r
), aPresContext
->AppUnitsPerDevPixel());
892 IntSize
nsSVGUtils::ConvertToSurfaceSize(const gfxSize
& aSize
,
893 bool* aResultOverflows
) {
894 IntSize
surfaceSize(ClampToInt(ceil(aSize
.width
)),
895 ClampToInt(ceil(aSize
.height
)));
897 *aResultOverflows
= surfaceSize
.width
!= ceil(aSize
.width
) ||
898 surfaceSize
.height
!= ceil(aSize
.height
);
900 if (!Factory::AllowedSurfaceSize(surfaceSize
)) {
902 std::min(NS_SVG_OFFSCREEN_MAX_DIMENSION
, surfaceSize
.width
);
904 std::min(NS_SVG_OFFSCREEN_MAX_DIMENSION
, surfaceSize
.height
);
905 *aResultOverflows
= true;
911 bool nsSVGUtils::HitTestRect(const gfx::Matrix
& aMatrix
, float aRX
, float aRY
,
912 float aRWidth
, float aRHeight
, float aX
,
914 gfx::Rect
rect(aRX
, aRY
, aRWidth
, aRHeight
);
915 if (rect
.IsEmpty() || aMatrix
.IsSingular()) {
918 gfx::Matrix toRectSpace
= aMatrix
;
919 toRectSpace
.Invert();
920 gfx::Point p
= toRectSpace
.TransformPoint(gfx::Point(aX
, aY
));
921 return rect
.x
<= p
.x
&& p
.x
<= rect
.XMost() && rect
.y
<= p
.y
&&
925 gfxRect
nsSVGUtils::GetClipRectForFrame(nsIFrame
* aFrame
, float aX
, float aY
,
926 float aWidth
, float aHeight
) {
927 const nsStyleDisplay
* disp
= aFrame
->StyleDisplay();
928 const nsStyleEffects
* effects
= aFrame
->StyleEffects();
931 disp
->mOverflowX
== StyleOverflow::Hidden
||
932 disp
->mOverflowY
== StyleOverflow::Hidden
;
934 if (!clipApplies
|| effects
->mClip
.IsAuto()) {
935 return gfxRect(aX
, aY
, aWidth
, aHeight
);
938 auto& rect
= effects
->mClip
.AsRect();
939 nsRect coordClipRect
= rect
.ToLayoutRect();
940 nsIntRect clipPxRect
= coordClipRect
.ToOutsidePixels(
941 aFrame
->PresContext()->AppUnitsPerDevPixel());
942 gfxRect clipRect
= gfxRect(clipPxRect
.x
, clipPxRect
.y
, clipPxRect
.width
,
944 if (rect
.right
.IsAuto()) {
945 clipRect
.width
= aWidth
- clipRect
.X();
947 if (rect
.bottom
.IsAuto()) {
948 clipRect
.height
= aHeight
- clipRect
.Y();
950 if (disp
->mOverflowX
!= StyleOverflow::Hidden
) {
952 clipRect
.width
= aWidth
;
954 if (disp
->mOverflowY
!= StyleOverflow::Hidden
) {
956 clipRect
.height
= aHeight
;
961 void nsSVGUtils::SetClipRect(gfxContext
* aContext
, const gfxMatrix
& aCTM
,
962 const gfxRect
& aRect
) {
963 if (aCTM
.IsSingular()) {
967 gfxContextMatrixAutoSaveRestore
matrixAutoSaveRestore(aContext
);
968 aContext
->Multiply(aCTM
);
969 aContext
->Clip(aRect
);
972 gfxRect
nsSVGUtils::GetBBox(nsIFrame
* aFrame
, uint32_t aFlags
,
973 const gfxMatrix
* aToBoundsSpace
) {
974 if (aFrame
->GetContent()->IsText()) {
975 aFrame
= aFrame
->GetParent();
978 if (nsSVGUtils::IsInSVGTextSubtree(aFrame
)) {
979 // It is possible to apply a gradient, pattern, clipping path, mask or
980 // filter to text. When one of these facilities is applied to text
981 // the bounding box is the entire text element in all
983 nsIFrame
* ancestor
= GetFirstNonAAncestorFrame(aFrame
);
984 if (ancestor
&& nsSVGUtils::IsInSVGTextSubtree(ancestor
)) {
985 while (!ancestor
->IsSVGTextFrame()) {
986 ancestor
= ancestor
->GetParent();
992 nsSVGDisplayableFrame
* svg
= do_QueryFrame(aFrame
);
993 const bool hasSVGLayout
= aFrame
->GetStateBits() & NS_FRAME_SVG_LAYOUT
;
994 if (hasSVGLayout
&& !svg
) {
995 // An SVG frame, but not one that can be displayed directly (for
996 // example, nsGradientFrame). These can't contribute to the bbox.
1000 const bool isOuterSVG
= svg
&& !hasSVGLayout
;
1001 MOZ_ASSERT(!isOuterSVG
|| aFrame
->IsSVGOuterSVGFrame());
1002 if (!svg
|| (isOuterSVG
&& (aFlags
& eUseFrameBoundsForOuterSVG
))) {
1003 // An HTML element or an SVG outer frame.
1004 MOZ_ASSERT(!hasSVGLayout
);
1005 bool onlyCurrentFrame
= aFlags
& eIncludeOnlyCurrentFrameForNonSVGElement
;
1006 return nsSVGIntegrationUtils::GetSVGBBoxForNonSVGFrame(
1008 /* aUnionContinuations = */ !onlyCurrentFrame
);
1013 nsIContent
* content
= aFrame
->GetContent();
1014 if (content
->IsSVGElement() &&
1015 !static_cast<const SVGElement
*>(content
)->HasValidDimensions()) {
1019 // Clean out flags which have no effects on returning bbox from now, so that
1020 // we can cache and reuse ObjectBoundingBoxProperty() in the code below.
1021 aFlags
&= ~eIncludeOnlyCurrentFrameForNonSVGElement
;
1022 aFlags
&= ~eUseFrameBoundsForOuterSVG
;
1023 if (!aFrame
->IsSVGUseFrame()) {
1024 aFlags
&= ~eUseUserSpaceOfUseElement
;
1027 if (aFlags
== eBBoxIncludeFillGeometry
&&
1028 // We only cache bbox in element's own user space
1030 gfxRect
* prop
= aFrame
->GetProperty(ObjectBoundingBoxProperty());
1037 if (aToBoundsSpace
) {
1038 matrix
= *aToBoundsSpace
;
1041 if (aFrame
->IsSVGForeignObjectFrame() ||
1042 aFlags
& nsSVGUtils::eUseUserSpaceOfUseElement
) {
1043 // The spec says getBBox "Returns the tight bounding box in *current user
1044 // space*". So we should really be doing this for all elements, but that
1045 // needs investigation to check that we won't break too much content.
1046 // NOTE: When changing this to apply to other frame types, make sure to
1047 // also update nsSVGUtils::FrameSpaceInCSSPxToUserSpaceOffset.
1048 MOZ_ASSERT(content
->IsSVGElement(), "bad cast");
1049 SVGElement
* element
= static_cast<SVGElement
*>(content
);
1050 matrix
= element
->PrependLocalTransformsTo(matrix
, eChildToUserSpace
);
1053 svg
->GetBBoxContribution(ToMatrix(matrix
), aFlags
).ToThebesRect();
1054 // Account for 'clipped'.
1055 if (aFlags
& nsSVGUtils::eBBoxIncludeClipped
) {
1056 gfxRect
clipRect(0, 0, 0, 0);
1057 float x
, y
, width
, height
;
1060 svg
->GetBBoxContribution(ToMatrix(tm
), nsSVGUtils::eBBoxIncludeFill
)
1064 width
= fillBBox
.width
;
1065 height
= fillBBox
.height
;
1066 bool hasClip
= aFrame
->StyleDisplay()->IsScrollableOverflow();
1068 clipRect
= nsSVGUtils::GetClipRectForFrame(aFrame
, x
, y
, width
, height
);
1069 if (aFrame
->IsSVGForeignObjectFrame() || aFrame
->IsSVGUseFrame()) {
1070 clipRect
= matrix
.TransformBounds(clipRect
);
1073 nsSVGClipPathFrame
* clipPathFrame
;
1074 if (SVGObserverUtils::GetAndObserveClipPath(aFrame
, &clipPathFrame
) ==
1075 SVGObserverUtils::eHasRefsSomeInvalid
) {
1076 bbox
= gfxRect(0, 0, 0, 0);
1078 if (clipPathFrame
) {
1079 SVGClipPathElement
* clipContent
=
1080 static_cast<SVGClipPathElement
*>(clipPathFrame
->GetContent());
1081 if (clipContent
->IsUnitsObjectBoundingBox()) {
1082 matrix
.PreTranslate(gfxPoint(x
, y
));
1083 matrix
.PreScale(width
, height
);
1084 } else if (aFrame
->IsSVGForeignObjectFrame()) {
1085 matrix
= gfxMatrix();
1089 nsSVGUtils::GetTransformMatrixInUserSpace(clipPathFrame
) * matrix
;
1091 bbox
= clipPathFrame
->GetBBoxForClipPathFrame(bbox
, matrix
, aFlags
)
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
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
));
1116 gfxPoint
nsSVGUtils::FrameSpaceInCSSPxToUserSpaceOffset(nsIFrame
* aFrame
) {
1117 if (!(aFrame
->GetStateBits() & 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.
1123 // Leaf frames apply their own offset inside their user space.
1124 if (aFrame
->IsFrameOfType(nsIFrame::eSVGGeometry
) ||
1125 nsSVGUtils::IsInSVGTextSubtree(aFrame
)) {
1126 return nsLayoutUtils::RectToGfxRect(aFrame
->GetRect(),
1127 AppUnitsPerCSSPixel())
1131 // For foreignObject frames, nsSVGUtils::GetBBox applies their local
1132 // transform, so we need to do the same here.
1133 if (aFrame
->IsSVGForeignObjectFrame()) {
1134 gfxMatrix transform
=
1135 static_cast<SVGElement
*>(aFrame
->GetContent())
1136 ->PrependLocalTransformsTo(gfxMatrix(), eChildToUserSpace
);
1137 NS_ASSERTION(!transform
.HasNonTranslation(),
1138 "we're relying on this being an offset-only transform");
1139 return transform
.GetTranslation();
1145 static gfxRect
GetBoundingBoxRelativeRect(const SVGAnimatedLength
* aXYWH
,
1146 const gfxRect
& aBBox
) {
1147 return gfxRect(aBBox
.x
+ nsSVGUtils::ObjectSpace(aBBox
, &aXYWH
[0]),
1148 aBBox
.y
+ nsSVGUtils::ObjectSpace(aBBox
, &aXYWH
[1]),
1149 nsSVGUtils::ObjectSpace(aBBox
, &aXYWH
[2]),
1150 nsSVGUtils::ObjectSpace(aBBox
, &aXYWH
[3]));
1153 gfxRect
nsSVGUtils::GetRelativeRect(uint16_t aUnits
,
1154 const SVGAnimatedLength
* aXYWH
,
1155 const gfxRect
& aBBox
,
1156 const UserSpaceMetrics
& aMetrics
) {
1157 if (aUnits
== SVG_UNIT_TYPE_OBJECTBOUNDINGBOX
) {
1158 return GetBoundingBoxRelativeRect(aXYWH
, aBBox
);
1160 return gfxRect(UserSpace(aMetrics
, &aXYWH
[0]), UserSpace(aMetrics
, &aXYWH
[1]),
1161 UserSpace(aMetrics
, &aXYWH
[2]),
1162 UserSpace(aMetrics
, &aXYWH
[3]));
1165 gfxRect
nsSVGUtils::GetRelativeRect(uint16_t aUnits
,
1166 const SVGAnimatedLength
* aXYWH
,
1167 const gfxRect
& aBBox
, nsIFrame
* aFrame
) {
1168 if (aUnits
== SVG_UNIT_TYPE_OBJECTBOUNDINGBOX
) {
1169 return GetBoundingBoxRelativeRect(aXYWH
, aBBox
);
1171 nsIContent
* content
= aFrame
->GetContent();
1172 if (content
->IsSVGElement()) {
1173 SVGElement
* svgElement
= static_cast<SVGElement
*>(content
);
1174 return GetRelativeRect(aUnits
, aXYWH
, aBBox
, SVGElementMetrics(svgElement
));
1176 return GetRelativeRect(aUnits
, aXYWH
, aBBox
,
1177 NonSVGFrameUserSpaceMetrics(aFrame
));
1180 bool nsSVGUtils::CanOptimizeOpacity(nsIFrame
* aFrame
) {
1181 if (!(aFrame
->GetStateBits() & NS_FRAME_SVG_LAYOUT
)) {
1184 LayoutFrameType type
= aFrame
->Type();
1185 if (type
!= LayoutFrameType::SVGImage
&&
1186 type
!= LayoutFrameType::SVGGeometry
) {
1189 if (aFrame
->StyleEffects()->HasFilters()) {
1192 // XXX The SVG WG is intending to allow fill, stroke and markers on <image>
1193 if (type
== LayoutFrameType::SVGImage
) {
1196 const nsStyleSVG
* style
= aFrame
->StyleSVG();
1197 if (style
->HasMarker()) {
1201 if (nsLayoutUtils::HasAnimationOfPropertySet(
1202 aFrame
, nsCSSPropertyIDSet::OpacityProperties())) {
1206 return !style
->HasFill() || !HasStroke(aFrame
);
1209 gfxMatrix
nsSVGUtils::AdjustMatrixForUnits(const gfxMatrix
& aMatrix
,
1210 SVGAnimatedEnumeration
* aUnits
,
1211 nsIFrame
* aFrame
, uint32_t aFlags
) {
1212 if (aFrame
&& aUnits
->GetAnimValue() == SVG_UNIT_TYPE_OBJECTBOUNDINGBOX
) {
1213 gfxRect bbox
= GetBBox(aFrame
, aFlags
);
1214 gfxMatrix tm
= aMatrix
;
1215 tm
.PreTranslate(gfxPoint(bbox
.X(), bbox
.Y()));
1216 tm
.PreScale(bbox
.Width(), bbox
.Height());
1222 nsIFrame
* nsSVGUtils::GetFirstNonAAncestorFrame(nsIFrame
* aStartFrame
) {
1223 for (nsIFrame
* ancestorFrame
= aStartFrame
; ancestorFrame
;
1224 ancestorFrame
= ancestorFrame
->GetParent()) {
1225 if (!ancestorFrame
->IsSVGAFrame()) {
1226 return ancestorFrame
;
1232 bool nsSVGUtils::GetNonScalingStrokeTransform(nsIFrame
* aFrame
,
1233 gfxMatrix
* aUserToOuterSVG
) {
1234 if (aFrame
->GetContent()->IsText()) {
1235 aFrame
= aFrame
->GetParent();
1238 if (!aFrame
->StyleSVGReset()->HasNonScalingStroke()) {
1242 MOZ_ASSERT(aFrame
->GetContent()->IsSVGElement(), "should be an SVG element");
1244 *aUserToOuterSVG
= ThebesMatrix(SVGContentUtils::GetCTM(
1245 static_cast<SVGElement
*>(aFrame
->GetContent()), true));
1247 return aUserToOuterSVG
->HasNonTranslation();
1250 // The logic here comes from _cairo_stroke_style_max_distance_from_path
1251 static gfxRect
PathExtentsToMaxStrokeExtents(const gfxRect
& aPathExtents
,
1253 double aStyleExpansionFactor
,
1254 const gfxMatrix
& aMatrix
) {
1255 double style_expansion
=
1256 aStyleExpansionFactor
* nsSVGUtils::GetStrokeWidth(aFrame
);
1258 gfxMatrix matrix
= aMatrix
;
1260 gfxMatrix outerSVGToUser
;
1261 if (nsSVGUtils::GetNonScalingStrokeTransform(aFrame
, &outerSVGToUser
)) {
1262 outerSVGToUser
.Invert();
1263 matrix
.PreMultiply(outerSVGToUser
);
1266 double dx
= style_expansion
* (fabs(matrix
._11
) + fabs(matrix
._21
));
1267 double dy
= style_expansion
* (fabs(matrix
._22
) + fabs(matrix
._12
));
1269 gfxRect strokeExtents
= aPathExtents
;
1270 strokeExtents
.Inflate(dx
, dy
);
1271 return strokeExtents
;
1275 gfxRect
nsSVGUtils::PathExtentsToMaxStrokeExtents(const gfxRect
& aPathExtents
,
1276 nsTextFrame
* aFrame
,
1277 const gfxMatrix
& aMatrix
) {
1278 NS_ASSERTION(nsSVGUtils::IsInSVGTextSubtree(aFrame
),
1279 "expected an nsTextFrame for SVG text");
1280 return ::PathExtentsToMaxStrokeExtents(aPathExtents
, aFrame
, 0.5, aMatrix
);
1284 gfxRect
nsSVGUtils::PathExtentsToMaxStrokeExtents(const gfxRect
& aPathExtents
,
1285 SVGGeometryFrame
* aFrame
,
1286 const gfxMatrix
& aMatrix
) {
1287 bool strokeMayHaveCorners
=
1288 !SVGContentUtils::ShapeTypeHasNoCorners(aFrame
->GetContent());
1290 // For a shape without corners the stroke can only extend half the stroke
1291 // width from the path in the x/y-axis directions. For shapes with corners
1292 // the stroke can extend by sqrt(1/2) (think 45 degree rotated rect, or line
1293 // with stroke-linecaps="square").
1294 double styleExpansionFactor
= strokeMayHaveCorners
? M_SQRT1_2
: 0.5;
1296 // The stroke can extend even further for paths that can be affected by
1297 // stroke-miterlimit.
1298 // We only need to do this if the limit is greater than 1, but it's probably
1299 // not worth optimizing for that.
1300 bool affectedByMiterlimit
= aFrame
->GetContent()->IsAnyOfSVGElements(
1301 nsGkAtoms::path
, nsGkAtoms::polyline
, nsGkAtoms::polygon
);
1303 if (affectedByMiterlimit
) {
1304 const nsStyleSVG
* style
= aFrame
->StyleSVG();
1305 if (style
->mStrokeLinejoin
== NS_STYLE_STROKE_LINEJOIN_MITER
&&
1306 styleExpansionFactor
< style
->mStrokeMiterlimit
/ 2.0) {
1307 styleExpansionFactor
= style
->mStrokeMiterlimit
/ 2.0;
1311 return ::PathExtentsToMaxStrokeExtents(aPathExtents
, aFrame
,
1312 styleExpansionFactor
, aMatrix
);
1315 // ----------------------------------------------------------------------
1318 nscolor
nsSVGUtils::GetFallbackOrPaintColor(
1319 const ComputedStyle
& aStyle
, StyleSVGPaint
nsStyleSVG::*aFillOrStroke
) {
1320 const auto& paint
= aStyle
.StyleSVG()->*aFillOrStroke
;
1322 switch (paint
.kind
.tag
) {
1323 case StyleSVGPaintKind::Tag::PaintServer
:
1324 case StyleSVGPaintKind::Tag::ContextStroke
:
1325 color
= paint
.fallback
.IsColor()
1326 ? paint
.fallback
.AsColor().CalcColor(aStyle
)
1327 : NS_RGBA(0, 0, 0, 0);
1329 case StyleSVGPaintKind::Tag::ContextFill
:
1330 color
= paint
.fallback
.IsColor()
1331 ? paint
.fallback
.AsColor().CalcColor(aStyle
)
1335 color
= paint
.kind
.AsColor().CalcColor(aStyle
);
1338 if (const auto* styleIfVisited
= aStyle
.GetStyleIfVisited()) {
1339 const auto& paintIfVisited
= styleIfVisited
->StyleSVG()->*aFillOrStroke
;
1340 // To prevent Web content from detecting if a user has visited a URL
1341 // (via URL loading triggered by paint servers or performance
1342 // differences between paint servers or between a paint server and a
1343 // color), we do not allow whether links are visited to change which
1344 // paint server is used or switch between paint servers and simple
1345 // colors. A :visited style may only override a simple color with
1346 // another simple color.
1347 if (paintIfVisited
.kind
.IsColor() && paint
.kind
.IsColor()) {
1348 nscolor colors
[2] = {
1349 color
, paintIfVisited
.kind
.AsColor().CalcColor(*styleIfVisited
)};
1350 return ComputedStyle::CombineVisitedColors(colors
,
1351 aStyle
.RelevantLinkVisited());
1358 void nsSVGUtils::MakeFillPatternFor(nsIFrame
* aFrame
, gfxContext
* aContext
,
1359 GeneralPattern
* aOutPattern
,
1360 imgDrawingParams
& aImgParams
,
1361 SVGContextPaint
* aContextPaint
) {
1362 const nsStyleSVG
* style
= aFrame
->StyleSVG();
1363 if (style
->mFill
.kind
.IsNone()) {
1367 const float opacity
= aFrame
->StyleEffects()->mOpacity
;
1369 float fillOpacity
= GetOpacity(style
->FillOpacitySource(),
1370 style
->mFillOpacity
, aContextPaint
);
1371 if (opacity
< 1.0f
&& nsSVGUtils::CanOptimizeOpacity(aFrame
)) {
1372 // Combine the group opacity into the fill opacity (we will have skipped
1373 // creating an offscreen surface to apply the group opacity).
1374 fillOpacity
*= opacity
;
1377 const DrawTarget
* dt
= aContext
->GetDrawTarget();
1379 nsSVGPaintServerFrame
* ps
=
1380 SVGObserverUtils::GetAndObservePaintServer(aFrame
, &nsStyleSVG::mFill
);
1383 RefPtr
<gfxPattern
> pattern
=
1384 ps
->GetPaintServerPattern(aFrame
, dt
, aContext
->CurrentMatrixDouble(),
1385 &nsStyleSVG::mFill
, fillOpacity
, aImgParams
);
1387 pattern
->CacheColorStops(dt
);
1388 aOutPattern
->Init(*pattern
->GetPattern(dt
));
1393 if (aContextPaint
) {
1394 RefPtr
<gfxPattern
> pattern
;
1395 switch (style
->mFill
.kind
.tag
) {
1396 case StyleSVGPaintKind::Tag::ContextFill
:
1397 pattern
= aContextPaint
->GetFillPattern(
1398 dt
, fillOpacity
, aContext
->CurrentMatrixDouble(), aImgParams
);
1400 case StyleSVGPaintKind::Tag::ContextStroke
:
1401 pattern
= aContextPaint
->GetStrokePattern(
1402 dt
, fillOpacity
, aContext
->CurrentMatrixDouble(), aImgParams
);
1407 aOutPattern
->Init(*pattern
->GetPattern(dt
));
1412 if (style
->mFill
.fallback
.IsNone()) {
1416 // On failure, use the fallback colour in case we have an
1417 // objectBoundingBox where the width or height of the object is zero.
1418 // See http://www.w3.org/TR/SVG11/coords.html#ObjectBoundingBox
1419 Color
color(Color::FromABGR(
1420 GetFallbackOrPaintColor(*aFrame
->Style(), &nsStyleSVG::mFill
)));
1421 color
.a
*= fillOpacity
;
1422 aOutPattern
->InitColorPattern(ToDeviceColor(color
));
1426 void nsSVGUtils::MakeStrokePatternFor(nsIFrame
* aFrame
, gfxContext
* aContext
,
1427 GeneralPattern
* aOutPattern
,
1428 imgDrawingParams
& aImgParams
,
1429 SVGContextPaint
* aContextPaint
) {
1430 const nsStyleSVG
* style
= aFrame
->StyleSVG();
1431 if (style
->mStroke
.kind
.IsNone()) {
1435 const float opacity
= aFrame
->StyleEffects()->mOpacity
;
1437 float strokeOpacity
= GetOpacity(style
->StrokeOpacitySource(),
1438 style
->mStrokeOpacity
, aContextPaint
);
1439 if (opacity
< 1.0f
&& nsSVGUtils::CanOptimizeOpacity(aFrame
)) {
1440 // Combine the group opacity into the stroke opacity (we will have skipped
1441 // creating an offscreen surface to apply the group opacity).
1442 strokeOpacity
*= opacity
;
1445 const DrawTarget
* dt
= aContext
->GetDrawTarget();
1447 nsSVGPaintServerFrame
* ps
=
1448 SVGObserverUtils::GetAndObservePaintServer(aFrame
, &nsStyleSVG::mStroke
);
1451 RefPtr
<gfxPattern
> pattern
= ps
->GetPaintServerPattern(
1452 aFrame
, dt
, aContext
->CurrentMatrixDouble(), &nsStyleSVG::mStroke
,
1453 strokeOpacity
, aImgParams
);
1455 pattern
->CacheColorStops(dt
);
1456 aOutPattern
->Init(*pattern
->GetPattern(dt
));
1461 if (aContextPaint
) {
1462 RefPtr
<gfxPattern
> pattern
;
1463 switch (style
->mStroke
.kind
.tag
) {
1464 case StyleSVGPaintKind::Tag::ContextFill
:
1465 pattern
= aContextPaint
->GetFillPattern(
1466 dt
, strokeOpacity
, aContext
->CurrentMatrixDouble(), aImgParams
);
1468 case StyleSVGPaintKind::Tag::ContextStroke
:
1469 pattern
= aContextPaint
->GetStrokePattern(
1470 dt
, strokeOpacity
, aContext
->CurrentMatrixDouble(), aImgParams
);
1475 aOutPattern
->Init(*pattern
->GetPattern(dt
));
1480 if (style
->mStroke
.fallback
.IsNone()) {
1484 // On failure, use the fallback colour in case we have an
1485 // objectBoundingBox where the width or height of the object is zero.
1486 // See http://www.w3.org/TR/SVG11/coords.html#ObjectBoundingBox
1487 Color
color(Color::FromABGR(
1488 GetFallbackOrPaintColor(*aFrame
->Style(), &nsStyleSVG::mStroke
)));
1489 color
.a
*= strokeOpacity
;
1490 aOutPattern
->InitColorPattern(ToDeviceColor(color
));
1494 float nsSVGUtils::GetOpacity(nsStyleSVGOpacitySource aOpacityType
,
1495 const float& aOpacity
,
1496 SVGContextPaint
* aContextPaint
) {
1497 float opacity
= 1.0f
;
1498 switch (aOpacityType
) {
1499 case eStyleSVGOpacitySource_Normal
:
1502 case eStyleSVGOpacitySource_ContextFillOpacity
:
1503 if (aContextPaint
) {
1504 opacity
= aContextPaint
->GetFillOpacity();
1507 case eStyleSVGOpacitySource_ContextStrokeOpacity
:
1508 if (aContextPaint
) {
1509 opacity
= aContextPaint
->GetStrokeOpacity();
1513 MOZ_ASSERT_UNREACHABLE(
1514 "Unknown object opacity inheritance type for SVG "
1520 bool nsSVGUtils::HasStroke(nsIFrame
* aFrame
, SVGContextPaint
* aContextPaint
) {
1521 const nsStyleSVG
* style
= aFrame
->StyleSVG();
1522 return style
->HasStroke() && GetStrokeWidth(aFrame
, aContextPaint
) > 0;
1525 float nsSVGUtils::GetStrokeWidth(nsIFrame
* aFrame
,
1526 SVGContextPaint
* aContextPaint
) {
1527 const nsStyleSVG
* style
= aFrame
->StyleSVG();
1528 if (aContextPaint
&& style
->StrokeWidthFromObject()) {
1529 return aContextPaint
->GetStrokeWidth();
1532 nsIContent
* content
= aFrame
->GetContent();
1533 if (content
->IsText()) {
1534 content
= content
->GetParent();
1537 SVGElement
* ctx
= static_cast<SVGElement
*>(content
);
1538 return SVGContentUtils::CoordToFloat(ctx
, style
->mStrokeWidth
);
1541 void nsSVGUtils::SetupStrokeGeometry(nsIFrame
* aFrame
, gfxContext
* aContext
,
1542 SVGContextPaint
* aContextPaint
) {
1543 SVGContentUtils::AutoStrokeOptions strokeOptions
;
1544 SVGContentUtils::GetStrokeOptions(
1545 &strokeOptions
, static_cast<SVGElement
*>(aFrame
->GetContent()),
1546 aFrame
->Style(), aContextPaint
);
1548 if (strokeOptions
.mLineWidth
<= 0) {
1552 aContext
->SetLineWidth(strokeOptions
.mLineWidth
);
1553 aContext
->SetLineCap(strokeOptions
.mLineCap
);
1554 aContext
->SetMiterLimit(strokeOptions
.mMiterLimit
);
1555 aContext
->SetLineJoin(strokeOptions
.mLineJoin
);
1556 aContext
->SetDash(strokeOptions
.mDashPattern
, strokeOptions
.mDashLength
,
1557 strokeOptions
.mDashOffset
);
1560 uint16_t nsSVGUtils::GetGeometryHitTestFlags(nsIFrame
* aFrame
) {
1563 switch (aFrame
->StyleUI()->mPointerEvents
) {
1564 case NS_STYLE_POINTER_EVENTS_NONE
:
1566 case NS_STYLE_POINTER_EVENTS_AUTO
:
1567 case NS_STYLE_POINTER_EVENTS_VISIBLEPAINTED
:
1568 if (aFrame
->StyleVisibility()->IsVisible()) {
1569 if (!aFrame
->StyleSVG()->mFill
.kind
.IsNone())
1570 flags
|= SVG_HIT_TEST_FILL
;
1571 if (!aFrame
->StyleSVG()->mStroke
.kind
.IsNone())
1572 flags
|= SVG_HIT_TEST_STROKE
;
1573 if (aFrame
->StyleSVG()->mStrokeOpacity
> 0)
1574 flags
|= SVG_HIT_TEST_CHECK_MRECT
;
1577 case NS_STYLE_POINTER_EVENTS_VISIBLEFILL
:
1578 if (aFrame
->StyleVisibility()->IsVisible()) {
1579 flags
|= SVG_HIT_TEST_FILL
;
1582 case NS_STYLE_POINTER_EVENTS_VISIBLESTROKE
:
1583 if (aFrame
->StyleVisibility()->IsVisible()) {
1584 flags
|= SVG_HIT_TEST_STROKE
;
1587 case NS_STYLE_POINTER_EVENTS_VISIBLE
:
1588 if (aFrame
->StyleVisibility()->IsVisible()) {
1589 flags
|= SVG_HIT_TEST_FILL
| SVG_HIT_TEST_STROKE
;
1592 case NS_STYLE_POINTER_EVENTS_PAINTED
:
1593 if (!aFrame
->StyleSVG()->mFill
.kind
.IsNone()) flags
|= SVG_HIT_TEST_FILL
;
1594 if (!aFrame
->StyleSVG()->mStroke
.kind
.IsNone())
1595 flags
|= SVG_HIT_TEST_STROKE
;
1596 if (aFrame
->StyleSVG()->mStrokeOpacity
) flags
|= SVG_HIT_TEST_CHECK_MRECT
;
1598 case NS_STYLE_POINTER_EVENTS_FILL
:
1599 flags
|= SVG_HIT_TEST_FILL
;
1601 case NS_STYLE_POINTER_EVENTS_STROKE
:
1602 flags
|= SVG_HIT_TEST_STROKE
;
1604 case NS_STYLE_POINTER_EVENTS_ALL
:
1605 flags
|= SVG_HIT_TEST_FILL
| SVG_HIT_TEST_STROKE
;
1608 NS_ERROR("not reached");
1615 void nsSVGUtils::PaintSVGGlyph(Element
* aElement
, gfxContext
* aContext
) {
1616 nsIFrame
* frame
= aElement
->GetPrimaryFrame();
1617 nsSVGDisplayableFrame
* svgFrame
= do_QueryFrame(frame
);
1622 if (frame
->GetContent()->IsSVGElement()) {
1623 // PaintSVG() expects the passed transform to be the transform to its own
1624 // SVG user space, so we need to account for any 'transform' attribute:
1625 m
= nsSVGUtils::GetTransformMatrixInUserSpace(frame
);
1628 // SVG-in-OpenType is not allowed to paint external resources, so we can
1629 // just pass a dummy params into PatintSVG.
1630 imgDrawingParams dummy
;
1631 svgFrame
->PaintSVG(*aContext
, m
, dummy
);
1634 bool nsSVGUtils::GetSVGGlyphExtents(Element
* aElement
,
1635 const gfxMatrix
& aSVGToAppSpace
,
1637 nsIFrame
* frame
= aElement
->GetPrimaryFrame();
1638 nsSVGDisplayableFrame
* svgFrame
= do_QueryFrame(frame
);
1643 gfxMatrix
transform(aSVGToAppSpace
);
1644 nsIContent
* content
= frame
->GetContent();
1645 if (content
->IsSVGElement()) {
1646 transform
= static_cast<SVGElement
*>(content
)->PrependLocalTransformsTo(
1652 ->GetBBoxContribution(gfx::ToMatrix(transform
),
1653 nsSVGUtils::eBBoxIncludeFill
|
1654 nsSVGUtils::eBBoxIncludeFillGeometry
|
1655 nsSVGUtils::eBBoxIncludeStroke
|
1656 nsSVGUtils::eBBoxIncludeStrokeGeometry
|
1657 nsSVGUtils::eBBoxIncludeMarkers
)
1662 nsRect
nsSVGUtils::ToCanvasBounds(const gfxRect
& aUserspaceRect
,
1663 const gfxMatrix
& aToCanvas
,
1664 const nsPresContext
* presContext
) {
1665 return nsLayoutUtils::RoundGfxRectToAppRect(
1666 aToCanvas
.TransformBounds(aUserspaceRect
),
1667 presContext
->AppUnitsPerDevPixel());
1670 gfxMatrix
nsSVGUtils::GetCSSPxToDevPxMatrix(nsIFrame
* aNonSVGFrame
) {
1671 int32_t appUnitsPerDevPixel
=
1672 aNonSVGFrame
->PresContext()->AppUnitsPerDevPixel();
1673 float devPxPerCSSPx
=
1674 1 / nsPresContext::AppUnitsToFloatCSSPixels(appUnitsPerDevPixel
);
1676 return gfxMatrix(devPxPerCSSPx
, 0.0, 0.0, devPxPerCSSPx
, 0.0, 0.0);
1679 gfxMatrix
nsSVGUtils::GetTransformMatrixInUserSpace(const nsIFrame
* aFrame
) {
1680 // We check element instead of aFrame directly because SVG element
1681 // may have non-SVG frame, <tspan> for example.
1682 MOZ_ASSERT(aFrame
->GetContent() && aFrame
->GetContent()->IsSVGElement(),
1683 "Only use this wrapper for SVG elements");
1685 if (!aFrame
->IsTransformed()) {
1689 nsDisplayTransform::FrameTransformProperties properties
{
1690 aFrame
, AppUnitsPerCSSPixel(), nullptr};
1691 nsStyleTransformMatrix::TransformReferenceBox refBox
;
1692 refBox
.Init(aFrame
);
1694 // SVG elements can have x/y offset, their default transform origin
1695 // is the origin of user space, not the top left point of the frame.
1696 Point3D svgTransformOrigin
{
1697 properties
.mToTransformOrigin
.x
- CSSPixel::FromAppUnits(refBox
.X()),
1698 properties
.mToTransformOrigin
.y
- CSSPixel::FromAppUnits(refBox
.Y()),
1699 properties
.mToTransformOrigin
.z
};
1701 Matrix svgTransform
;
1703 (void)aFrame
->IsSVGTransformed(&svgTransform
);
1705 if (properties
.HasTransform()) {
1706 trans
= nsStyleTransformMatrix::ReadTransforms(
1707 properties
.mTranslate
, properties
.mRotate
, properties
.mScale
,
1708 properties
.mMotion
, properties
.mTransform
, refBox
,
1709 AppUnitsPerCSSPixel());
1711 trans
= Matrix4x4::From2D(svgTransform
);
1714 trans
.ChangeBasis(svgTransformOrigin
);
1717 trans
.ProjectTo2D();
1718 (void)trans
.CanDraw2D(&mm
);
1720 return ThebesMatrix(mm
);