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
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 "nsCSSFrameConstructor.h"
20 #include "nsDisplayList.h"
21 #include "nsFrameList.h"
22 #include "nsGkAtoms.h"
23 #include "nsIContent.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/PresShell.h"
38 #include "mozilla/StaticPrefs_svg.h"
39 #include "mozilla/SVGClipPathFrame.h"
40 #include "mozilla/SVGContainerFrame.h"
41 #include "mozilla/SVGContentUtils.h"
42 #include "mozilla/SVGContextPaint.h"
43 #include "mozilla/SVGForeignObjectFrame.h"
44 #include "mozilla/SVGIntegrationUtils.h"
45 #include "mozilla/SVGGeometryFrame.h"
46 #include "mozilla/SVGMaskFrame.h"
47 #include "mozilla/SVGObserverUtils.h"
48 #include "mozilla/SVGOuterSVGFrame.h"
49 #include "mozilla/SVGTextFrame.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::dom
;
61 using namespace mozilla::dom::SVGUnitTypes_Binding
;
62 using namespace mozilla::gfx
;
63 using namespace mozilla::image
;
65 bool NS_SVGNewGetBBoxEnabled() {
66 return mozilla::StaticPrefs::svg_new_getBBox_enabled();
71 // we only take the address of this:
72 static gfx::UserDataKey sSVGAutoRenderStateKey
;
74 SVGAutoRenderState::SVGAutoRenderState(DrawTarget
* aDrawTarget
)
75 : mDrawTarget(aDrawTarget
),
76 mOriginalRenderState(nullptr),
77 mPaintingToWindow(false) {
78 mOriginalRenderState
= aDrawTarget
->RemoveUserData(&sSVGAutoRenderStateKey
);
79 // We always remove ourselves from aContext before it dies, so
80 // passing nullptr as the destroy function is okay.
81 aDrawTarget
->AddUserData(&sSVGAutoRenderStateKey
, this, nullptr);
84 SVGAutoRenderState::~SVGAutoRenderState() {
85 mDrawTarget
->RemoveUserData(&sSVGAutoRenderStateKey
);
86 if (mOriginalRenderState
) {
87 mDrawTarget
->AddUserData(&sSVGAutoRenderStateKey
, mOriginalRenderState
,
92 void SVGAutoRenderState::SetPaintingToWindow(bool aPaintingToWindow
) {
93 mPaintingToWindow
= aPaintingToWindow
;
97 bool SVGAutoRenderState::IsPaintingToWindow(DrawTarget
* aDrawTarget
) {
98 void* state
= aDrawTarget
->GetUserData(&sSVGAutoRenderStateKey
);
100 return static_cast<SVGAutoRenderState
*>(state
)->mPaintingToWindow
;
105 // Unlike containers, leaf frames do not include GetPosition() in
107 static bool FrameDoesNotIncludePositionInTM(const nsIFrame
* aFrame
) {
108 return aFrame
->IsSVGGeometryFrame() || aFrame
->IsSVGImageFrame() ||
109 aFrame
->IsInSVGTextSubtree();
112 nsRect
SVGUtils::GetPostFilterInkOverflowRect(nsIFrame
* aFrame
,
113 const nsRect
& aPreFilterRect
) {
114 MOZ_ASSERT(aFrame
->HasAnyStateBits(NS_FRAME_SVG_LAYOUT
),
115 "Called on invalid frame type");
117 // Note: we do not return here for eHasNoRefs since we must still handle any
118 // CSS filter functions.
119 // in that case we disable painting of the element.
120 nsTArray
<SVGFilterFrame
*> filterFrames
;
121 if (!aFrame
->StyleEffects()->HasFilters() ||
122 SVGObserverUtils::GetAndObserveFilters(aFrame
, &filterFrames
) ==
123 SVGObserverUtils::eHasRefsSomeInvalid
) {
124 return aPreFilterRect
;
127 return FilterInstance::GetPostFilterBounds(aFrame
, filterFrames
, nullptr,
129 .valueOr(aPreFilterRect
);
132 bool SVGUtils::OuterSVGIsCallingReflowSVG(nsIFrame
* aFrame
) {
133 return GetOuterSVGFrame(aFrame
)->IsCallingReflowSVG();
136 bool SVGUtils::AnyOuterSVGIsCallingReflowSVG(nsIFrame
* aFrame
) {
137 SVGOuterSVGFrame
* outer
= GetOuterSVGFrame(aFrame
);
139 if (outer
->IsCallingReflowSVG()) {
142 outer
= GetOuterSVGFrame(outer
->GetParent());
147 void SVGUtils::ScheduleReflowSVG(nsIFrame
* aFrame
) {
148 MOZ_ASSERT(aFrame
->IsSVGFrame(), "Passed bad frame!");
150 // If this is triggered, the callers should be fixed to call us before
151 // ReflowSVG is called. If we try to mark dirty bits on frames while we're
152 // in the process of removing them, things will get messed up.
153 MOZ_ASSERT(!OuterSVGIsCallingReflowSVG(aFrame
),
154 "Do not call under ISVGDisplayableFrame::ReflowSVG!");
156 // We don't call SVGObserverUtils::InvalidateRenderingObservers here because
157 // we should only be called under InvalidateAndScheduleReflowSVG (which
158 // calls InvalidateBounds) or SVGDisplayContainerFrame::InsertFrames
159 // (at which point the frame has no observers).
161 if (aFrame
->HasAnyStateBits(NS_FRAME_IS_NONDISPLAY
)) {
165 if (aFrame
->HasAnyStateBits(NS_FRAME_IS_DIRTY
| NS_FRAME_FIRST_REFLOW
)) {
166 // Nothing to do if we're already dirty, or if the outer-<svg>
167 // hasn't yet had its initial reflow.
171 SVGOuterSVGFrame
* outerSVGFrame
= nullptr;
173 // We must not add dirty bits to the SVGOuterSVGFrame or else
174 // PresShell::FrameNeedsReflow won't work when we pass it in below.
175 if (aFrame
->IsSVGOuterSVGFrame()) {
176 outerSVGFrame
= static_cast<SVGOuterSVGFrame
*>(aFrame
);
178 aFrame
->MarkSubtreeDirty();
180 nsIFrame
* f
= aFrame
->GetParent();
181 while (f
&& !f
->IsSVGOuterSVGFrame()) {
182 if (f
->HasAnyStateBits(NS_FRAME_IS_DIRTY
| NS_FRAME_HAS_DIRTY_CHILDREN
)) {
185 f
->AddStateBits(NS_FRAME_HAS_DIRTY_CHILDREN
);
187 MOZ_ASSERT(f
->IsSVGFrame(), "IsSVGOuterSVGFrame check above not valid!");
190 outerSVGFrame
= static_cast<SVGOuterSVGFrame
*>(f
);
192 MOZ_ASSERT(outerSVGFrame
&& outerSVGFrame
->IsSVGOuterSVGFrame(),
193 "Did not find SVGOuterSVGFrame!");
196 if (outerSVGFrame
->HasAnyStateBits(NS_FRAME_IN_REFLOW
)) {
197 // We're currently under an SVGOuterSVGFrame::Reflow call so there is no
198 // need to call PresShell::FrameNeedsReflow, since we have an
199 // SVGOuterSVGFrame::DidReflow call pending.
203 nsFrameState dirtyBit
=
204 (outerSVGFrame
== aFrame
? NS_FRAME_IS_DIRTY
205 : NS_FRAME_HAS_DIRTY_CHILDREN
);
207 aFrame
->PresShell()->FrameNeedsReflow(outerSVGFrame
, IntrinsicDirty::None
,
211 bool SVGUtils::NeedsReflowSVG(const nsIFrame
* aFrame
) {
212 MOZ_ASSERT(aFrame
->IsSVGFrame(), "SVG uses bits differently!");
214 // The flags we test here may change, hence why we have this separate
216 return aFrame
->IsSubtreeDirty();
219 Size
SVGUtils::GetContextSize(const nsIFrame
* aFrame
) {
222 MOZ_ASSERT(aFrame
->GetContent()->IsSVGElement(), "bad cast");
223 const SVGElement
* element
= static_cast<SVGElement
*>(aFrame
->GetContent());
225 SVGViewportElement
* ctx
= element
->GetCtx();
227 size
.width
= ctx
->GetLength(SVGContentUtils::X
);
228 size
.height
= ctx
->GetLength(SVGContentUtils::Y
);
233 float SVGUtils::ObjectSpace(const gfxRect
& aRect
,
234 const SVGAnimatedLength
* aLength
) {
237 switch (aLength
->GetCtxType()) {
238 case SVGContentUtils::X
:
239 axis
= aRect
.Width();
241 case SVGContentUtils::Y
:
242 axis
= aRect
.Height();
244 case SVGContentUtils::XY
:
245 axis
= float(SVGContentUtils::ComputeNormalizedHypotenuse(
246 aRect
.Width(), aRect
.Height()));
249 MOZ_ASSERT_UNREACHABLE("unexpected ctx type");
253 if (aLength
->IsPercentage()) {
254 // Multiply first to avoid precision errors:
255 return axis
* aLength
->GetAnimValInSpecifiedUnits() / 100;
257 return aLength
->GetAnimValueWithZoom(
258 static_cast<SVGViewportElement
*>(nullptr)) *
262 float SVGUtils::UserSpace(nsIFrame
* aNonSVGContext
,
263 const SVGAnimatedLength
* aLength
) {
264 MOZ_ASSERT(!aNonSVGContext
->IsTextFrame(), "Not expecting text content");
265 return aLength
->GetAnimValueWithZoom(aNonSVGContext
);
268 float SVGUtils::UserSpace(const UserSpaceMetrics
& aMetrics
,
269 const SVGAnimatedLength
* aLength
) {
270 return aLength
->GetAnimValueWithZoom(aMetrics
);
273 SVGOuterSVGFrame
* SVGUtils::GetOuterSVGFrame(nsIFrame
* aFrame
) {
274 return static_cast<SVGOuterSVGFrame
*>(nsLayoutUtils::GetClosestFrameOfType(
275 aFrame
, LayoutFrameType::SVGOuterSVG
));
278 nsIFrame
* SVGUtils::GetOuterSVGFrameAndCoveredRegion(nsIFrame
* aFrame
,
280 ISVGDisplayableFrame
* svg
= do_QueryFrame(aFrame
);
284 SVGOuterSVGFrame
* outer
= GetOuterSVGFrame(aFrame
);
289 if (aFrame
->HasAnyStateBits(NS_FRAME_IS_NONDISPLAY
)) {
294 auto ctm
= nsLayoutUtils::GetTransformToAncestor(RelativeTo
{aFrame
},
300 gfxMatrix m
= ThebesMatrix(mm
);
302 float appUnitsPerDevPixel
= aFrame
->PresContext()->AppUnitsPerDevPixel();
303 float devPixelPerCSSPixel
=
304 float(AppUnitsPerCSSPixel()) / appUnitsPerDevPixel
;
306 // The matrix that GetBBox accepts should operate on "user space",
307 // i.e. with CSS pixel unit.
308 m
.PreScale(devPixelPerCSSPixel
, devPixelPerCSSPixel
);
310 auto initPosition
= gfxPoint(
311 NSAppUnitsToFloatPixels(aFrame
->GetPosition().x
, AppUnitsPerCSSPixel()),
312 NSAppUnitsToFloatPixels(aFrame
->GetPosition().y
, AppUnitsPerCSSPixel()));
314 // Both SVGUtils::GetBBox and nsLayoutUtils::GetTransformToAncestor
315 // will count this displacement, we should remove it here to avoid
317 m
.PreTranslate(-initPosition
);
319 uint32_t flags
= SVGUtils::eForGetClientRects
| SVGUtils::eBBoxIncludeFill
|
320 SVGUtils::eBBoxIncludeStroke
|
321 SVGUtils::eBBoxIncludeMarkers
|
322 SVGUtils::eUseUserSpaceOfUseElement
;
324 gfxRect bbox
= SVGUtils::GetBBox(aFrame
, flags
, &m
);
325 *aRect
= nsLayoutUtils::RoundGfxRectToAppRect(bbox
, appUnitsPerDevPixel
);
330 gfxMatrix
SVGUtils::GetCanvasTM(nsIFrame
* aFrame
) {
331 // XXX yuck, we really need a common interface for GetCanvasTM
333 if (!aFrame
->HasAnyStateBits(NS_FRAME_SVG_LAYOUT
)) {
334 return GetCSSPxToDevPxMatrix(aFrame
);
337 if (aFrame
->IsSVGForeignObjectFrame()) {
338 return static_cast<SVGForeignObjectFrame
*>(aFrame
)->GetCanvasTM();
341 if (SVGContainerFrame
* containerFrame
= do_QueryFrame(aFrame
)) {
342 return containerFrame
->GetCanvasTM();
345 MOZ_ASSERT(aFrame
->GetParent()->IsSVGContainerFrame());
347 auto* parent
= static_cast<SVGContainerFrame
*>(aFrame
->GetParent());
348 auto* content
= static_cast<SVGElement
*>(aFrame
->GetContent());
350 return content
->ChildToUserSpaceTransform() * parent
->GetCanvasTM();
353 bool SVGUtils::GetParentSVGTransforms(const nsIFrame
* aFrame
,
354 gfx::Matrix
* aFromParentTransform
) {
355 MOZ_ASSERT(aFrame
->HasAllStateBits(NS_FRAME_SVG_LAYOUT
|
356 NS_FRAME_MAY_BE_TRANSFORMED
),
357 "Expecting an SVG frame that can be transformed");
358 if (SVGContainerFrame
* parent
= do_QueryFrame(aFrame
->GetParent())) {
359 return parent
->HasChildrenOnlyTransform(aFromParentTransform
);
364 void SVGUtils::NotifyChildrenOfSVGChange(nsIFrame
* aFrame
, uint32_t aFlags
) {
365 for (nsIFrame
* kid
: aFrame
->PrincipalChildList()) {
366 ISVGDisplayableFrame
* SVGFrame
= do_QueryFrame(kid
);
368 SVGFrame
->NotifySVGChanged(aFlags
);
370 NS_ASSERTION(kid
->IsSVGFrame() || kid
->IsInSVGTextSubtree(),
371 "SVG frame expected");
372 // recurse into the children of container frames e.g. <clipPath>, <mask>
373 // in case they have child frames with transformation matrices
374 if (kid
->IsSVGFrame()) {
375 NotifyChildrenOfSVGChange(kid
, aFlags
);
381 // ************************************************************
383 float SVGUtils::ComputeOpacity(const nsIFrame
* aFrame
, bool aHandleOpacity
) {
384 const auto* styleEffects
= aFrame
->StyleEffects();
386 if (!styleEffects
->IsOpaque() &&
387 (SVGUtils::CanOptimizeOpacity(aFrame
) || !aHandleOpacity
)) {
391 return styleEffects
->mOpacity
;
394 SVGUtils::MaskUsage
SVGUtils::DetermineMaskUsage(const nsIFrame
* aFrame
,
395 bool aHandleOpacity
) {
398 using ClipPathType
= StyleClipPath::Tag
;
400 usage
.mOpacity
= ComputeOpacity(aFrame
, aHandleOpacity
);
402 nsIFrame
* firstFrame
=
403 nsLayoutUtils::FirstContinuationOrIBSplitSibling(aFrame
);
405 const nsStyleSVGReset
* svgReset
= firstFrame
->StyleSVGReset();
407 if (SVGObserverUtils::GetAndObserveMasks(firstFrame
, nullptr) !=
408 SVGObserverUtils::eHasNoRefs
) {
409 usage
.mShouldGenerateMaskLayer
= true;
412 SVGClipPathFrame
* clipPathFrame
;
413 // XXX check return value?
414 SVGObserverUtils::GetAndObserveClipPath(firstFrame
, &clipPathFrame
);
415 MOZ_ASSERT(!clipPathFrame
|| svgReset
->mClipPath
.IsUrl());
417 switch (svgReset
->mClipPath
.tag
) {
418 case ClipPathType::Url
:
420 if (clipPathFrame
->IsTrivial()) {
421 usage
.mShouldApplyClipPath
= true;
423 usage
.mShouldGenerateClipMaskLayer
= true;
427 case ClipPathType::Shape
: {
428 usage
.mShouldApplyBasicShapeOrPath
= true;
429 const auto& shape
= svgReset
->mClipPath
.AsShape()._0
;
430 usage
.mIsSimpleClipShape
=
431 !usage
.mShouldGenerateMaskLayer
&&
432 (shape
->IsRect() || shape
->IsCircle() || shape
->IsEllipse());
435 case ClipPathType::Box
:
436 usage
.mShouldApplyBasicShapeOrPath
= true;
438 case ClipPathType::None
:
439 MOZ_ASSERT(!usage
.mShouldGenerateClipMaskLayer
&&
440 !usage
.mShouldApplyClipPath
&&
441 !usage
.mShouldApplyBasicShapeOrPath
);
444 MOZ_ASSERT_UNREACHABLE("Unsupported clip-path type.");
450 class MixModeBlender
{
452 using Factory
= gfx::Factory
;
454 MixModeBlender(nsIFrame
* aFrame
, gfxContext
* aContext
)
455 : mFrame(aFrame
), mSourceCtx(aContext
) {
456 MOZ_ASSERT(mFrame
&& mSourceCtx
);
459 bool ShouldCreateDrawTargetForBlend() const {
460 return mFrame
->StyleEffects()->HasMixBlendMode();
463 gfxContext
* CreateBlendTarget(const gfxMatrix
& aTransform
) {
464 MOZ_ASSERT(ShouldCreateDrawTargetForBlend());
466 // Create a temporary context to draw to so we can blend it back with
468 IntRect drawRect
= ComputeClipExtsInDeviceSpace(aTransform
);
469 if (drawRect
.IsEmpty()) {
473 RefPtr
<DrawTarget
> targetDT
=
474 mSourceCtx
->GetDrawTarget()->CreateSimilarDrawTarget(
475 drawRect
.Size(), SurfaceFormat::B8G8R8A8
);
476 if (!targetDT
|| !targetDT
->IsValid()) {
480 MOZ_ASSERT(!mTargetCtx
,
481 "CreateBlendTarget is designed to be used once only.");
483 mTargetCtx
= gfxContext::CreateOrNull(targetDT
);
484 MOZ_ASSERT(mTargetCtx
); // already checked the draw target above
485 mTargetCtx
->SetMatrix(mSourceCtx
->CurrentMatrix() *
486 Matrix::Translation(-drawRect
.TopLeft()));
488 mTargetOffset
= drawRect
.TopLeft();
490 return mTargetCtx
.get();
493 void BlendToTarget() {
494 MOZ_ASSERT(ShouldCreateDrawTargetForBlend());
495 MOZ_ASSERT(mTargetCtx
,
496 "BlendToTarget should be used after CreateBlendTarget.");
498 RefPtr
<SourceSurface
> targetSurf
= mTargetCtx
->GetDrawTarget()->Snapshot();
500 gfxContextAutoSaveRestore
save(mSourceCtx
);
501 mSourceCtx
->SetMatrix(Matrix()); // This will be restored right after.
502 auto pattern
= MakeRefPtr
<gfxPattern
>(
503 targetSurf
, Matrix::Translation(mTargetOffset
.x
, mTargetOffset
.y
));
504 mSourceCtx
->SetPattern(pattern
);
509 MixModeBlender() = delete;
511 IntRect
ComputeClipExtsInDeviceSpace(const gfxMatrix
& aTransform
) {
512 // These are used if we require a temporary surface for a custom blend
513 // mode. Clip the source context first, so that we can generate a smaller
514 // temporary surface. (Since we will clip this context in
515 // SetupContextMatrix, a pair of save/restore is needed.)
516 gfxContextAutoSaveRestore saver
;
518 if (!mFrame
->HasAnyStateBits(NS_FRAME_IS_NONDISPLAY
)) {
519 saver
.SetContext(mSourceCtx
);
520 // aFrame has a valid ink overflow rect, so clip to it before calling
521 // PushGroup() to minimize the size of the surfaces we'll composite:
522 gfxContextMatrixAutoSaveRestore
matrixAutoSaveRestore(mSourceCtx
);
523 mSourceCtx
->Multiply(aTransform
);
524 nsRect overflowRect
= mFrame
->InkOverflowRectRelativeToSelf();
525 if (FrameDoesNotIncludePositionInTM(mFrame
)) {
526 overflowRect
= overflowRect
+ mFrame
->GetPosition();
528 mSourceCtx
->Clip(NSRectToSnappedRect(
529 overflowRect
, mFrame
->PresContext()->AppUnitsPerDevPixel(),
530 *mSourceCtx
->GetDrawTarget()));
533 // Get the clip extents in device space.
534 gfxRect clippedFrameSurfaceRect
=
535 mSourceCtx
->GetClipExtents(gfxContext::eDeviceSpace
);
536 clippedFrameSurfaceRect
.RoundOut();
539 ToRect(clippedFrameSurfaceRect
).ToIntRect(&result
);
541 return Factory::CheckSurfaceSize(result
.Size()) ? result
: IntRect();
545 gfxContext
* mSourceCtx
;
546 UniquePtr
<gfxContext
> mTargetCtx
;
547 IntPoint mTargetOffset
;
550 void SVGUtils::PaintFrameWithEffects(nsIFrame
* aFrame
, gfxContext
& aContext
,
551 const gfxMatrix
& aTransform
,
552 imgDrawingParams
& aImgParams
) {
553 NS_ASSERTION(aFrame
->HasAnyStateBits(NS_FRAME_IS_NONDISPLAY
) ||
554 aFrame
->PresContext()->Document()->IsSVGGlyphsDocument(),
555 "Only painting of non-display SVG should take this code path");
557 ISVGDisplayableFrame
* svgFrame
= do_QueryFrame(aFrame
);
562 MaskUsage maskUsage
= DetermineMaskUsage(aFrame
, true);
563 if (maskUsage
.IsTransparent()) {
567 if (auto* svg
= SVGElement::FromNode(aFrame
->GetContent())) {
568 if (!svg
->HasValidDimensions()) {
571 if (aFrame
->IsSVGSymbolFrame() && !svg
->IsInSVGUseShadowTree()) {
576 /* SVG defines the following rendering model:
582 * 5. Apply clipping, masking, group opacity
584 * We follow this, but perform a couple of optimizations:
586 * + Use cairo's clipPath when representable natively (single object
589 * + Merge opacity and masking if both used together.
592 /* Properties are added lazily and may have been removed by a restyle,
593 so make sure all applicable ones are set again. */
594 SVGClipPathFrame
* clipPathFrame
;
595 nsTArray
<SVGMaskFrame
*> maskFrames
;
596 nsTArray
<SVGFilterFrame
*> filterFrames
;
597 const bool hasInvalidFilter
=
598 SVGObserverUtils::GetAndObserveFilters(aFrame
, &filterFrames
) ==
599 SVGObserverUtils::eHasRefsSomeInvalid
;
600 SVGObserverUtils::GetAndObserveClipPath(aFrame
, &clipPathFrame
);
601 SVGObserverUtils::GetAndObserveMasks(aFrame
, &maskFrames
);
603 SVGMaskFrame
* maskFrame
= maskFrames
.IsEmpty() ? nullptr : maskFrames
[0];
605 MixModeBlender
blender(aFrame
, &aContext
);
606 gfxContext
* target
= blender
.ShouldCreateDrawTargetForBlend()
607 ? blender
.CreateBlendTarget(aTransform
)
614 /* Check if we need to do additional operations on this child's
615 * rendering, which necessitates rendering into another surface. */
616 bool shouldPushMask
= false;
618 if (maskUsage
.ShouldGenerateMask()) {
619 RefPtr
<SourceSurface
> maskSurface
;
621 // maskFrame can be nullptr even if maskUsage.ShouldGenerateMaskLayer() is
622 // true. That happens when a user gives an unresolvable mask-id, such as
624 // mask:url(#id-which-does-not-exist)
625 // Since we only uses SVGUtils with SVG elements, not like mask on an
626 // HTML element, we should treat an unresolvable mask as no-mask here.
627 if (maskUsage
.ShouldGenerateMaskLayer() && maskFrame
) {
628 StyleMaskMode maskMode
=
629 aFrame
->StyleSVGReset()->mMask
.mLayers
[0].mMaskMode
;
630 SVGMaskFrame::MaskParams
params(aContext
.GetDrawTarget(), aFrame
,
631 aTransform
, maskUsage
.Opacity(), maskMode
,
634 maskSurface
= maskFrame
->GetMaskForMaskedFrame(params
);
637 // Either entire surface is clipped out, or gfx buffer allocation
638 // failure in SVGMaskFrame::GetMaskForMaskedFrame.
641 shouldPushMask
= true;
644 if (maskUsage
.ShouldGenerateClipMaskLayer()) {
645 RefPtr
<SourceSurface
> clipMaskSurface
=
646 clipPathFrame
->GetClipMask(aContext
, aFrame
, aTransform
, maskSurface
);
647 if (clipMaskSurface
) {
648 maskSurface
= clipMaskSurface
;
650 // Either entire surface is clipped out, or gfx buffer allocation
651 // failure in SVGClipPathFrame::GetClipMask.
654 shouldPushMask
= true;
657 if (!maskUsage
.ShouldGenerateLayer()) {
658 shouldPushMask
= true;
661 // SVG mask multiply opacity into maskSurface already, so we do not bother
662 // to apply opacity again.
663 if (shouldPushMask
) {
664 // We want the mask to be untransformed so use the inverse of the
665 // current transform as the maskTransform to compensate.
666 Matrix maskTransform
= aContext
.CurrentMatrix();
667 maskTransform
.Invert();
668 target
->PushGroupForBlendBack(gfxContentType::COLOR_ALPHA
,
669 maskFrame
? 1.0f
: maskUsage
.Opacity(),
670 maskSurface
, maskTransform
);
674 /* If this frame has only a trivial clipPath, set up cairo's clipping now so
675 * we can just do normal painting and get it clipped appropriately.
677 if (maskUsage
.ShouldApplyClipPath() ||
678 maskUsage
.ShouldApplyBasicShapeOrPath()) {
679 if (maskUsage
.ShouldApplyClipPath()) {
680 clipPathFrame
->ApplyClipPath(aContext
, aFrame
, aTransform
);
682 CSSClipPathInstance::ApplyBasicShapeOrPathClip(aContext
, aFrame
,
687 /* Paint the child */
689 // Invalid filters should render the unfiltered contents per spec.
690 if (aFrame
->StyleEffects()->HasFilters() && !hasInvalidFilter
) {
691 gfxContextMatrixAutoSaveRestore
autoSR(target
);
693 // 'target' is currently scaled such that its user space units are CSS
694 // pixels (SVG user space units). But PaintFilteredFrame expects it to be
695 // scaled in such a way that its user space units are device pixels. So we
696 // have to adjust the scale.
697 gfxMatrix reverseScaleMatrix
= SVGUtils::GetCSSPxToDevPxMatrix(aFrame
);
698 DebugOnly
<bool> invertible
= reverseScaleMatrix
.Invert();
699 target
->SetMatrixDouble(reverseScaleMatrix
* aTransform
*
700 target
->CurrentMatrixDouble());
702 auto callback
= [&](gfxContext
& aContext
, imgDrawingParams
& aImgParams
,
703 const gfxMatrix
* aFilterTransform
,
704 const nsIntRect
* aDirtyRect
) {
705 svgFrame
->PaintSVG(aContext
,
707 ? SVGUtils::GetCSSPxToDevPxMatrix(aFrame
)
711 // If we're masking a userSpaceOnUse mask we may need to include the
712 // stroke too. Err on the side of caution and include it always.
713 gfxRect bbox
= GetBBox(aFrame
, SVGUtils::eUseFrameBoundsForOuterSVG
|
714 SVGUtils::eBBoxIncludeFillGeometry
|
715 SVGUtils::eBBoxIncludeStroke
);
716 FilterInstance::PaintFilteredFrame(
717 aFrame
, aFrame
->StyleEffects()->mFilters
.AsSpan(), filterFrames
, target
,
718 callback
, nullptr, aImgParams
, 1.0f
, &bbox
);
720 svgFrame
->PaintSVG(*target
, aTransform
, aImgParams
);
723 if (maskUsage
.ShouldApplyClipPath() ||
724 maskUsage
.ShouldApplyBasicShapeOrPath()) {
728 if (shouldPushMask
) {
729 target
->PopGroupAndBlend();
732 if (blender
.ShouldCreateDrawTargetForBlend()) {
733 MOZ_ASSERT(target
!= &aContext
);
734 blender
.BlendToTarget();
738 bool SVGUtils::HitTestClip(nsIFrame
* aFrame
, const gfxPoint
& aPoint
) {
739 const nsStyleSVGReset
* svgReset
= aFrame
->StyleSVGReset();
740 if (!svgReset
->HasClipPath()) {
743 if (svgReset
->mClipPath
.IsUrl()) {
744 // If the clip-path property references non-existent or invalid clipPath
745 // element(s) we ignore it.
746 SVGClipPathFrame
* clipPathFrame
;
747 SVGObserverUtils::GetAndObserveClipPath(aFrame
, &clipPathFrame
);
748 return !clipPathFrame
||
749 clipPathFrame
->PointIsInsideClipPath(aFrame
, aPoint
);
751 return CSSClipPathInstance::HitTestBasicShapeOrPathClip(aFrame
, aPoint
);
754 IntSize
SVGUtils::ConvertToSurfaceSize(const gfxSize
& aSize
,
755 bool* aResultOverflows
) {
756 IntSize
surfaceSize(ClampToInt(ceil(aSize
.width
)),
757 ClampToInt(ceil(aSize
.height
)));
759 *aResultOverflows
= surfaceSize
.width
!= ceil(aSize
.width
) ||
760 surfaceSize
.height
!= ceil(aSize
.height
);
762 if (!Factory::AllowedSurfaceSize(surfaceSize
)) {
764 std::min(NS_SVG_OFFSCREEN_MAX_DIMENSION
, surfaceSize
.width
);
766 std::min(NS_SVG_OFFSCREEN_MAX_DIMENSION
, surfaceSize
.height
);
767 *aResultOverflows
= true;
773 bool SVGUtils::HitTestRect(const gfx::Matrix
& aMatrix
, float aRX
, float aRY
,
774 float aRWidth
, float aRHeight
, float aX
, float aY
) {
775 gfx::Rect
rect(aRX
, aRY
, aRWidth
, aRHeight
);
776 if (rect
.IsEmpty() || aMatrix
.IsSingular()) {
779 gfx::Matrix toRectSpace
= aMatrix
;
780 toRectSpace
.Invert();
781 gfx::Point p
= toRectSpace
.TransformPoint(gfx::Point(aX
, aY
));
782 return rect
.x
<= p
.x
&& p
.x
<= rect
.XMost() && rect
.y
<= p
.y
&&
786 gfxRect
SVGUtils::GetClipRectForFrame(const nsIFrame
* aFrame
, float aX
,
787 float aY
, float aWidth
, float aHeight
) {
788 const nsStyleDisplay
* disp
= aFrame
->StyleDisplay();
789 const nsStyleEffects
* effects
= aFrame
->StyleEffects();
791 bool clipApplies
= disp
->mOverflowX
== StyleOverflow::Hidden
||
792 disp
->mOverflowY
== StyleOverflow::Hidden
;
794 if (!clipApplies
|| effects
->mClip
.IsAuto()) {
795 return gfxRect(aX
, aY
, aWidth
, aHeight
);
798 const auto& rect
= effects
->mClip
.AsRect();
799 nsRect coordClipRect
= rect
.ToLayoutRect();
800 nsIntRect clipPxRect
= coordClipRect
.ToOutsidePixels(AppUnitsPerCSSPixel());
802 gfxRect(clipPxRect
.x
, clipPxRect
.y
, clipPxRect
.width
, clipPxRect
.height
);
803 if (rect
.right
.IsAuto()) {
804 clipRect
.width
= aWidth
- clipRect
.X();
806 if (rect
.bottom
.IsAuto()) {
807 clipRect
.height
= aHeight
- clipRect
.Y();
809 if (disp
->mOverflowX
!= StyleOverflow::Hidden
) {
811 clipRect
.width
= aWidth
;
813 if (disp
->mOverflowY
!= StyleOverflow::Hidden
) {
815 clipRect
.height
= aHeight
;
820 gfxRect
SVGUtils::GetBBox(nsIFrame
* aFrame
, uint32_t aFlags
,
821 const gfxMatrix
* aToBoundsSpace
) {
822 if (aFrame
->IsTextFrame()) {
823 aFrame
= aFrame
->GetParent();
826 if (aFrame
->IsInSVGTextSubtree()) {
827 // It is possible to apply a gradient, pattern, clipping path, mask or
828 // filter to text. When one of these facilities is applied to text
829 // the bounding box is the entire text element in all cases.
831 nsLayoutUtils::GetClosestFrameOfType(aFrame
, LayoutFrameType::SVGText
);
834 ISVGDisplayableFrame
* svg
= do_QueryFrame(aFrame
);
835 const bool hasSVGLayout
= aFrame
->HasAnyStateBits(NS_FRAME_SVG_LAYOUT
);
836 if (hasSVGLayout
&& !svg
) {
837 // An SVG frame, but not one that can be displayed directly (for
838 // example, nsGradientFrame). These can't contribute to the bbox.
842 const bool isOuterSVG
= svg
&& !hasSVGLayout
;
843 MOZ_ASSERT(!isOuterSVG
|| aFrame
->IsSVGOuterSVGFrame());
844 if (!svg
|| (isOuterSVG
&& (aFlags
& eUseFrameBoundsForOuterSVG
))) {
845 // An HTML element or an SVG outer frame.
846 MOZ_ASSERT(!hasSVGLayout
);
847 bool onlyCurrentFrame
= aFlags
& eIncludeOnlyCurrentFrameForNonSVGElement
;
848 return SVGIntegrationUtils::GetSVGBBoxForNonSVGFrame(
850 /* aUnionContinuations = */ !onlyCurrentFrame
);
855 if (auto* element
= SVGElement::FromNodeOrNull(aFrame
->GetContent())) {
856 if (!element
->HasValidDimensions()) {
861 // Clean out flags which have no effects on returning bbox from now, so that
862 // we can cache and reuse ObjectBoundingBoxProperty() in the code below.
864 ~(eIncludeOnlyCurrentFrameForNonSVGElement
| eUseFrameBoundsForOuterSVG
);
865 if (!aFrame
->IsSVGUseFrame()) {
866 aFlags
&= ~eUseUserSpaceOfUseElement
;
869 if (aFlags
== eBBoxIncludeFillGeometry
&&
870 // We only cache bbox in element's own user space
872 gfxRect
* prop
= aFrame
->GetProperty(ObjectBoundingBoxProperty());
879 if (aToBoundsSpace
) {
880 matrix
= *aToBoundsSpace
;
883 if (aFrame
->IsSVGForeignObjectFrame() ||
884 aFlags
& SVGUtils::eUseUserSpaceOfUseElement
) {
885 // The spec says getBBox "Returns the tight bounding box in *current user
886 // space*". So we should really be doing this for all elements, but that
887 // needs investigation to check that we won't break too much content.
888 // NOTE: When changing this to apply to other frame types, make sure to
889 // also update SVGUtils::FrameSpaceInCSSPxToUserSpaceOffset.
890 MOZ_ASSERT(aFrame
->GetContent()->IsSVGElement(), "bad cast");
891 auto* element
= static_cast<SVGElement
*>(aFrame
->GetContent());
892 matrix
= element
->ChildToUserSpaceTransform() * matrix
;
895 svg
->GetBBoxContribution(ToMatrix(matrix
), aFlags
).ToThebesRect();
896 // Account for 'clipped'.
897 if (aFlags
& SVGUtils::eBBoxIncludeClipped
) {
900 svg
->GetBBoxContribution({}, SVGUtils::eBBoxIncludeFill
).ToThebesRect();
901 // XXX Should probably check for overflow: clip too.
902 bool hasClip
= aFrame
->StyleDisplay()->IsScrollableOverflow();
904 clipRect
= SVGUtils::GetClipRectForFrame(aFrame
, 0.0f
, 0.0f
,
905 fillBBox
.width
, fillBBox
.height
);
906 clipRect
.MoveBy(fillBBox
.TopLeft());
907 if (aFrame
->IsSVGForeignObjectFrame() || aFrame
->IsSVGUseFrame()) {
908 clipRect
= matrix
.TransformBounds(clipRect
);
911 SVGClipPathFrame
* clipPathFrame
;
912 if (SVGObserverUtils::GetAndObserveClipPath(aFrame
, &clipPathFrame
) ==
913 SVGObserverUtils::eHasRefsSomeInvalid
) {
917 SVGClipPathElement
* clipContent
=
918 static_cast<SVGClipPathElement
*>(clipPathFrame
->GetContent());
919 if (clipContent
->IsUnitsObjectBoundingBox()) {
920 matrix
.PreTranslate(fillBBox
.TopLeft());
921 matrix
.PreScale(fillBBox
.width
, fillBBox
.height
);
922 } else if (aFrame
->IsSVGForeignObjectFrame()) {
923 matrix
= gfxMatrix();
925 matrix
*= SVGUtils::GetTransformMatrixInUserSpace(clipPathFrame
);
927 bbox
= clipPathFrame
->GetBBoxForClipPathFrame(bbox
, matrix
, aFlags
)
932 bbox
= bbox
.Intersect(clipRect
);
935 if (bbox
.IsEmpty()) {
941 if (aFlags
== eBBoxIncludeFillGeometry
&&
942 // We only cache bbox in element's own user space
944 // Obtaining the bbox for objectBoundingBox calculations is common so we
945 // cache the result for future calls, since calculation can be expensive:
946 aFrame
->SetProperty(ObjectBoundingBoxProperty(), new gfxRect(bbox
));
952 gfxPoint
SVGUtils::FrameSpaceInCSSPxToUserSpaceOffset(const nsIFrame
* aFrame
) {
953 if (!aFrame
->HasAnyStateBits(NS_FRAME_SVG_LAYOUT
)) {
954 // The user space for non-SVG frames is defined as the bounding box of the
955 // frame's border-box rects over all continuations.
959 // Leaf frames apply their own offset inside their user space.
960 if (FrameDoesNotIncludePositionInTM(aFrame
)) {
961 return nsLayoutUtils::RectToGfxRect(aFrame
->GetRect(),
962 AppUnitsPerCSSPixel())
966 // For foreignObject frames, SVGUtils::GetBBox applies their local
967 // transform, so we need to do the same here.
968 if (aFrame
->IsSVGForeignObjectFrame()) {
969 gfxMatrix transform
= static_cast<SVGElement
*>(aFrame
->GetContent())
970 ->ChildToUserSpaceTransform();
971 NS_ASSERTION(!transform
.HasNonTranslation(),
972 "we're relying on this being an offset-only transform");
973 return transform
.GetTranslation();
979 static gfxRect
GetBoundingBoxRelativeRect(const SVGAnimatedLength
* aXYWH
,
980 const gfxRect
& aBBox
) {
981 return gfxRect(aBBox
.x
+ SVGUtils::ObjectSpace(aBBox
, &aXYWH
[0]),
982 aBBox
.y
+ SVGUtils::ObjectSpace(aBBox
, &aXYWH
[1]),
983 SVGUtils::ObjectSpace(aBBox
, &aXYWH
[2]),
984 SVGUtils::ObjectSpace(aBBox
, &aXYWH
[3]));
987 gfxRect
SVGUtils::GetRelativeRect(uint16_t aUnits
,
988 const SVGAnimatedLength
* aXYWH
,
989 const gfxRect
& aBBox
,
990 const UserSpaceMetrics
& aMetrics
) {
991 if (aUnits
== SVG_UNIT_TYPE_OBJECTBOUNDINGBOX
) {
992 return GetBoundingBoxRelativeRect(aXYWH
, aBBox
);
994 return gfxRect(UserSpace(aMetrics
, &aXYWH
[0]), UserSpace(aMetrics
, &aXYWH
[1]),
995 UserSpace(aMetrics
, &aXYWH
[2]),
996 UserSpace(aMetrics
, &aXYWH
[3]));
999 gfxRect
SVGUtils::GetRelativeRect(uint16_t aUnits
,
1000 const SVGAnimatedLength
* aXYWH
,
1001 const gfxRect
& aBBox
, nsIFrame
* aFrame
) {
1002 if (aUnits
== SVG_UNIT_TYPE_OBJECTBOUNDINGBOX
) {
1003 return GetBoundingBoxRelativeRect(aXYWH
, aBBox
);
1005 if (SVGElement
* svgElement
= SVGElement::FromNode(aFrame
->GetContent())) {
1006 return GetRelativeRect(aUnits
, aXYWH
, aBBox
, SVGElementMetrics(svgElement
));
1008 return GetRelativeRect(aUnits
, aXYWH
, aBBox
,
1009 NonSVGFrameUserSpaceMetrics(aFrame
));
1012 bool SVGUtils::CanOptimizeOpacity(const nsIFrame
* aFrame
) {
1013 if (!aFrame
->HasAnyStateBits(NS_FRAME_SVG_LAYOUT
)) {
1016 auto* content
= aFrame
->GetContent();
1017 if (!content
->IsSVGGeometryElement() &&
1018 !content
->IsSVGElement(nsGkAtoms::image
)) {
1021 if (aFrame
->StyleEffects()->HasFilters()) {
1024 // XXX The SVG WG is intending to allow fill, stroke and markers on <image>
1025 if (content
->IsSVGElement(nsGkAtoms::image
)) {
1028 const nsStyleSVG
* style
= aFrame
->StyleSVG();
1029 if (style
->HasMarker() &&
1030 static_cast<SVGGeometryElement
*>(content
)->IsMarkable()) {
1034 if (nsLayoutUtils::HasAnimationOfPropertySet(
1035 aFrame
, nsCSSPropertyIDSet::OpacityProperties())) {
1039 return !style
->HasFill() || !HasStroke(aFrame
);
1042 gfxMatrix
SVGUtils::AdjustMatrixForUnits(const gfxMatrix
& aMatrix
,
1043 const SVGAnimatedEnumeration
* aUnits
,
1044 nsIFrame
* aFrame
, uint32_t aFlags
) {
1045 if (aFrame
&& aUnits
->GetAnimValue() == SVG_UNIT_TYPE_OBJECTBOUNDINGBOX
) {
1046 gfxRect bbox
= GetBBox(aFrame
, aFlags
);
1047 gfxMatrix tm
= aMatrix
;
1048 tm
.PreTranslate(gfxPoint(bbox
.X(), bbox
.Y()));
1049 tm
.PreScale(bbox
.Width(), bbox
.Height());
1055 bool SVGUtils::GetNonScalingStrokeTransform(const nsIFrame
* aFrame
,
1056 gfxMatrix
* aUserToOuterSVG
) {
1057 if (aFrame
->GetContent()->IsText()) {
1058 aFrame
= aFrame
->GetParent();
1061 if (!aFrame
->StyleSVGReset()->HasNonScalingStroke()) {
1065 MOZ_ASSERT(aFrame
->GetContent()->IsSVGElement(), "should be an SVG element");
1067 SVGElement
* content
= static_cast<SVGElement
*>(aFrame
->GetContent());
1069 ThebesMatrix(SVGContentUtils::GetNonScalingStrokeCTM(content
));
1071 return aUserToOuterSVG
->HasNonTranslation() && !aUserToOuterSVG
->IsSingular();
1074 void SVGUtils::UpdateNonScalingStrokeStateBit(nsIFrame
* aFrame
) {
1075 MOZ_ASSERT(aFrame
->HasAnyStateBits(NS_FRAME_SVG_LAYOUT
),
1076 "Called on invalid frame type");
1077 MOZ_ASSERT(aFrame
->StyleSVGReset()->HasNonScalingStroke(),
1078 "Expecting initial frame to have non-scaling-stroke style");
1081 MOZ_ASSERT(aFrame
->IsSVGFrame(), "Unexpected frame type");
1082 aFrame
->AddStateBits(NS_STATE_SVG_MAY_CONTAIN_NON_SCALING_STROKE
);
1083 if (aFrame
->IsSVGOuterSVGFrame()) {
1086 } while ((aFrame
= aFrame
->GetParent()));
1089 // The logic here comes from _cairo_stroke_style_max_distance_from_path
1090 static gfxRect
PathExtentsToMaxStrokeExtents(const gfxRect
& aPathExtents
,
1091 const nsIFrame
* aFrame
,
1092 double aStyleExpansionFactor
,
1093 const gfxMatrix
& aMatrix
) {
1094 double style_expansion
=
1095 aStyleExpansionFactor
* SVGUtils::GetStrokeWidth(aFrame
);
1097 gfxMatrix matrix
= aMatrix
;
1099 gfxMatrix outerSVGToUser
;
1100 if (SVGUtils::GetNonScalingStrokeTransform(aFrame
, &outerSVGToUser
)) {
1101 outerSVGToUser
.Invert();
1102 matrix
.PreMultiply(outerSVGToUser
);
1105 double dx
= style_expansion
* (fabs(matrix
._11
) + fabs(matrix
._21
));
1106 double dy
= style_expansion
* (fabs(matrix
._22
) + fabs(matrix
._12
));
1108 gfxRect strokeExtents
= aPathExtents
;
1109 strokeExtents
.Inflate(dx
, dy
);
1110 return strokeExtents
;
1114 gfxRect
SVGUtils::PathExtentsToMaxStrokeExtents(const gfxRect
& aPathExtents
,
1115 const nsTextFrame
* aFrame
,
1116 const gfxMatrix
& aMatrix
) {
1117 NS_ASSERTION(aFrame
->IsInSVGTextSubtree(),
1118 "expected an nsTextFrame for SVG text");
1119 return mozilla::PathExtentsToMaxStrokeExtents(aPathExtents
, aFrame
, 0.5,
1124 gfxRect
SVGUtils::PathExtentsToMaxStrokeExtents(const gfxRect
& aPathExtents
,
1125 const SVGGeometryFrame
* aFrame
,
1126 const gfxMatrix
& aMatrix
) {
1127 bool strokeMayHaveCorners
=
1128 !SVGContentUtils::ShapeTypeHasNoCorners(aFrame
->GetContent());
1130 // For a shape without corners the stroke can only extend half the stroke
1131 // width from the path in the x/y-axis directions. For shapes with corners
1132 // the stroke can extend by sqrt(1/2) (think 45 degree rotated rect, or line
1133 // with stroke-linecaps="square").
1134 double styleExpansionFactor
= strokeMayHaveCorners
? M_SQRT1_2
: 0.5;
1136 // The stroke can extend even further for paths that can be affected by
1137 // stroke-miterlimit.
1138 // We only need to do this if the limit is greater than 1, but it's probably
1139 // not worth optimizing for that.
1140 bool affectedByMiterlimit
= aFrame
->GetContent()->IsAnyOfSVGElements(
1141 nsGkAtoms::path
, nsGkAtoms::polyline
, nsGkAtoms::polygon
);
1143 if (affectedByMiterlimit
) {
1144 const nsStyleSVG
* style
= aFrame
->StyleSVG();
1145 if (style
->mStrokeLinejoin
== StyleStrokeLinejoin::Miter
&&
1146 styleExpansionFactor
< style
->mStrokeMiterlimit
/ 2.0) {
1147 styleExpansionFactor
= style
->mStrokeMiterlimit
/ 2.0;
1151 return mozilla::PathExtentsToMaxStrokeExtents(aPathExtents
, aFrame
,
1152 styleExpansionFactor
, aMatrix
);
1155 // ----------------------------------------------------------------------
1158 nscolor
SVGUtils::GetFallbackOrPaintColor(
1159 const ComputedStyle
& aStyle
, StyleSVGPaint
nsStyleSVG::*aFillOrStroke
,
1160 nscolor aDefaultContextFallbackColor
) {
1161 const auto& paint
= aStyle
.StyleSVG()->*aFillOrStroke
;
1163 switch (paint
.kind
.tag
) {
1164 case StyleSVGPaintKind::Tag::PaintServer
:
1165 color
= paint
.fallback
.IsColor()
1166 ? paint
.fallback
.AsColor().CalcColor(aStyle
)
1167 : NS_RGBA(0, 0, 0, 0);
1169 case StyleSVGPaintKind::Tag::ContextStroke
:
1170 case StyleSVGPaintKind::Tag::ContextFill
:
1171 color
= paint
.fallback
.IsColor()
1172 ? paint
.fallback
.AsColor().CalcColor(aStyle
)
1173 : aDefaultContextFallbackColor
;
1176 color
= paint
.kind
.AsColor().CalcColor(aStyle
);
1179 if (const auto* styleIfVisited
= aStyle
.GetStyleIfVisited()) {
1180 const auto& paintIfVisited
= styleIfVisited
->StyleSVG()->*aFillOrStroke
;
1181 // To prevent Web content from detecting if a user has visited a URL
1182 // (via URL loading triggered by paint servers or performance
1183 // differences between paint servers or between a paint server and a
1184 // color), we do not allow whether links are visited to change which
1185 // paint server is used or switch between paint servers and simple
1186 // colors. A :visited style may only override a simple color with
1187 // another simple color.
1188 if (paintIfVisited
.kind
.IsColor() && paint
.kind
.IsColor()) {
1189 nscolor colors
[2] = {
1190 color
, paintIfVisited
.kind
.AsColor().CalcColor(*styleIfVisited
)};
1191 return ComputedStyle::CombineVisitedColors(colors
,
1192 aStyle
.RelevantLinkVisited());
1199 void SVGUtils::MakeFillPatternFor(nsIFrame
* aFrame
, gfxContext
* aContext
,
1200 GeneralPattern
* aOutPattern
,
1201 imgDrawingParams
& aImgParams
,
1202 SVGContextPaint
* aContextPaint
) {
1203 const nsStyleSVG
* style
= aFrame
->StyleSVG();
1204 if (style
->mFill
.kind
.IsNone()) {
1208 const auto* styleEffects
= aFrame
->StyleEffects();
1210 float fillOpacity
= GetOpacity(style
->mFillOpacity
, aContextPaint
);
1211 if (!styleEffects
->IsOpaque() && SVGUtils::CanOptimizeOpacity(aFrame
)) {
1212 // Combine the group opacity into the fill opacity (we will have skipped
1213 // creating an offscreen surface to apply the group opacity).
1214 fillOpacity
*= styleEffects
->mOpacity
;
1217 const DrawTarget
* dt
= aContext
->GetDrawTarget();
1219 SVGPaintServerFrame
* ps
=
1220 SVGObserverUtils::GetAndObservePaintServer(aFrame
, &nsStyleSVG::mFill
);
1223 RefPtr
<gfxPattern
> pattern
=
1224 ps
->GetPaintServerPattern(aFrame
, dt
, aContext
->CurrentMatrixDouble(),
1225 &nsStyleSVG::mFill
, fillOpacity
, aImgParams
);
1227 pattern
->CacheColorStops(dt
);
1228 aOutPattern
->Init(*pattern
->GetPattern(dt
));
1233 if (aContextPaint
) {
1234 RefPtr
<gfxPattern
> pattern
;
1235 switch (style
->mFill
.kind
.tag
) {
1236 case StyleSVGPaintKind::Tag::ContextFill
:
1237 pattern
= aContextPaint
->GetFillPattern(
1238 dt
, fillOpacity
, aContext
->CurrentMatrixDouble(), aImgParams
);
1240 case StyleSVGPaintKind::Tag::ContextStroke
:
1241 pattern
= aContextPaint
->GetStrokePattern(
1242 dt
, fillOpacity
, aContext
->CurrentMatrixDouble(), aImgParams
);
1247 aOutPattern
->Init(*pattern
->GetPattern(dt
));
1252 if (style
->mFill
.fallback
.IsNone()) {
1256 // On failure, use the fallback colour in case we have an
1257 // objectBoundingBox where the width or height of the object is zero.
1258 // See http://www.w3.org/TR/SVG11/coords.html#ObjectBoundingBox
1259 sRGBColor
color(sRGBColor::FromABGR(GetFallbackOrPaintColor(
1260 *aFrame
->Style(), &nsStyleSVG::mFill
, NS_RGB(0, 0, 0))));
1261 color
.a
*= fillOpacity
;
1262 aOutPattern
->InitColorPattern(ToDeviceColor(color
));
1266 void SVGUtils::MakeStrokePatternFor(nsIFrame
* aFrame
, gfxContext
* aContext
,
1267 GeneralPattern
* aOutPattern
,
1268 imgDrawingParams
& aImgParams
,
1269 SVGContextPaint
* aContextPaint
) {
1270 const nsStyleSVG
* style
= aFrame
->StyleSVG();
1271 if (style
->mStroke
.kind
.IsNone()) {
1275 const auto* styleEffects
= aFrame
->StyleEffects();
1277 float strokeOpacity
= GetOpacity(style
->mStrokeOpacity
, aContextPaint
);
1278 if (!styleEffects
->IsOpaque() && SVGUtils::CanOptimizeOpacity(aFrame
)) {
1279 // Combine the group opacity into the stroke opacity (we will have skipped
1280 // creating an offscreen surface to apply the group opacity).
1281 strokeOpacity
*= styleEffects
->mOpacity
;
1284 const DrawTarget
* dt
= aContext
->GetDrawTarget();
1286 SVGPaintServerFrame
* ps
=
1287 SVGObserverUtils::GetAndObservePaintServer(aFrame
, &nsStyleSVG::mStroke
);
1290 RefPtr
<gfxPattern
> pattern
= ps
->GetPaintServerPattern(
1291 aFrame
, dt
, aContext
->CurrentMatrixDouble(), &nsStyleSVG::mStroke
,
1292 strokeOpacity
, aImgParams
);
1294 pattern
->CacheColorStops(dt
);
1295 aOutPattern
->Init(*pattern
->GetPattern(dt
));
1300 if (aContextPaint
) {
1301 RefPtr
<gfxPattern
> pattern
;
1302 switch (style
->mStroke
.kind
.tag
) {
1303 case StyleSVGPaintKind::Tag::ContextFill
:
1304 pattern
= aContextPaint
->GetFillPattern(
1305 dt
, strokeOpacity
, aContext
->CurrentMatrixDouble(), aImgParams
);
1307 case StyleSVGPaintKind::Tag::ContextStroke
:
1308 pattern
= aContextPaint
->GetStrokePattern(
1309 dt
, strokeOpacity
, aContext
->CurrentMatrixDouble(), aImgParams
);
1314 aOutPattern
->Init(*pattern
->GetPattern(dt
));
1319 if (style
->mStroke
.fallback
.IsNone()) {
1323 // On failure, use the fallback colour in case we have an
1324 // objectBoundingBox where the width or height of the object is zero.
1325 // See http://www.w3.org/TR/SVG11/coords.html#ObjectBoundingBox
1326 sRGBColor
color(sRGBColor::FromABGR(GetFallbackOrPaintColor(
1327 *aFrame
->Style(), &nsStyleSVG::mStroke
, NS_RGBA(0, 0, 0, 0))));
1328 color
.a
*= strokeOpacity
;
1329 aOutPattern
->InitColorPattern(ToDeviceColor(color
));
1333 float SVGUtils::GetOpacity(const StyleSVGOpacity
& aOpacity
,
1334 const SVGContextPaint
* aContextPaint
) {
1335 float opacity
= 1.0f
;
1336 switch (aOpacity
.tag
) {
1337 case StyleSVGOpacity::Tag::Opacity
:
1338 return aOpacity
.AsOpacity();
1339 case StyleSVGOpacity::Tag::ContextFillOpacity
:
1340 if (aContextPaint
) {
1341 opacity
= aContextPaint
->GetFillOpacity();
1344 case StyleSVGOpacity::Tag::ContextStrokeOpacity
:
1345 if (aContextPaint
) {
1346 opacity
= aContextPaint
->GetStrokeOpacity();
1353 bool SVGUtils::HasStroke(const nsIFrame
* aFrame
,
1354 const SVGContextPaint
* aContextPaint
) {
1355 const nsStyleSVG
* style
= aFrame
->StyleSVG();
1356 return style
->HasStroke() && GetStrokeWidth(aFrame
, aContextPaint
) > 0;
1359 float SVGUtils::GetStrokeWidth(const nsIFrame
* aFrame
,
1360 const SVGContextPaint
* aContextPaint
) {
1361 nsIContent
* content
= aFrame
->GetContent();
1362 if (content
->IsText()) {
1363 content
= content
->GetParent();
1366 auto* ctx
= SVGElement::FromNode(content
);
1367 return SVGContentUtils::GetStrokeWidth(ctx
, aFrame
->Style(), aContextPaint
);
1370 void SVGUtils::SetupStrokeGeometry(nsIFrame
* aFrame
, gfxContext
* aContext
,
1371 SVGContextPaint
* aContextPaint
) {
1372 MOZ_ASSERT(aFrame
->GetContent()->IsSVGElement(), "bad cast");
1373 SVGContentUtils::AutoStrokeOptions strokeOptions
;
1374 SVGContentUtils::GetStrokeOptions(&strokeOptions
,
1375 SVGElement::FromNode(aFrame
->GetContent()),
1376 aFrame
->Style(), aContextPaint
);
1378 if (strokeOptions
.mLineWidth
<= 0) {
1382 // SVGContentUtils::GetStrokeOptions gets the stroke options in CSS px;
1383 // convert to device pixels for gfxContext.
1384 float devPxPerCSSPx
= aFrame
->PresContext()->CSSToDevPixelScale().scale
;
1386 aContext
->SetLineWidth(strokeOptions
.mLineWidth
* devPxPerCSSPx
);
1387 aContext
->SetLineCap(strokeOptions
.mLineCap
);
1388 aContext
->SetMiterLimit(strokeOptions
.mMiterLimit
);
1389 aContext
->SetLineJoin(strokeOptions
.mLineJoin
);
1390 aContext
->SetDash(strokeOptions
.mDashPattern
, strokeOptions
.mDashLength
,
1391 strokeOptions
.mDashOffset
, devPxPerCSSPx
);
1394 uint16_t SVGUtils::GetGeometryHitTestFlags(const nsIFrame
* aFrame
) {
1397 switch (aFrame
->Style()->PointerEvents()) {
1398 case StylePointerEvents::None
:
1400 case StylePointerEvents::Auto
:
1401 case StylePointerEvents::Visiblepainted
:
1402 if (aFrame
->StyleVisibility()->IsVisible()) {
1403 if (!aFrame
->StyleSVG()->mFill
.kind
.IsNone()) {
1404 flags
= SVG_HIT_TEST_FILL
;
1406 if (!aFrame
->StyleSVG()->mStroke
.kind
.IsNone()) {
1407 flags
|= SVG_HIT_TEST_STROKE
;
1411 case StylePointerEvents::Visiblefill
:
1412 if (aFrame
->StyleVisibility()->IsVisible()) {
1413 flags
= SVG_HIT_TEST_FILL
;
1416 case StylePointerEvents::Visiblestroke
:
1417 if (aFrame
->StyleVisibility()->IsVisible()) {
1418 flags
= SVG_HIT_TEST_STROKE
;
1421 case StylePointerEvents::Visible
:
1422 if (aFrame
->StyleVisibility()->IsVisible()) {
1423 flags
= SVG_HIT_TEST_FILL
| SVG_HIT_TEST_STROKE
;
1426 case StylePointerEvents::Painted
:
1427 if (!aFrame
->StyleSVG()->mFill
.kind
.IsNone()) {
1428 flags
= SVG_HIT_TEST_FILL
;
1430 if (!aFrame
->StyleSVG()->mStroke
.kind
.IsNone()) {
1431 flags
|= SVG_HIT_TEST_STROKE
;
1434 case StylePointerEvents::Fill
:
1435 flags
= SVG_HIT_TEST_FILL
;
1437 case StylePointerEvents::Stroke
:
1438 flags
= SVG_HIT_TEST_STROKE
;
1440 case StylePointerEvents::All
:
1441 flags
= SVG_HIT_TEST_FILL
| SVG_HIT_TEST_STROKE
;
1444 NS_ERROR("not reached");
1451 void SVGUtils::PaintSVGGlyph(Element
* aElement
, gfxContext
* aContext
) {
1452 nsIFrame
* frame
= aElement
->GetPrimaryFrame();
1453 ISVGDisplayableFrame
* svgFrame
= do_QueryFrame(frame
);
1458 if (frame
->GetContent()->IsSVGElement()) {
1459 // PaintSVG() expects the passed transform to be the transform to its own
1460 // SVG user space, so we need to account for any 'transform' attribute:
1461 m
= SVGUtils::GetTransformMatrixInUserSpace(frame
);
1464 // SVG-in-OpenType is not allowed to paint external resources, so we can
1465 // just pass a dummy params into PatintSVG.
1466 imgDrawingParams dummy
;
1467 svgFrame
->PaintSVG(*aContext
, m
, dummy
);
1470 bool SVGUtils::GetSVGGlyphExtents(const Element
* aElement
,
1471 const gfxMatrix
& aSVGToAppSpace
,
1473 nsIFrame
* frame
= aElement
->GetPrimaryFrame();
1474 ISVGDisplayableFrame
* svgFrame
= do_QueryFrame(frame
);
1479 gfxMatrix transform
= aSVGToAppSpace
;
1480 if (auto* svg
= SVGElement::FromNode(frame
->GetContent())) {
1481 transform
= svg
->ChildToUserSpaceTransform() * transform
;
1486 ->GetBBoxContribution(gfx::ToMatrix(transform
),
1487 SVGUtils::eBBoxIncludeFill
|
1488 SVGUtils::eBBoxIncludeFillGeometry
|
1489 SVGUtils::eBBoxIncludeStroke
|
1490 SVGUtils::eBBoxIncludeStrokeGeometry
|
1491 SVGUtils::eBBoxIncludeMarkers
)
1496 nsRect
SVGUtils::ToCanvasBounds(const gfxRect
& aUserspaceRect
,
1497 const gfxMatrix
& aToCanvas
,
1498 const nsPresContext
* presContext
) {
1499 return nsLayoutUtils::RoundGfxRectToAppRect(
1500 aToCanvas
.TransformBounds(aUserspaceRect
),
1501 presContext
->AppUnitsPerDevPixel());
1504 gfxMatrix
SVGUtils::GetCSSPxToDevPxMatrix(const nsIFrame
* aNonSVGFrame
) {
1505 float devPxPerCSSPx
= aNonSVGFrame
->PresContext()->CSSToDevPixelScale().scale
;
1507 return gfxMatrix(devPxPerCSSPx
, 0.0, 0.0, devPxPerCSSPx
, 0.0, 0.0);
1510 gfxMatrix
SVGUtils::GetTransformMatrixInUserSpace(const nsIFrame
* aFrame
) {
1511 // We check element instead of aFrame directly because SVG element
1512 // may have non-SVG frame, <tspan> for example.
1513 MOZ_ASSERT(aFrame
->GetContent() && aFrame
->GetContent()->IsSVGElement(),
1514 "Only use this wrapper for SVG elements");
1516 if (!aFrame
->IsTransformed()) {
1520 nsStyleTransformMatrix::TransformReferenceBox
refBox(aFrame
);
1521 nsDisplayTransform::FrameTransformProperties properties
{
1522 aFrame
, refBox
, AppUnitsPerCSSPixel()};
1524 // SVG elements can have x/y offset, their default transform origin
1525 // is the origin of user space, not the top left point of the frame.
1526 Point3D svgTransformOrigin
{
1527 properties
.mToTransformOrigin
.x
- CSSPixel::FromAppUnits(refBox
.X()),
1528 properties
.mToTransformOrigin
.y
- CSSPixel::FromAppUnits(refBox
.Y()),
1529 properties
.mToTransformOrigin
.z
};
1531 Matrix svgTransform
;
1533 if (properties
.HasTransform()) {
1534 trans
= nsStyleTransformMatrix::ReadTransforms(
1535 properties
.mTranslate
, properties
.mRotate
, properties
.mScale
,
1536 properties
.mMotion
.ptrOr(nullptr), properties
.mTransform
, refBox
,
1537 AppUnitsPerCSSPixel());
1540 trans
.ChangeBasis(svgTransformOrigin
);
1543 trans
.ProjectTo2D();
1544 (void)trans
.CanDraw2D(&mm
);
1546 return ThebesMatrix(mm
);
1549 } // namespace mozilla