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/StaticPrefs_svg.h"
38 #include "mozilla/SVGClipPathFrame.h"
39 #include "mozilla/SVGContainerFrame.h"
40 #include "mozilla/SVGContentUtils.h"
41 #include "mozilla/SVGContextPaint.h"
42 #include "mozilla/SVGForeignObjectFrame.h"
43 #include "mozilla/SVGIntegrationUtils.h"
44 #include "mozilla/SVGGeometryFrame.h"
45 #include "mozilla/SVGMaskFrame.h"
46 #include "mozilla/SVGObserverUtils.h"
47 #include "mozilla/SVGOuterSVGFrame.h"
48 #include "mozilla/SVGTextFrame.h"
49 #include "mozilla/Unused.h"
50 #include "mozilla/gfx/2D.h"
51 #include "mozilla/gfx/PatternHelpers.h"
52 #include "mozilla/dom/Document.h"
53 #include "mozilla/dom/SVGClipPathElement.h"
54 #include "mozilla/dom/SVGGeometryElement.h"
55 #include "mozilla/dom/SVGPathElement.h"
56 #include "mozilla/dom/SVGUnitTypesBinding.h"
57 #include "mozilla/dom/SVGViewportElement.h"
59 using namespace mozilla::dom
;
60 using namespace mozilla::dom::SVGUnitTypes_Binding
;
61 using namespace mozilla::gfx
;
62 using namespace mozilla::image
;
64 bool NS_SVGNewGetBBoxEnabled() {
65 return mozilla::StaticPrefs::svg_new_getBBox_enabled();
70 // we only take the address of this:
71 static gfx::UserDataKey sSVGAutoRenderStateKey
;
73 SVGAutoRenderState::SVGAutoRenderState(DrawTarget
* aDrawTarget
)
74 : mDrawTarget(aDrawTarget
),
75 mOriginalRenderState(nullptr),
76 mPaintingToWindow(false) {
77 mOriginalRenderState
= aDrawTarget
->RemoveUserData(&sSVGAutoRenderStateKey
);
78 // We always remove ourselves from aContext before it dies, so
79 // passing nullptr as the destroy function is okay.
80 aDrawTarget
->AddUserData(&sSVGAutoRenderStateKey
, this, nullptr);
83 SVGAutoRenderState::~SVGAutoRenderState() {
84 mDrawTarget
->RemoveUserData(&sSVGAutoRenderStateKey
);
85 if (mOriginalRenderState
) {
86 mDrawTarget
->AddUserData(&sSVGAutoRenderStateKey
, mOriginalRenderState
,
91 void SVGAutoRenderState::SetPaintingToWindow(bool aPaintingToWindow
) {
92 mPaintingToWindow
= aPaintingToWindow
;
96 bool SVGAutoRenderState::IsPaintingToWindow(DrawTarget
* aDrawTarget
) {
97 void* state
= aDrawTarget
->GetUserData(&sSVGAutoRenderStateKey
);
99 return static_cast<SVGAutoRenderState
*>(state
)->mPaintingToWindow
;
104 // Unlike containers, leaf frames do not include GetPosition() in
106 static bool FrameDoesNotIncludePositionInTM(const nsIFrame
* aFrame
) {
107 return aFrame
->IsSVGGeometryFrame() || aFrame
->IsSVGImageFrame() ||
108 aFrame
->IsInSVGTextSubtree();
111 nsRect
SVGUtils::GetPostFilterInkOverflowRect(nsIFrame
* aFrame
,
112 const nsRect
& aPreFilterRect
) {
113 MOZ_ASSERT(aFrame
->HasAnyStateBits(NS_FRAME_SVG_LAYOUT
),
114 "Called on invalid frame type");
116 // Note: we do not return here for eHasNoRefs since we must still handle any
117 // CSS filter functions.
118 // in that case we disable painting of the element.
119 nsTArray
<SVGFilterFrame
*> filterFrames
;
120 if (!aFrame
->StyleEffects()->HasFilters() ||
121 SVGObserverUtils::GetAndObserveFilters(aFrame
, &filterFrames
) ==
122 SVGObserverUtils::eHasRefsSomeInvalid
) {
123 return aPreFilterRect
;
126 return FilterInstance::GetPostFilterBounds(aFrame
, filterFrames
, nullptr,
128 .valueOr(aPreFilterRect
);
131 bool SVGUtils::OuterSVGIsCallingReflowSVG(nsIFrame
* aFrame
) {
132 return GetOuterSVGFrame(aFrame
)->IsCallingReflowSVG();
135 bool SVGUtils::AnyOuterSVGIsCallingReflowSVG(nsIFrame
* aFrame
) {
136 SVGOuterSVGFrame
* outer
= GetOuterSVGFrame(aFrame
);
138 if (outer
->IsCallingReflowSVG()) {
141 outer
= GetOuterSVGFrame(outer
->GetParent());
146 void SVGUtils::ScheduleReflowSVG(nsIFrame
* aFrame
) {
147 MOZ_ASSERT(aFrame
->IsFrameOfType(nsIFrame::eSVG
), "Passed bad frame!");
149 // If this is triggered, the callers should be fixed to call us before
150 // ReflowSVG is called. If we try to mark dirty bits on frames while we're
151 // in the process of removing them, things will get messed up.
152 MOZ_ASSERT(!OuterSVGIsCallingReflowSVG(aFrame
),
153 "Do not call under ISVGDisplayableFrame::ReflowSVG!");
155 // We don't call SVGObserverUtils::InvalidateRenderingObservers here because
156 // we should only be called under InvalidateAndScheduleReflowSVG (which
157 // calls InvalidateBounds) or SVGDisplayContainerFrame::InsertFrames
158 // (at which point the frame has no observers).
160 if (aFrame
->HasAnyStateBits(NS_FRAME_IS_NONDISPLAY
)) {
164 if (aFrame
->HasAnyStateBits(NS_FRAME_IS_DIRTY
| NS_FRAME_FIRST_REFLOW
)) {
165 // Nothing to do if we're already dirty, or if the outer-<svg>
166 // hasn't yet had its initial reflow.
170 SVGOuterSVGFrame
* outerSVGFrame
= nullptr;
172 // We must not add dirty bits to the SVGOuterSVGFrame or else
173 // PresShell::FrameNeedsReflow won't work when we pass it in below.
174 if (aFrame
->IsSVGOuterSVGFrame()) {
175 outerSVGFrame
= static_cast<SVGOuterSVGFrame
*>(aFrame
);
177 aFrame
->MarkSubtreeDirty();
179 nsIFrame
* f
= aFrame
->GetParent();
180 while (f
&& !f
->IsSVGOuterSVGFrame()) {
181 if (f
->HasAnyStateBits(NS_FRAME_IS_DIRTY
| NS_FRAME_HAS_DIRTY_CHILDREN
)) {
184 f
->AddStateBits(NS_FRAME_HAS_DIRTY_CHILDREN
);
186 MOZ_ASSERT(f
->IsFrameOfType(nsIFrame::eSVG
),
187 "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
->IsFrameOfType(nsIFrame::eSVG
),
213 "SVG uses bits differently!");
215 // The flags we test here may change, hence why we have this separate
217 return aFrame
->IsSubtreeDirty();
220 Size
SVGUtils::GetContextSize(const nsIFrame
* aFrame
) {
223 MOZ_ASSERT(aFrame
->GetContent()->IsSVGElement(), "bad cast");
224 const SVGElement
* element
= static_cast<SVGElement
*>(aFrame
->GetContent());
226 SVGViewportElement
* ctx
= element
->GetCtx();
228 size
.width
= ctx
->GetLength(SVGContentUtils::X
);
229 size
.height
= ctx
->GetLength(SVGContentUtils::Y
);
234 float SVGUtils::ObjectSpace(const gfxRect
& aRect
,
235 const SVGAnimatedLength
* aLength
) {
238 switch (aLength
->GetCtxType()) {
239 case SVGContentUtils::X
:
240 axis
= aRect
.Width();
242 case SVGContentUtils::Y
:
243 axis
= aRect
.Height();
245 case SVGContentUtils::XY
:
246 axis
= float(SVGContentUtils::ComputeNormalizedHypotenuse(
247 aRect
.Width(), aRect
.Height()));
250 MOZ_ASSERT_UNREACHABLE("unexpected ctx type");
254 if (aLength
->IsPercentage()) {
255 // Multiply first to avoid precision errors:
256 return axis
* aLength
->GetAnimValInSpecifiedUnits() / 100;
258 return aLength
->GetAnimValue(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
->GetAnimValue(aNonSVGContext
);
268 float SVGUtils::UserSpace(const UserSpaceMetrics
& aMetrics
,
269 const SVGAnimatedLength
* aLength
) {
270 return aLength
->GetAnimValue(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
)) {
290 *aRect
= nsRect(0, 0, 0, 0);
292 uint32_t flags
= SVGUtils::eForGetClientRects
| SVGUtils::eBBoxIncludeFill
|
293 SVGUtils::eBBoxIncludeStroke
|
294 SVGUtils::eBBoxIncludeMarkers
|
295 SVGUtils::eUseUserSpaceOfUseElement
;
297 auto ctm
= nsLayoutUtils::GetTransformToAncestor(RelativeTo
{aFrame
},
300 float initPositionX
= NSAppUnitsToFloatPixels(aFrame
->GetPosition().x
,
301 AppUnitsPerCSSPixel()),
302 initPositionY
= NSAppUnitsToFloatPixels(aFrame
->GetPosition().y
,
303 AppUnitsPerCSSPixel());
308 gfxMatrix m
= ThebesMatrix(mm
);
310 float appUnitsPerDevPixel
= aFrame
->PresContext()->AppUnitsPerDevPixel();
311 float devPixelPerCSSPixel
=
312 float(AppUnitsPerCSSPixel()) / appUnitsPerDevPixel
;
314 // The matrix that GetBBox accepts should operate on "user space",
315 // i.e. with CSS pixel unit.
316 m
= m
.PreScale(devPixelPerCSSPixel
, devPixelPerCSSPixel
);
318 // Both SVGUtils::GetBBox and nsLayoutUtils::GetTransformToAncestor
319 // will count this displacement, we should remove it here to avoid
321 m
= m
.PreTranslate(-initPositionX
, -initPositionY
);
323 gfxRect bbox
= SVGUtils::GetBBox(aFrame
, flags
, &m
);
324 *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()->IsFrameOfType(nsIFrame::eSVGContainer
));
347 auto* parent
= static_cast<SVGContainerFrame
*>(aFrame
->GetParent());
348 auto* content
= static_cast<SVGElement
*>(aFrame
->GetContent());
350 return content
->PrependLocalTransformsTo(parent
->GetCanvasTM());
353 bool SVGUtils::IsSVGTransformed(const nsIFrame
* aFrame
,
354 gfx::Matrix
* aOwnTransform
,
355 gfx::Matrix
* aFromParentTransform
) {
356 MOZ_ASSERT(aFrame
->HasAllStateBits(NS_FRAME_SVG_LAYOUT
|
357 NS_FRAME_MAY_BE_TRANSFORMED
),
358 "Expecting an SVG frame that can be transformed");
359 bool foundTransform
= false;
361 // Check if our parent has children-only transforms:
362 if (SVGContainerFrame
* parent
= do_QueryFrame(aFrame
->GetParent())) {
363 foundTransform
= parent
->HasChildrenOnlyTransform(aFromParentTransform
);
366 if (auto* content
= SVGElement::FromNode(aFrame
->GetContent())) {
367 auto* transformList
= content
->GetAnimatedTransformList();
368 if ((transformList
&& transformList
->HasTransform()) ||
369 content
->GetAnimateMotionTransform()) {
371 *aOwnTransform
= gfx::ToMatrix(
372 content
->PrependLocalTransformsTo(gfxMatrix(), eUserSpaceToParent
));
374 foundTransform
= true;
377 return foundTransform
;
380 void SVGUtils::NotifyChildrenOfSVGChange(nsIFrame
* aFrame
, uint32_t aFlags
) {
381 for (nsIFrame
* kid
: aFrame
->PrincipalChildList()) {
382 ISVGDisplayableFrame
* SVGFrame
= do_QueryFrame(kid
);
384 SVGFrame
->NotifySVGChanged(aFlags
);
387 kid
->IsFrameOfType(nsIFrame::eSVG
) || kid
->IsInSVGTextSubtree(),
388 "SVG frame expected");
389 // recurse into the children of container frames e.g. <clipPath>, <mask>
390 // in case they have child frames with transformation matrices
391 if (kid
->IsFrameOfType(nsIFrame::eSVG
)) {
392 NotifyChildrenOfSVGChange(kid
, aFlags
);
398 // ************************************************************
400 float SVGUtils::ComputeOpacity(const nsIFrame
* aFrame
, bool aHandleOpacity
) {
401 const auto* styleEffects
= aFrame
->StyleEffects();
403 if (!styleEffects
->IsOpaque() &&
404 (SVGUtils::CanOptimizeOpacity(aFrame
) || !aHandleOpacity
)) {
408 return styleEffects
->mOpacity
;
411 SVGUtils::MaskUsage
SVGUtils::DetermineMaskUsage(const nsIFrame
* aFrame
,
412 bool aHandleOpacity
) {
415 using ClipPathType
= StyleClipPath::Tag
;
417 usage
.mOpacity
= ComputeOpacity(aFrame
, aHandleOpacity
);
419 nsIFrame
* firstFrame
=
420 nsLayoutUtils::FirstContinuationOrIBSplitSibling(aFrame
);
422 const nsStyleSVGReset
* svgReset
= firstFrame
->StyleSVGReset();
424 if (SVGObserverUtils::GetAndObserveMasks(firstFrame
, nullptr) !=
425 SVGObserverUtils::eHasNoRefs
) {
426 usage
.mShouldGenerateMaskLayer
= true;
429 SVGClipPathFrame
* clipPathFrame
;
430 // XXX check return value?
431 SVGObserverUtils::GetAndObserveClipPath(firstFrame
, &clipPathFrame
);
432 MOZ_ASSERT(!clipPathFrame
|| svgReset
->mClipPath
.IsUrl());
434 switch (svgReset
->mClipPath
.tag
) {
435 case ClipPathType::Url
:
437 if (clipPathFrame
->IsTrivial()) {
438 usage
.mShouldApplyClipPath
= true;
440 usage
.mShouldGenerateClipMaskLayer
= true;
444 case ClipPathType::Shape
: {
445 usage
.mShouldApplyBasicShapeOrPath
= true;
446 const auto& shape
= svgReset
->mClipPath
.AsShape()._0
;
447 usage
.mIsSimpleClipShape
=
448 !usage
.mShouldGenerateMaskLayer
&&
449 (shape
->IsRect() || shape
->IsCircle() || shape
->IsEllipse());
452 case ClipPathType::Box
:
453 usage
.mShouldApplyBasicShapeOrPath
= true;
455 case ClipPathType::None
:
456 MOZ_ASSERT(!usage
.mShouldGenerateClipMaskLayer
&&
457 !usage
.mShouldApplyClipPath
&&
458 !usage
.mShouldApplyBasicShapeOrPath
);
461 MOZ_ASSERT_UNREACHABLE("Unsupported clip-path type.");
467 class MixModeBlender
{
469 using Factory
= gfx::Factory
;
471 MixModeBlender(nsIFrame
* aFrame
, gfxContext
* aContext
)
472 : mFrame(aFrame
), mSourceCtx(aContext
) {
473 MOZ_ASSERT(mFrame
&& mSourceCtx
);
476 bool ShouldCreateDrawTargetForBlend() const {
477 return mFrame
->StyleEffects()->HasMixBlendMode();
480 gfxContext
* CreateBlendTarget(const gfxMatrix
& aTransform
) {
481 MOZ_ASSERT(ShouldCreateDrawTargetForBlend());
483 // Create a temporary context to draw to so we can blend it back with
485 IntRect drawRect
= ComputeClipExtsInDeviceSpace(aTransform
);
486 if (drawRect
.IsEmpty()) {
490 RefPtr
<DrawTarget
> targetDT
=
491 mSourceCtx
->GetDrawTarget()->CreateSimilarDrawTarget(
492 drawRect
.Size(), SurfaceFormat::B8G8R8A8
);
493 if (!targetDT
|| !targetDT
->IsValid()) {
497 MOZ_ASSERT(!mTargetCtx
,
498 "CreateBlendTarget is designed to be used once only.");
500 mTargetCtx
= gfxContext::CreateOrNull(targetDT
);
501 MOZ_ASSERT(mTargetCtx
); // already checked the draw target above
502 mTargetCtx
->SetMatrix(mSourceCtx
->CurrentMatrix() *
503 Matrix::Translation(-drawRect
.TopLeft()));
505 mTargetOffset
= drawRect
.TopLeft();
507 return mTargetCtx
.get();
510 void BlendToTarget() {
511 MOZ_ASSERT(ShouldCreateDrawTargetForBlend());
512 MOZ_ASSERT(mTargetCtx
,
513 "BlendToTarget should be used after CreateBlendTarget.");
515 RefPtr
<SourceSurface
> targetSurf
= mTargetCtx
->GetDrawTarget()->Snapshot();
517 gfxContextAutoSaveRestore
save(mSourceCtx
);
518 mSourceCtx
->SetMatrix(Matrix()); // This will be restored right after.
519 RefPtr
<gfxPattern
> pattern
= new gfxPattern(
520 targetSurf
, Matrix::Translation(mTargetOffset
.x
, mTargetOffset
.y
));
521 mSourceCtx
->SetPattern(pattern
);
526 MixModeBlender() = delete;
528 IntRect
ComputeClipExtsInDeviceSpace(const gfxMatrix
& aTransform
) {
529 // These are used if we require a temporary surface for a custom blend
530 // mode. Clip the source context first, so that we can generate a smaller
531 // temporary surface. (Since we will clip this context in
532 // SetupContextMatrix, a pair of save/restore is needed.)
533 gfxContextAutoSaveRestore saver
;
535 if (!mFrame
->HasAnyStateBits(NS_FRAME_IS_NONDISPLAY
)) {
536 saver
.SetContext(mSourceCtx
);
537 // aFrame has a valid ink overflow rect, so clip to it before calling
538 // PushGroup() to minimize the size of the surfaces we'll composite:
539 gfxContextMatrixAutoSaveRestore
matrixAutoSaveRestore(mSourceCtx
);
540 mSourceCtx
->Multiply(aTransform
);
541 nsRect overflowRect
= mFrame
->InkOverflowRectRelativeToSelf();
542 if (FrameDoesNotIncludePositionInTM(mFrame
)) {
543 overflowRect
= overflowRect
+ mFrame
->GetPosition();
545 mSourceCtx
->Clip(NSRectToSnappedRect(
546 overflowRect
, mFrame
->PresContext()->AppUnitsPerDevPixel(),
547 *mSourceCtx
->GetDrawTarget()));
550 // Get the clip extents in device space.
551 gfxRect clippedFrameSurfaceRect
=
552 mSourceCtx
->GetClipExtents(gfxContext::eDeviceSpace
);
553 clippedFrameSurfaceRect
.RoundOut();
556 ToRect(clippedFrameSurfaceRect
).ToIntRect(&result
);
558 return Factory::CheckSurfaceSize(result
.Size()) ? result
: IntRect();
562 gfxContext
* mSourceCtx
;
563 UniquePtr
<gfxContext
> mTargetCtx
;
564 IntPoint mTargetOffset
;
567 void SVGUtils::PaintFrameWithEffects(nsIFrame
* aFrame
, gfxContext
& aContext
,
568 const gfxMatrix
& aTransform
,
569 imgDrawingParams
& aImgParams
) {
570 NS_ASSERTION(aFrame
->HasAnyStateBits(NS_FRAME_IS_NONDISPLAY
) ||
571 aFrame
->PresContext()->Document()->IsSVGGlyphsDocument(),
572 "Only painting of non-display SVG should take this code path");
574 ISVGDisplayableFrame
* svgFrame
= do_QueryFrame(aFrame
);
579 MaskUsage maskUsage
= DetermineMaskUsage(aFrame
, true);
580 if (maskUsage
.IsTransparent()) {
584 if (auto* svg
= SVGElement::FromNode(aFrame
->GetContent())) {
585 if (!svg
->HasValidDimensions()) {
590 /* SVG defines the following rendering model:
596 * 5. Apply clipping, masking, group opacity
598 * We follow this, but perform a couple of optimizations:
600 * + Use cairo's clipPath when representable natively (single object
603 * + Merge opacity and masking if both used together.
606 /* Properties are added lazily and may have been removed by a restyle,
607 so make sure all applicable ones are set again. */
608 SVGClipPathFrame
* clipPathFrame
;
609 nsTArray
<SVGMaskFrame
*> maskFrames
;
610 nsTArray
<SVGFilterFrame
*> filterFrames
;
611 const bool hasInvalidFilter
=
612 SVGObserverUtils::GetAndObserveFilters(aFrame
, &filterFrames
) ==
613 SVGObserverUtils::eHasRefsSomeInvalid
;
614 SVGObserverUtils::GetAndObserveClipPath(aFrame
, &clipPathFrame
);
615 SVGObserverUtils::GetAndObserveMasks(aFrame
, &maskFrames
);
617 SVGMaskFrame
* maskFrame
= maskFrames
.IsEmpty() ? nullptr : maskFrames
[0];
619 MixModeBlender
blender(aFrame
, &aContext
);
620 gfxContext
* target
= blender
.ShouldCreateDrawTargetForBlend()
621 ? blender
.CreateBlendTarget(aTransform
)
628 /* Check if we need to do additional operations on this child's
629 * rendering, which necessitates rendering into another surface. */
630 bool shouldPushMask
= false;
632 if (maskUsage
.ShouldGenerateMask()) {
633 RefPtr
<SourceSurface
> maskSurface
;
635 // maskFrame can be nullptr even if maskUsage.ShouldGenerateMaskLayer() is
636 // true. That happens when a user gives an unresolvable mask-id, such as
638 // mask:url(#id-which-does-not-exist)
639 // Since we only uses SVGUtils with SVG elements, not like mask on an
640 // HTML element, we should treat an unresolvable mask as no-mask here.
641 if (maskUsage
.ShouldGenerateMaskLayer() && maskFrame
) {
642 StyleMaskMode maskMode
=
643 aFrame
->StyleSVGReset()->mMask
.mLayers
[0].mMaskMode
;
644 SVGMaskFrame::MaskParams
params(aContext
.GetDrawTarget(), aFrame
,
645 aTransform
, maskUsage
.Opacity(), maskMode
,
648 maskSurface
= maskFrame
->GetMaskForMaskedFrame(params
);
651 // Either entire surface is clipped out, or gfx buffer allocation
652 // failure in SVGMaskFrame::GetMaskForMaskedFrame.
655 shouldPushMask
= true;
658 if (maskUsage
.ShouldGenerateClipMaskLayer()) {
659 RefPtr
<SourceSurface
> clipMaskSurface
=
660 clipPathFrame
->GetClipMask(aContext
, aFrame
, aTransform
, maskSurface
);
661 if (clipMaskSurface
) {
662 maskSurface
= clipMaskSurface
;
664 // Either entire surface is clipped out, or gfx buffer allocation
665 // failure in SVGClipPathFrame::GetClipMask.
668 shouldPushMask
= true;
671 if (!maskUsage
.ShouldGenerateLayer()) {
672 shouldPushMask
= true;
675 // SVG mask multiply opacity into maskSurface already, so we do not bother
676 // to apply opacity again.
677 if (shouldPushMask
) {
678 // We want the mask to be untransformed so use the inverse of the
679 // current transform as the maskTransform to compensate.
680 Matrix maskTransform
= aContext
.CurrentMatrix();
681 maskTransform
.Invert();
682 target
->PushGroupForBlendBack(gfxContentType::COLOR_ALPHA
,
683 maskFrame
? 1.0f
: maskUsage
.Opacity(),
684 maskSurface
, maskTransform
);
688 /* If this frame has only a trivial clipPath, set up cairo's clipping now so
689 * we can just do normal painting and get it clipped appropriately.
691 if (maskUsage
.ShouldApplyClipPath() ||
692 maskUsage
.ShouldApplyBasicShapeOrPath()) {
693 if (maskUsage
.ShouldApplyClipPath()) {
694 clipPathFrame
->ApplyClipPath(aContext
, aFrame
, aTransform
);
696 CSSClipPathInstance::ApplyBasicShapeOrPathClip(aContext
, aFrame
,
701 /* Paint the child */
703 // Invalid filters should render the unfiltered contents per spec.
704 if (aFrame
->StyleEffects()->HasFilters() && !hasInvalidFilter
) {
705 gfxContextMatrixAutoSaveRestore
autoSR(target
);
707 // 'target' is currently scaled such that its user space units are CSS
708 // pixels (SVG user space units). But PaintFilteredFrame expects it to be
709 // scaled in such a way that its user space units are device pixels. So we
710 // have to adjust the scale.
711 gfxMatrix reverseScaleMatrix
= SVGUtils::GetCSSPxToDevPxMatrix(aFrame
);
712 DebugOnly
<bool> invertible
= reverseScaleMatrix
.Invert();
713 target
->SetMatrixDouble(reverseScaleMatrix
* aTransform
*
714 target
->CurrentMatrixDouble());
716 auto callback
= [&](gfxContext
& aContext
, imgDrawingParams
& aImgParams
,
717 const gfxMatrix
* aFilterTransform
,
718 const nsIntRect
* aDirtyRect
) {
719 svgFrame
->PaintSVG(aContext
,
721 ? SVGUtils::GetCSSPxToDevPxMatrix(aFrame
)
725 // If we're masking a userSpaceOnUse mask we may need to include the
726 // stroke too. Err on the side of caution and include it always.
727 gfxRect bbox
= GetBBox(aFrame
, SVGUtils::eUseFrameBoundsForOuterSVG
|
728 SVGUtils::eBBoxIncludeFillGeometry
|
729 SVGUtils::eBBoxIncludeStroke
);
730 FilterInstance::PaintFilteredFrame(
731 aFrame
, aFrame
->StyleEffects()->mFilters
.AsSpan(), filterFrames
, target
,
732 callback
, nullptr, aImgParams
, 1.0f
, &bbox
);
734 svgFrame
->PaintSVG(*target
, aTransform
, aImgParams
);
737 if (maskUsage
.ShouldApplyClipPath() ||
738 maskUsage
.ShouldApplyBasicShapeOrPath()) {
742 if (shouldPushMask
) {
743 target
->PopGroupAndBlend();
746 if (blender
.ShouldCreateDrawTargetForBlend()) {
747 MOZ_ASSERT(target
!= &aContext
);
748 blender
.BlendToTarget();
752 bool SVGUtils::HitTestClip(nsIFrame
* aFrame
, const gfxPoint
& aPoint
) {
753 const nsStyleSVGReset
* svgReset
= aFrame
->StyleSVGReset();
754 if (!svgReset
->HasClipPath()) {
757 if (svgReset
->mClipPath
.IsUrl()) {
758 // If the clip-path property references non-existent or invalid clipPath
759 // element(s) we ignore it.
760 SVGClipPathFrame
* clipPathFrame
;
761 SVGObserverUtils::GetAndObserveClipPath(aFrame
, &clipPathFrame
);
762 return !clipPathFrame
||
763 clipPathFrame
->PointIsInsideClipPath(aFrame
, aPoint
);
765 return CSSClipPathInstance::HitTestBasicShapeOrPathClip(aFrame
, aPoint
);
768 IntSize
SVGUtils::ConvertToSurfaceSize(const gfxSize
& aSize
,
769 bool* aResultOverflows
) {
770 IntSize
surfaceSize(ClampToInt(ceil(aSize
.width
)),
771 ClampToInt(ceil(aSize
.height
)));
773 *aResultOverflows
= surfaceSize
.width
!= ceil(aSize
.width
) ||
774 surfaceSize
.height
!= ceil(aSize
.height
);
776 if (!Factory::AllowedSurfaceSize(surfaceSize
)) {
778 std::min(NS_SVG_OFFSCREEN_MAX_DIMENSION
, surfaceSize
.width
);
780 std::min(NS_SVG_OFFSCREEN_MAX_DIMENSION
, surfaceSize
.height
);
781 *aResultOverflows
= true;
787 bool SVGUtils::HitTestRect(const gfx::Matrix
& aMatrix
, float aRX
, float aRY
,
788 float aRWidth
, float aRHeight
, float aX
, float aY
) {
789 gfx::Rect
rect(aRX
, aRY
, aRWidth
, aRHeight
);
790 if (rect
.IsEmpty() || aMatrix
.IsSingular()) {
793 gfx::Matrix toRectSpace
= aMatrix
;
794 toRectSpace
.Invert();
795 gfx::Point p
= toRectSpace
.TransformPoint(gfx::Point(aX
, aY
));
796 return rect
.x
<= p
.x
&& p
.x
<= rect
.XMost() && rect
.y
<= p
.y
&&
800 gfxRect
SVGUtils::GetClipRectForFrame(const nsIFrame
* aFrame
, float aX
,
801 float aY
, float aWidth
, float aHeight
) {
802 const nsStyleDisplay
* disp
= aFrame
->StyleDisplay();
803 const nsStyleEffects
* effects
= aFrame
->StyleEffects();
805 bool clipApplies
= disp
->mOverflowX
== StyleOverflow::Hidden
||
806 disp
->mOverflowY
== StyleOverflow::Hidden
;
808 if (!clipApplies
|| effects
->mClip
.IsAuto()) {
809 return gfxRect(aX
, aY
, aWidth
, aHeight
);
812 const auto& rect
= effects
->mClip
.AsRect();
813 nsRect coordClipRect
= rect
.ToLayoutRect();
814 nsIntRect clipPxRect
= coordClipRect
.ToOutsidePixels(
815 aFrame
->PresContext()->AppUnitsPerDevPixel());
817 gfxRect(clipPxRect
.x
, clipPxRect
.y
, clipPxRect
.width
, clipPxRect
.height
);
818 if (rect
.right
.IsAuto()) {
819 clipRect
.width
= aWidth
- clipRect
.X();
821 if (rect
.bottom
.IsAuto()) {
822 clipRect
.height
= aHeight
- clipRect
.Y();
824 if (disp
->mOverflowX
!= StyleOverflow::Hidden
) {
826 clipRect
.width
= aWidth
;
828 if (disp
->mOverflowY
!= StyleOverflow::Hidden
) {
830 clipRect
.height
= aHeight
;
835 gfxRect
SVGUtils::GetBBox(nsIFrame
* aFrame
, uint32_t aFlags
,
836 const gfxMatrix
* aToBoundsSpace
) {
837 if (aFrame
->IsTextFrame()) {
838 aFrame
= aFrame
->GetParent();
841 if (aFrame
->IsInSVGTextSubtree()) {
842 // It is possible to apply a gradient, pattern, clipping path, mask or
843 // filter to text. When one of these facilities is applied to text
844 // the bounding box is the entire text element in all cases.
846 nsLayoutUtils::GetClosestFrameOfType(aFrame
, LayoutFrameType::SVGText
);
849 ISVGDisplayableFrame
* svg
= do_QueryFrame(aFrame
);
850 const bool hasSVGLayout
= aFrame
->HasAnyStateBits(NS_FRAME_SVG_LAYOUT
);
851 if (hasSVGLayout
&& !svg
) {
852 // An SVG frame, but not one that can be displayed directly (for
853 // example, nsGradientFrame). These can't contribute to the bbox.
857 const bool isOuterSVG
= svg
&& !hasSVGLayout
;
858 MOZ_ASSERT(!isOuterSVG
|| aFrame
->IsSVGOuterSVGFrame());
859 if (!svg
|| (isOuterSVG
&& (aFlags
& eUseFrameBoundsForOuterSVG
))) {
860 // An HTML element or an SVG outer frame.
861 MOZ_ASSERT(!hasSVGLayout
);
862 bool onlyCurrentFrame
= aFlags
& eIncludeOnlyCurrentFrameForNonSVGElement
;
863 return SVGIntegrationUtils::GetSVGBBoxForNonSVGFrame(
865 /* aUnionContinuations = */ !onlyCurrentFrame
);
870 if (auto* element
= SVGElement::FromNodeOrNull(aFrame
->GetContent())) {
871 if (!element
->HasValidDimensions()) {
876 // Clean out flags which have no effects on returning bbox from now, so that
877 // we can cache and reuse ObjectBoundingBoxProperty() in the code below.
879 ~(eIncludeOnlyCurrentFrameForNonSVGElement
| eUseFrameBoundsForOuterSVG
);
880 if (!aFrame
->IsSVGUseFrame()) {
881 aFlags
&= ~eUseUserSpaceOfUseElement
;
884 if (aFlags
== eBBoxIncludeFillGeometry
&&
885 // We only cache bbox in element's own user space
887 gfxRect
* prop
= aFrame
->GetProperty(ObjectBoundingBoxProperty());
894 if (aToBoundsSpace
) {
895 matrix
= *aToBoundsSpace
;
898 if (aFrame
->IsSVGForeignObjectFrame() ||
899 aFlags
& SVGUtils::eUseUserSpaceOfUseElement
) {
900 // The spec says getBBox "Returns the tight bounding box in *current user
901 // space*". So we should really be doing this for all elements, but that
902 // needs investigation to check that we won't break too much content.
903 // NOTE: When changing this to apply to other frame types, make sure to
904 // also update SVGUtils::FrameSpaceInCSSPxToUserSpaceOffset.
905 MOZ_ASSERT(aFrame
->GetContent()->IsSVGElement(), "bad cast");
906 SVGElement
* element
= static_cast<SVGElement
*>(aFrame
->GetContent());
907 matrix
= element
->PrependLocalTransformsTo(matrix
, eChildToUserSpace
);
910 svg
->GetBBoxContribution(ToMatrix(matrix
), aFlags
).ToThebesRect();
911 // Account for 'clipped'.
912 if (aFlags
& SVGUtils::eBBoxIncludeClipped
) {
914 float x
, y
, width
, height
;
916 svg
->GetBBoxContribution({}, SVGUtils::eBBoxIncludeFill
).ToThebesRect();
919 width
= fillBBox
.width
;
920 height
= fillBBox
.height
;
921 bool hasClip
= aFrame
->StyleDisplay()->IsScrollableOverflow();
923 clipRect
= SVGUtils::GetClipRectForFrame(aFrame
, x
, y
, width
, height
);
924 if (aFrame
->IsSVGForeignObjectFrame() || aFrame
->IsSVGUseFrame()) {
925 clipRect
= matrix
.TransformBounds(clipRect
);
928 SVGClipPathFrame
* clipPathFrame
;
929 if (SVGObserverUtils::GetAndObserveClipPath(aFrame
, &clipPathFrame
) ==
930 SVGObserverUtils::eHasRefsSomeInvalid
) {
931 bbox
= gfxRect(0, 0, 0, 0);
934 SVGClipPathElement
* clipContent
=
935 static_cast<SVGClipPathElement
*>(clipPathFrame
->GetContent());
936 if (clipContent
->IsUnitsObjectBoundingBox()) {
937 matrix
.PreTranslate(gfxPoint(x
, y
));
938 matrix
.PreScale(width
, height
);
939 } else if (aFrame
->IsSVGForeignObjectFrame()) {
940 matrix
= gfxMatrix();
942 matrix
*= SVGUtils::GetTransformMatrixInUserSpace(clipPathFrame
);
944 bbox
= clipPathFrame
->GetBBoxForClipPathFrame(bbox
, matrix
, aFlags
)
949 bbox
= bbox
.Intersect(clipRect
);
952 if (bbox
.IsEmpty()) {
953 bbox
= gfxRect(0, 0, 0, 0);
958 if (aFlags
== eBBoxIncludeFillGeometry
&&
959 // We only cache bbox in element's own user space
961 // Obtaining the bbox for objectBoundingBox calculations is common so we
962 // cache the result for future calls, since calculation can be expensive:
963 aFrame
->SetProperty(ObjectBoundingBoxProperty(), new gfxRect(bbox
));
969 gfxPoint
SVGUtils::FrameSpaceInCSSPxToUserSpaceOffset(const nsIFrame
* aFrame
) {
970 if (!aFrame
->HasAnyStateBits(NS_FRAME_SVG_LAYOUT
)) {
971 // The user space for non-SVG frames is defined as the bounding box of the
972 // frame's border-box rects over all continuations.
976 // Leaf frames apply their own offset inside their user space.
977 if (FrameDoesNotIncludePositionInTM(aFrame
)) {
978 return nsLayoutUtils::RectToGfxRect(aFrame
->GetRect(),
979 AppUnitsPerCSSPixel())
983 // For foreignObject frames, SVGUtils::GetBBox applies their local
984 // transform, so we need to do the same here.
985 if (aFrame
->IsSVGForeignObjectFrame()) {
986 gfxMatrix transform
=
987 static_cast<SVGElement
*>(aFrame
->GetContent())
988 ->PrependLocalTransformsTo(gfxMatrix(), eChildToUserSpace
);
989 NS_ASSERTION(!transform
.HasNonTranslation(),
990 "we're relying on this being an offset-only transform");
991 return transform
.GetTranslation();
997 static gfxRect
GetBoundingBoxRelativeRect(const SVGAnimatedLength
* aXYWH
,
998 const gfxRect
& aBBox
) {
999 return gfxRect(aBBox
.x
+ SVGUtils::ObjectSpace(aBBox
, &aXYWH
[0]),
1000 aBBox
.y
+ SVGUtils::ObjectSpace(aBBox
, &aXYWH
[1]),
1001 SVGUtils::ObjectSpace(aBBox
, &aXYWH
[2]),
1002 SVGUtils::ObjectSpace(aBBox
, &aXYWH
[3]));
1005 gfxRect
SVGUtils::GetRelativeRect(uint16_t aUnits
,
1006 const SVGAnimatedLength
* aXYWH
,
1007 const gfxRect
& aBBox
,
1008 const UserSpaceMetrics
& aMetrics
) {
1009 if (aUnits
== SVG_UNIT_TYPE_OBJECTBOUNDINGBOX
) {
1010 return GetBoundingBoxRelativeRect(aXYWH
, aBBox
);
1012 return gfxRect(UserSpace(aMetrics
, &aXYWH
[0]), UserSpace(aMetrics
, &aXYWH
[1]),
1013 UserSpace(aMetrics
, &aXYWH
[2]),
1014 UserSpace(aMetrics
, &aXYWH
[3]));
1017 gfxRect
SVGUtils::GetRelativeRect(uint16_t aUnits
,
1018 const SVGAnimatedLength
* aXYWH
,
1019 const gfxRect
& aBBox
, nsIFrame
* aFrame
) {
1020 if (aUnits
== SVG_UNIT_TYPE_OBJECTBOUNDINGBOX
) {
1021 return GetBoundingBoxRelativeRect(aXYWH
, aBBox
);
1023 if (SVGElement
* svgElement
= SVGElement::FromNode(aFrame
->GetContent())) {
1024 return GetRelativeRect(aUnits
, aXYWH
, aBBox
, SVGElementMetrics(svgElement
));
1026 return GetRelativeRect(aUnits
, aXYWH
, aBBox
,
1027 NonSVGFrameUserSpaceMetrics(aFrame
));
1030 bool SVGUtils::CanOptimizeOpacity(const nsIFrame
* aFrame
) {
1031 if (!aFrame
->HasAnyStateBits(NS_FRAME_SVG_LAYOUT
)) {
1034 auto* content
= aFrame
->GetContent();
1035 if (!content
->IsSVGGeometryElement() &&
1036 !content
->IsSVGElement(nsGkAtoms::image
)) {
1039 if (aFrame
->StyleEffects()->HasFilters()) {
1042 // XXX The SVG WG is intending to allow fill, stroke and markers on <image>
1043 if (content
->IsSVGElement(nsGkAtoms::image
)) {
1046 const nsStyleSVG
* style
= aFrame
->StyleSVG();
1047 if (style
->HasMarker() &&
1048 static_cast<SVGGeometryElement
*>(content
)->IsMarkable()) {
1052 if (nsLayoutUtils::HasAnimationOfPropertySet(
1053 aFrame
, nsCSSPropertyIDSet::OpacityProperties())) {
1057 return !style
->HasFill() || !HasStroke(aFrame
);
1060 gfxMatrix
SVGUtils::AdjustMatrixForUnits(const gfxMatrix
& aMatrix
,
1061 const SVGAnimatedEnumeration
* aUnits
,
1062 nsIFrame
* aFrame
, uint32_t aFlags
) {
1063 if (aFrame
&& aUnits
->GetAnimValue() == SVG_UNIT_TYPE_OBJECTBOUNDINGBOX
) {
1064 gfxRect bbox
= GetBBox(aFrame
, aFlags
);
1065 gfxMatrix tm
= aMatrix
;
1066 tm
.PreTranslate(gfxPoint(bbox
.X(), bbox
.Y()));
1067 tm
.PreScale(bbox
.Width(), bbox
.Height());
1073 bool SVGUtils::GetNonScalingStrokeTransform(const nsIFrame
* aFrame
,
1074 gfxMatrix
* aUserToOuterSVG
) {
1075 if (aFrame
->GetContent()->IsText()) {
1076 aFrame
= aFrame
->GetParent();
1079 if (!aFrame
->StyleSVGReset()->HasNonScalingStroke()) {
1083 MOZ_ASSERT(aFrame
->GetContent()->IsSVGElement(), "should be an SVG element");
1085 *aUserToOuterSVG
= ThebesMatrix(SVGContentUtils::GetCTM(
1086 static_cast<SVGElement
*>(aFrame
->GetContent()), true));
1088 return aUserToOuterSVG
->HasNonTranslation();
1091 // The logic here comes from _cairo_stroke_style_max_distance_from_path
1092 static gfxRect
PathExtentsToMaxStrokeExtents(const gfxRect
& aPathExtents
,
1093 const nsIFrame
* aFrame
,
1094 double aStyleExpansionFactor
,
1095 const gfxMatrix
& aMatrix
) {
1096 double style_expansion
=
1097 aStyleExpansionFactor
* SVGUtils::GetStrokeWidth(aFrame
);
1099 gfxMatrix matrix
= aMatrix
;
1101 gfxMatrix outerSVGToUser
;
1102 if (SVGUtils::GetNonScalingStrokeTransform(aFrame
, &outerSVGToUser
)) {
1103 outerSVGToUser
.Invert();
1104 matrix
.PreMultiply(outerSVGToUser
);
1107 double dx
= style_expansion
* (fabs(matrix
._11
) + fabs(matrix
._21
));
1108 double dy
= style_expansion
* (fabs(matrix
._22
) + fabs(matrix
._12
));
1110 gfxRect strokeExtents
= aPathExtents
;
1111 strokeExtents
.Inflate(dx
, dy
);
1112 return strokeExtents
;
1116 gfxRect
SVGUtils::PathExtentsToMaxStrokeExtents(const gfxRect
& aPathExtents
,
1117 const nsTextFrame
* aFrame
,
1118 const gfxMatrix
& aMatrix
) {
1119 NS_ASSERTION(aFrame
->IsInSVGTextSubtree(),
1120 "expected an nsTextFrame for SVG text");
1121 return mozilla::PathExtentsToMaxStrokeExtents(aPathExtents
, aFrame
, 0.5,
1126 gfxRect
SVGUtils::PathExtentsToMaxStrokeExtents(const gfxRect
& aPathExtents
,
1127 const SVGGeometryFrame
* aFrame
,
1128 const gfxMatrix
& aMatrix
) {
1129 bool strokeMayHaveCorners
=
1130 !SVGContentUtils::ShapeTypeHasNoCorners(aFrame
->GetContent());
1132 // For a shape without corners the stroke can only extend half the stroke
1133 // width from the path in the x/y-axis directions. For shapes with corners
1134 // the stroke can extend by sqrt(1/2) (think 45 degree rotated rect, or line
1135 // with stroke-linecaps="square").
1136 double styleExpansionFactor
= strokeMayHaveCorners
? M_SQRT1_2
: 0.5;
1138 // The stroke can extend even further for paths that can be affected by
1139 // stroke-miterlimit.
1140 // We only need to do this if the limit is greater than 1, but it's probably
1141 // not worth optimizing for that.
1142 bool affectedByMiterlimit
= aFrame
->GetContent()->IsAnyOfSVGElements(
1143 nsGkAtoms::path
, nsGkAtoms::polyline
, nsGkAtoms::polygon
);
1145 if (affectedByMiterlimit
) {
1146 const nsStyleSVG
* style
= aFrame
->StyleSVG();
1147 if (style
->mStrokeLinejoin
== StyleStrokeLinejoin::Miter
&&
1148 styleExpansionFactor
< style
->mStrokeMiterlimit
/ 2.0) {
1149 styleExpansionFactor
= style
->mStrokeMiterlimit
/ 2.0;
1153 return mozilla::PathExtentsToMaxStrokeExtents(aPathExtents
, aFrame
,
1154 styleExpansionFactor
, aMatrix
);
1157 // ----------------------------------------------------------------------
1160 nscolor
SVGUtils::GetFallbackOrPaintColor(
1161 const ComputedStyle
& aStyle
, StyleSVGPaint
nsStyleSVG::*aFillOrStroke
,
1162 nscolor aDefaultContextFallbackColor
) {
1163 const auto& paint
= aStyle
.StyleSVG()->*aFillOrStroke
;
1165 switch (paint
.kind
.tag
) {
1166 case StyleSVGPaintKind::Tag::PaintServer
:
1167 color
= paint
.fallback
.IsColor()
1168 ? paint
.fallback
.AsColor().CalcColor(aStyle
)
1169 : NS_RGBA(0, 0, 0, 0);
1171 case StyleSVGPaintKind::Tag::ContextStroke
:
1172 case StyleSVGPaintKind::Tag::ContextFill
:
1173 color
= paint
.fallback
.IsColor()
1174 ? paint
.fallback
.AsColor().CalcColor(aStyle
)
1175 : aDefaultContextFallbackColor
;
1178 color
= paint
.kind
.AsColor().CalcColor(aStyle
);
1181 if (const auto* styleIfVisited
= aStyle
.GetStyleIfVisited()) {
1182 const auto& paintIfVisited
= styleIfVisited
->StyleSVG()->*aFillOrStroke
;
1183 // To prevent Web content from detecting if a user has visited a URL
1184 // (via URL loading triggered by paint servers or performance
1185 // differences between paint servers or between a paint server and a
1186 // color), we do not allow whether links are visited to change which
1187 // paint server is used or switch between paint servers and simple
1188 // colors. A :visited style may only override a simple color with
1189 // another simple color.
1190 if (paintIfVisited
.kind
.IsColor() && paint
.kind
.IsColor()) {
1191 nscolor colors
[2] = {
1192 color
, paintIfVisited
.kind
.AsColor().CalcColor(*styleIfVisited
)};
1193 return ComputedStyle::CombineVisitedColors(colors
,
1194 aStyle
.RelevantLinkVisited());
1201 void SVGUtils::MakeFillPatternFor(nsIFrame
* aFrame
, gfxContext
* aContext
,
1202 GeneralPattern
* aOutPattern
,
1203 imgDrawingParams
& aImgParams
,
1204 SVGContextPaint
* aContextPaint
) {
1205 const nsStyleSVG
* style
= aFrame
->StyleSVG();
1206 if (style
->mFill
.kind
.IsNone()) {
1210 const auto* styleEffects
= aFrame
->StyleEffects();
1212 float fillOpacity
= GetOpacity(style
->mFillOpacity
, aContextPaint
);
1213 if (!styleEffects
->IsOpaque() && SVGUtils::CanOptimizeOpacity(aFrame
)) {
1214 // Combine the group opacity into the fill opacity (we will have skipped
1215 // creating an offscreen surface to apply the group opacity).
1216 fillOpacity
*= styleEffects
->mOpacity
;
1219 const DrawTarget
* dt
= aContext
->GetDrawTarget();
1221 SVGPaintServerFrame
* ps
=
1222 SVGObserverUtils::GetAndObservePaintServer(aFrame
, &nsStyleSVG::mFill
);
1225 RefPtr
<gfxPattern
> pattern
=
1226 ps
->GetPaintServerPattern(aFrame
, dt
, aContext
->CurrentMatrixDouble(),
1227 &nsStyleSVG::mFill
, fillOpacity
, aImgParams
);
1229 pattern
->CacheColorStops(dt
);
1230 aOutPattern
->Init(*pattern
->GetPattern(dt
));
1235 if (aContextPaint
) {
1236 RefPtr
<gfxPattern
> pattern
;
1237 switch (style
->mFill
.kind
.tag
) {
1238 case StyleSVGPaintKind::Tag::ContextFill
:
1239 pattern
= aContextPaint
->GetFillPattern(
1240 dt
, fillOpacity
, aContext
->CurrentMatrixDouble(), aImgParams
);
1242 case StyleSVGPaintKind::Tag::ContextStroke
:
1243 pattern
= aContextPaint
->GetStrokePattern(
1244 dt
, fillOpacity
, aContext
->CurrentMatrixDouble(), aImgParams
);
1249 aOutPattern
->Init(*pattern
->GetPattern(dt
));
1254 if (style
->mFill
.fallback
.IsNone()) {
1258 // On failure, use the fallback colour in case we have an
1259 // objectBoundingBox where the width or height of the object is zero.
1260 // See http://www.w3.org/TR/SVG11/coords.html#ObjectBoundingBox
1261 sRGBColor
color(sRGBColor::FromABGR(GetFallbackOrPaintColor(
1262 *aFrame
->Style(), &nsStyleSVG::mFill
, NS_RGB(0, 0, 0))));
1263 color
.a
*= fillOpacity
;
1264 aOutPattern
->InitColorPattern(ToDeviceColor(color
));
1268 void SVGUtils::MakeStrokePatternFor(nsIFrame
* aFrame
, gfxContext
* aContext
,
1269 GeneralPattern
* aOutPattern
,
1270 imgDrawingParams
& aImgParams
,
1271 SVGContextPaint
* aContextPaint
) {
1272 const nsStyleSVG
* style
= aFrame
->StyleSVG();
1273 if (style
->mStroke
.kind
.IsNone()) {
1277 const auto* styleEffects
= aFrame
->StyleEffects();
1279 float strokeOpacity
= GetOpacity(style
->mStrokeOpacity
, aContextPaint
);
1280 if (!styleEffects
->IsOpaque() && SVGUtils::CanOptimizeOpacity(aFrame
)) {
1281 // Combine the group opacity into the stroke opacity (we will have skipped
1282 // creating an offscreen surface to apply the group opacity).
1283 strokeOpacity
*= styleEffects
->mOpacity
;
1286 const DrawTarget
* dt
= aContext
->GetDrawTarget();
1288 SVGPaintServerFrame
* ps
=
1289 SVGObserverUtils::GetAndObservePaintServer(aFrame
, &nsStyleSVG::mStroke
);
1292 RefPtr
<gfxPattern
> pattern
= ps
->GetPaintServerPattern(
1293 aFrame
, dt
, aContext
->CurrentMatrixDouble(), &nsStyleSVG::mStroke
,
1294 strokeOpacity
, aImgParams
);
1296 pattern
->CacheColorStops(dt
);
1297 aOutPattern
->Init(*pattern
->GetPattern(dt
));
1302 if (aContextPaint
) {
1303 RefPtr
<gfxPattern
> pattern
;
1304 switch (style
->mStroke
.kind
.tag
) {
1305 case StyleSVGPaintKind::Tag::ContextFill
:
1306 pattern
= aContextPaint
->GetFillPattern(
1307 dt
, strokeOpacity
, aContext
->CurrentMatrixDouble(), aImgParams
);
1309 case StyleSVGPaintKind::Tag::ContextStroke
:
1310 pattern
= aContextPaint
->GetStrokePattern(
1311 dt
, strokeOpacity
, aContext
->CurrentMatrixDouble(), aImgParams
);
1316 aOutPattern
->Init(*pattern
->GetPattern(dt
));
1321 if (style
->mStroke
.fallback
.IsNone()) {
1325 // On failure, use the fallback colour in case we have an
1326 // objectBoundingBox where the width or height of the object is zero.
1327 // See http://www.w3.org/TR/SVG11/coords.html#ObjectBoundingBox
1328 sRGBColor
color(sRGBColor::FromABGR(GetFallbackOrPaintColor(
1329 *aFrame
->Style(), &nsStyleSVG::mStroke
, NS_RGBA(0, 0, 0, 0))));
1330 color
.a
*= strokeOpacity
;
1331 aOutPattern
->InitColorPattern(ToDeviceColor(color
));
1335 float SVGUtils::GetOpacity(const StyleSVGOpacity
& aOpacity
,
1336 const SVGContextPaint
* aContextPaint
) {
1337 float opacity
= 1.0f
;
1338 switch (aOpacity
.tag
) {
1339 case StyleSVGOpacity::Tag::Opacity
:
1340 return aOpacity
.AsOpacity();
1341 case StyleSVGOpacity::Tag::ContextFillOpacity
:
1342 if (aContextPaint
) {
1343 opacity
= aContextPaint
->GetFillOpacity();
1346 case StyleSVGOpacity::Tag::ContextStrokeOpacity
:
1347 if (aContextPaint
) {
1348 opacity
= aContextPaint
->GetStrokeOpacity();
1355 bool SVGUtils::HasStroke(const nsIFrame
* aFrame
,
1356 const SVGContextPaint
* aContextPaint
) {
1357 const nsStyleSVG
* style
= aFrame
->StyleSVG();
1358 return style
->HasStroke() && GetStrokeWidth(aFrame
, aContextPaint
) > 0;
1361 float SVGUtils::GetStrokeWidth(const nsIFrame
* aFrame
,
1362 const SVGContextPaint
* aContextPaint
) {
1363 nsIContent
* content
= aFrame
->GetContent();
1364 if (content
->IsText()) {
1365 content
= content
->GetParent();
1368 auto* ctx
= SVGElement::FromNode(content
);
1369 return SVGContentUtils::GetStrokeWidth(ctx
, aFrame
->Style(), aContextPaint
);
1372 void SVGUtils::SetupStrokeGeometry(nsIFrame
* aFrame
, gfxContext
* aContext
,
1373 SVGContextPaint
* aContextPaint
) {
1374 MOZ_ASSERT(aFrame
->GetContent()->IsSVGElement(), "bad cast");
1375 SVGContentUtils::AutoStrokeOptions strokeOptions
;
1376 SVGContentUtils::GetStrokeOptions(&strokeOptions
,
1377 SVGElement::FromNode(aFrame
->GetContent()),
1378 aFrame
->Style(), aContextPaint
);
1380 if (strokeOptions
.mLineWidth
<= 0) {
1384 // SVGContentUtils::GetStrokeOptions gets the stroke options in CSS px;
1385 // convert to device pixels for gfxContext.
1386 float devPxPerCSSPx
= aFrame
->PresContext()->CSSToDevPixelScale().scale
;
1388 aContext
->SetLineWidth(strokeOptions
.mLineWidth
* devPxPerCSSPx
);
1389 aContext
->SetLineCap(strokeOptions
.mLineCap
);
1390 aContext
->SetMiterLimit(strokeOptions
.mMiterLimit
);
1391 aContext
->SetLineJoin(strokeOptions
.mLineJoin
);
1392 aContext
->SetDash(strokeOptions
.mDashPattern
, strokeOptions
.mDashLength
,
1393 strokeOptions
.mDashOffset
, devPxPerCSSPx
);
1396 uint16_t SVGUtils::GetGeometryHitTestFlags(const nsIFrame
* aFrame
) {
1399 switch (aFrame
->Style()->PointerEvents()) {
1400 case StylePointerEvents::None
:
1402 case StylePointerEvents::Auto
:
1403 case StylePointerEvents::Visiblepainted
:
1404 if (aFrame
->StyleVisibility()->IsVisible()) {
1405 if (!aFrame
->StyleSVG()->mFill
.kind
.IsNone()) {
1406 flags
= SVG_HIT_TEST_FILL
;
1408 if (!aFrame
->StyleSVG()->mStroke
.kind
.IsNone()) {
1409 flags
|= SVG_HIT_TEST_STROKE
;
1413 case StylePointerEvents::Visiblefill
:
1414 if (aFrame
->StyleVisibility()->IsVisible()) {
1415 flags
= SVG_HIT_TEST_FILL
;
1418 case StylePointerEvents::Visiblestroke
:
1419 if (aFrame
->StyleVisibility()->IsVisible()) {
1420 flags
= SVG_HIT_TEST_STROKE
;
1423 case StylePointerEvents::Visible
:
1424 if (aFrame
->StyleVisibility()->IsVisible()) {
1425 flags
= SVG_HIT_TEST_FILL
| SVG_HIT_TEST_STROKE
;
1428 case StylePointerEvents::Painted
:
1429 if (!aFrame
->StyleSVG()->mFill
.kind
.IsNone()) {
1430 flags
= SVG_HIT_TEST_FILL
;
1432 if (!aFrame
->StyleSVG()->mStroke
.kind
.IsNone()) {
1433 flags
|= SVG_HIT_TEST_STROKE
;
1436 case StylePointerEvents::Fill
:
1437 flags
= SVG_HIT_TEST_FILL
;
1439 case StylePointerEvents::Stroke
:
1440 flags
= SVG_HIT_TEST_STROKE
;
1442 case StylePointerEvents::All
:
1443 flags
= SVG_HIT_TEST_FILL
| SVG_HIT_TEST_STROKE
;
1446 NS_ERROR("not reached");
1453 void SVGUtils::PaintSVGGlyph(Element
* aElement
, gfxContext
* aContext
) {
1454 nsIFrame
* frame
= aElement
->GetPrimaryFrame();
1455 ISVGDisplayableFrame
* svgFrame
= do_QueryFrame(frame
);
1460 if (frame
->GetContent()->IsSVGElement()) {
1461 // PaintSVG() expects the passed transform to be the transform to its own
1462 // SVG user space, so we need to account for any 'transform' attribute:
1463 m
= SVGUtils::GetTransformMatrixInUserSpace(frame
);
1466 // SVG-in-OpenType is not allowed to paint external resources, so we can
1467 // just pass a dummy params into PatintSVG.
1468 imgDrawingParams dummy
;
1469 svgFrame
->PaintSVG(*aContext
, m
, dummy
);
1472 bool SVGUtils::GetSVGGlyphExtents(const Element
* aElement
,
1473 const gfxMatrix
& aSVGToAppSpace
,
1475 nsIFrame
* frame
= aElement
->GetPrimaryFrame();
1476 ISVGDisplayableFrame
* svgFrame
= do_QueryFrame(frame
);
1481 gfxMatrix
transform(aSVGToAppSpace
);
1482 if (auto* svg
= SVGElement::FromNode(frame
->GetContent())) {
1483 transform
= svg
->PrependLocalTransformsTo(aSVGToAppSpace
);
1488 ->GetBBoxContribution(gfx::ToMatrix(transform
),
1489 SVGUtils::eBBoxIncludeFill
|
1490 SVGUtils::eBBoxIncludeFillGeometry
|
1491 SVGUtils::eBBoxIncludeStroke
|
1492 SVGUtils::eBBoxIncludeStrokeGeometry
|
1493 SVGUtils::eBBoxIncludeMarkers
)
1498 nsRect
SVGUtils::ToCanvasBounds(const gfxRect
& aUserspaceRect
,
1499 const gfxMatrix
& aToCanvas
,
1500 const nsPresContext
* presContext
) {
1501 return nsLayoutUtils::RoundGfxRectToAppRect(
1502 aToCanvas
.TransformBounds(aUserspaceRect
),
1503 presContext
->AppUnitsPerDevPixel());
1506 gfxMatrix
SVGUtils::GetCSSPxToDevPxMatrix(const nsIFrame
* aNonSVGFrame
) {
1507 float devPxPerCSSPx
= aNonSVGFrame
->PresContext()->CSSToDevPixelScale().scale
;
1509 return gfxMatrix(devPxPerCSSPx
, 0.0, 0.0, devPxPerCSSPx
, 0.0, 0.0);
1512 gfxMatrix
SVGUtils::GetTransformMatrixInUserSpace(const nsIFrame
* aFrame
) {
1513 // We check element instead of aFrame directly because SVG element
1514 // may have non-SVG frame, <tspan> for example.
1515 MOZ_ASSERT(aFrame
->GetContent() && aFrame
->GetContent()->IsSVGElement(),
1516 "Only use this wrapper for SVG elements");
1518 if (!aFrame
->IsTransformed()) {
1522 nsStyleTransformMatrix::TransformReferenceBox
refBox(aFrame
);
1523 nsDisplayTransform::FrameTransformProperties properties
{
1524 aFrame
, refBox
, AppUnitsPerCSSPixel()};
1526 // SVG elements can have x/y offset, their default transform origin
1527 // is the origin of user space, not the top left point of the frame.
1528 Point3D svgTransformOrigin
{
1529 properties
.mToTransformOrigin
.x
- CSSPixel::FromAppUnits(refBox
.X()),
1530 properties
.mToTransformOrigin
.y
- CSSPixel::FromAppUnits(refBox
.Y()),
1531 properties
.mToTransformOrigin
.z
};
1533 Matrix svgTransform
;
1535 (void)aFrame
->IsSVGTransformed(&svgTransform
);
1537 if (properties
.HasTransform()) {
1538 trans
= nsStyleTransformMatrix::ReadTransforms(
1539 properties
.mTranslate
, properties
.mRotate
, properties
.mScale
,
1540 properties
.mMotion
.ptrOr(nullptr), properties
.mTransform
, refBox
,
1541 AppUnitsPerCSSPixel());
1543 trans
= Matrix4x4::From2D(svgTransform
);
1546 trans
.ChangeBasis(svgTransformOrigin
);
1549 trans
.ProjectTo2D();
1550 (void)trans
.CanDraw2D(&mm
);
1552 return ThebesMatrix(mm
);
1555 } // namespace mozilla