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