Bug 1858915 [wpt PR 42525] - Use taskcluster-run in infra tests, a=testonly
[gecko.git] / layout / svg / SVGUtils.cpp
blob376dc64ac513b4252fafa3876f403134d8696138
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 // Main header first:
8 // This is also necessary to ensure our definition of M_SQRT1_2 is picked up
9 #include "SVGUtils.h"
10 #include <algorithm>
12 // Keep others in (case-insensitive) order:
13 #include "gfx2DGlue.h"
14 #include "gfxContext.h"
15 #include "gfxMatrix.h"
16 #include "gfxPlatform.h"
17 #include "gfxRect.h"
18 #include "gfxUtils.h"
19 #include "nsCSSFrameConstructor.h"
20 #include "nsDisplayList.h"
21 #include "nsFrameList.h"
22 #include "nsGkAtoms.h"
23 #include "nsIContent.h"
24 #include "nsIFrame.h"
25 #include "nsIFrameInlines.h"
26 #include "nsLayoutUtils.h"
27 #include "nsPresContext.h"
28 #include "nsStyleStruct.h"
29 #include "nsStyleTransformMatrix.h"
30 #include "SVGAnimatedLength.h"
31 #include "SVGPaintServerFrame.h"
32 #include "nsTextFrame.h"
33 #include "mozilla/CSSClipPathInstance.h"
34 #include "mozilla/FilterInstance.h"
35 #include "mozilla/ISVGDisplayableFrame.h"
36 #include "mozilla/Preferences.h"
37 #include "mozilla/StaticPrefs_svg.h"
38 #include "mozilla/SVGClipPathFrame.h"
39 #include "mozilla/SVGContainerFrame.h"
40 #include "mozilla/SVGContentUtils.h"
41 #include "mozilla/SVGContextPaint.h"
42 #include "mozilla/SVGForeignObjectFrame.h"
43 #include "mozilla/SVGIntegrationUtils.h"
44 #include "mozilla/SVGGeometryFrame.h"
45 #include "mozilla/SVGMaskFrame.h"
46 #include "mozilla/SVGObserverUtils.h"
47 #include "mozilla/SVGOuterSVGFrame.h"
48 #include "mozilla/SVGTextFrame.h"
49 #include "mozilla/Unused.h"
50 #include "mozilla/gfx/2D.h"
51 #include "mozilla/gfx/PatternHelpers.h"
52 #include "mozilla/dom/Document.h"
53 #include "mozilla/dom/SVGClipPathElement.h"
54 #include "mozilla/dom/SVGGeometryElement.h"
55 #include "mozilla/dom/SVGPathElement.h"
56 #include "mozilla/dom/SVGUnitTypesBinding.h"
57 #include "mozilla/dom/SVGViewportElement.h"
59 using namespace mozilla::dom;
60 using namespace mozilla::dom::SVGUnitTypes_Binding;
61 using namespace mozilla::gfx;
62 using namespace mozilla::image;
64 bool NS_SVGNewGetBBoxEnabled() {
65 return mozilla::StaticPrefs::svg_new_getBBox_enabled();
68 namespace mozilla {
70 // we only take the address of this:
71 static gfx::UserDataKey sSVGAutoRenderStateKey;
73 SVGAutoRenderState::SVGAutoRenderState(DrawTarget* aDrawTarget)
74 : mDrawTarget(aDrawTarget),
75 mOriginalRenderState(nullptr),
76 mPaintingToWindow(false) {
77 mOriginalRenderState = aDrawTarget->RemoveUserData(&sSVGAutoRenderStateKey);
78 // We always remove ourselves from aContext before it dies, so
79 // passing nullptr as the destroy function is okay.
80 aDrawTarget->AddUserData(&sSVGAutoRenderStateKey, this, nullptr);
83 SVGAutoRenderState::~SVGAutoRenderState() {
84 mDrawTarget->RemoveUserData(&sSVGAutoRenderStateKey);
85 if (mOriginalRenderState) {
86 mDrawTarget->AddUserData(&sSVGAutoRenderStateKey, mOriginalRenderState,
87 nullptr);
91 void SVGAutoRenderState::SetPaintingToWindow(bool aPaintingToWindow) {
92 mPaintingToWindow = aPaintingToWindow;
95 /* static */
96 bool SVGAutoRenderState::IsPaintingToWindow(DrawTarget* aDrawTarget) {
97 void* state = aDrawTarget->GetUserData(&sSVGAutoRenderStateKey);
98 if (state) {
99 return static_cast<SVGAutoRenderState*>(state)->mPaintingToWindow;
101 return false;
104 // Unlike containers, leaf frames do not include GetPosition() in
105 // GetCanvasTM().
106 static bool FrameDoesNotIncludePositionInTM(const nsIFrame* aFrame) {
107 return aFrame->IsSVGGeometryFrame() || aFrame->IsSVGImageFrame() ||
108 aFrame->IsInSVGTextSubtree();
111 nsRect SVGUtils::GetPostFilterInkOverflowRect(nsIFrame* aFrame,
112 const nsRect& aPreFilterRect) {
113 MOZ_ASSERT(aFrame->HasAnyStateBits(NS_FRAME_SVG_LAYOUT),
114 "Called on invalid frame type");
116 // Note: we do not return here for eHasNoRefs since we must still handle any
117 // CSS filter functions.
118 // 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,
127 &aPreFilterRect)
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);
137 do {
138 if (outer->IsCallingReflowSVG()) {
139 return true;
141 outer = GetOuterSVGFrame(outer->GetParent());
142 } while (outer);
143 return false;
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)) {
161 return;
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.
167 return;
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);
176 } else {
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)) {
182 return;
184 f->AddStateBits(NS_FRAME_HAS_DIRTY_CHILDREN);
185 f = f->GetParent();
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.
200 return;
203 nsFrameState dirtyBit =
204 (outerSVGFrame == aFrame ? NS_FRAME_IS_DIRTY
205 : NS_FRAME_HAS_DIRTY_CHILDREN);
207 aFrame->PresShell()->FrameNeedsReflow(outerSVGFrame, IntrinsicDirty::None,
208 dirtyBit);
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
216 // function.
217 return aFrame->IsSubtreeDirty();
220 Size SVGUtils::GetContextSize(const nsIFrame* aFrame) {
221 Size size;
223 MOZ_ASSERT(aFrame->GetContent()->IsSVGElement(), "bad cast");
224 const SVGElement* element = static_cast<SVGElement*>(aFrame->GetContent());
226 SVGViewportElement* ctx = element->GetCtx();
227 if (ctx) {
228 size.width = ctx->GetLength(SVGContentUtils::X);
229 size.height = ctx->GetLength(SVGContentUtils::Y);
231 return size;
234 float SVGUtils::ObjectSpace(const gfxRect& aRect,
235 const SVGAnimatedLength* aLength) {
236 float axis;
238 switch (aLength->GetCtxType()) {
239 case SVGContentUtils::X:
240 axis = aRect.Width();
241 break;
242 case SVGContentUtils::Y:
243 axis = aRect.Height();
244 break;
245 case SVGContentUtils::XY:
246 axis = float(SVGContentUtils::ComputeNormalizedHypotenuse(
247 aRect.Width(), aRect.Height()));
248 break;
249 default:
250 MOZ_ASSERT_UNREACHABLE("unexpected ctx type");
251 axis = 0.0f;
252 break;
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)) *
259 axis;
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,
279 nsRect* aRect) {
280 ISVGDisplayableFrame* svg = do_QueryFrame(aFrame);
281 if (!svg) {
282 return nullptr;
284 SVGOuterSVGFrame* outer = GetOuterSVGFrame(aFrame);
285 if (outer == svg) {
286 return nullptr;
289 if (aFrame->HasAnyStateBits(NS_FRAME_IS_NONDISPLAY)) {
290 *aRect = nsRect(0, 0, 0, 0);
291 } else {
292 uint32_t flags = SVGUtils::eForGetClientRects | SVGUtils::eBBoxIncludeFill |
293 SVGUtils::eBBoxIncludeStroke |
294 SVGUtils::eBBoxIncludeMarkers |
295 SVGUtils::eUseUserSpaceOfUseElement;
297 auto ctm = nsLayoutUtils::GetTransformToAncestor(RelativeTo{aFrame},
298 RelativeTo{outer});
300 float initPositionX = NSAppUnitsToFloatPixels(aFrame->GetPosition().x,
301 AppUnitsPerCSSPixel()),
302 initPositionY = NSAppUnitsToFloatPixels(aFrame->GetPosition().y,
303 AppUnitsPerCSSPixel());
305 Matrix mm;
306 ctm.ProjectTo2D();
307 ctm.CanDraw2D(&mm);
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
320 // double-counting.
321 m = m.PreTranslate(-initPositionX, -initPositionY);
323 gfxRect bbox = SVGUtils::GetBBox(aFrame, flags, &m);
324 *aRect = nsLayoutUtils::RoundGfxRectToAppRect(bbox, appUnitsPerDevPixel);
327 return outer;
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()) {
370 if (aOwnTransform) {
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);
383 if (SVGFrame) {
384 SVGFrame->NotifySVGChanged(aFlags);
385 } else {
386 NS_ASSERTION(
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)) {
405 return 1.0f;
408 return styleEffects->mOpacity;
411 SVGUtils::MaskUsage SVGUtils::DetermineMaskUsage(const nsIFrame* aFrame,
412 bool aHandleOpacity) {
413 MaskUsage usage;
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:
436 if (clipPathFrame) {
437 if (clipPathFrame->IsTrivial()) {
438 usage.mShouldApplyClipPath = true;
439 } else {
440 usage.mShouldGenerateClipMaskLayer = true;
443 break;
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());
450 break;
452 case ClipPathType::Box:
453 usage.mShouldApplyBasicShapeOrPath = true;
454 break;
455 case ClipPathType::None:
456 MOZ_ASSERT(!usage.mShouldGenerateClipMaskLayer &&
457 !usage.mShouldApplyClipPath &&
458 !usage.mShouldApplyBasicShapeOrPath);
459 break;
460 default:
461 MOZ_ASSERT_UNREACHABLE("Unsupported clip-path type.");
462 break;
464 return usage;
467 class MixModeBlender {
468 public:
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
484 // another operator.
485 IntRect drawRect = ComputeClipExtsInDeviceSpace(aTransform);
486 if (drawRect.IsEmpty()) {
487 return nullptr;
490 RefPtr<DrawTarget> targetDT =
491 mSourceCtx->GetDrawTarget()->CreateSimilarDrawTarget(
492 drawRect.Size(), SurfaceFormat::B8G8R8A8);
493 if (!targetDT || !targetDT->IsValid()) {
494 return nullptr;
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);
522 mSourceCtx->Paint();
525 private:
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();
555 IntRect result;
556 ToRect(clippedFrameSurfaceRect).ToIntRect(&result);
558 return Factory::CheckSurfaceSize(result.Size()) ? result : IntRect();
561 nsIFrame* mFrame;
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);
575 if (!svgFrame) {
576 return;
579 MaskUsage maskUsage = DetermineMaskUsage(aFrame, true);
580 if (maskUsage.IsTransparent()) {
581 return;
584 if (auto* svg = SVGElement::FromNode(aFrame->GetContent())) {
585 if (!svg->HasValidDimensions()) {
586 return;
590 /* SVG defines the following rendering model:
592 * 1. Render fill
593 * 2. Render stroke
594 * 3. Render markers
595 * 4. Apply filter
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
601 * clip region).
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)
622 : &aContext;
624 if (!target) {
625 return;
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
637 // mask:url()
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,
646 aImgParams);
648 maskSurface = maskFrame->GetMaskForMaskedFrame(params);
650 if (!maskSurface) {
651 // Either entire surface is clipped out, or gfx buffer allocation
652 // failure in SVGMaskFrame::GetMaskForMaskedFrame.
653 return;
655 shouldPushMask = true;
658 if (maskUsage.ShouldGenerateClipMaskLayer()) {
659 RefPtr<SourceSurface> clipMaskSurface =
660 clipPathFrame->GetClipMask(aContext, aFrame, aTransform, maskSurface);
661 if (clipMaskSurface) {
662 maskSurface = clipMaskSurface;
663 } else {
664 // Either entire surface is clipped out, or gfx buffer allocation
665 // failure in SVGClipPathFrame::GetClipMask.
666 return;
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);
695 } else {
696 CSSClipPathInstance::ApplyBasicShapeOrPathClip(aContext, aFrame,
697 aTransform);
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,
720 aFilterTransform
721 ? SVGUtils::GetCSSPxToDevPxMatrix(aFrame)
722 : aTransform,
723 aImgParams);
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);
733 } else {
734 svgFrame->PaintSVG(*target, aTransform, aImgParams);
737 if (maskUsage.ShouldApplyClipPath() ||
738 maskUsage.ShouldApplyBasicShapeOrPath()) {
739 aContext.PopClip();
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()) {
755 return true;
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)) {
777 surfaceSize.width =
778 std::min(NS_SVG_OFFSCREEN_MAX_DIMENSION, surfaceSize.width);
779 surfaceSize.height =
780 std::min(NS_SVG_OFFSCREEN_MAX_DIMENSION, surfaceSize.height);
781 *aResultOverflows = true;
784 return surfaceSize;
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()) {
791 return false;
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 &&
797 p.y <= rect.YMost();
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());
816 gfxRect clipRect =
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) {
825 clipRect.x = aX;
826 clipRect.width = aWidth;
828 if (disp->mOverflowY != StyleOverflow::Hidden) {
829 clipRect.y = aY;
830 clipRect.height = aHeight;
832 return clipRect;
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.
845 aFrame =
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.
854 return gfxRect();
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(
864 aFrame,
865 /* aUnionContinuations = */ !onlyCurrentFrame);
868 MOZ_ASSERT(svg);
870 if (auto* element = SVGElement::FromNodeOrNull(aFrame->GetContent())) {
871 if (!element->HasValidDimensions()) {
872 return gfxRect();
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.
878 aFlags &=
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
886 !aToBoundsSpace) {
887 gfxRect* prop = aFrame->GetProperty(ObjectBoundingBoxProperty());
888 if (prop) {
889 return *prop;
893 gfxMatrix matrix;
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);
909 gfxRect bbox =
910 svg->GetBBoxContribution(ToMatrix(matrix), aFlags).ToThebesRect();
911 // Account for 'clipped'.
912 if (aFlags & SVGUtils::eBBoxIncludeClipped) {
913 gfxRect clipRect;
914 float x, y, width, height;
915 gfxRect fillBBox =
916 svg->GetBBoxContribution({}, SVGUtils::eBBoxIncludeFill).ToThebesRect();
917 x = fillBBox.x;
918 y = fillBBox.y;
919 width = fillBBox.width;
920 height = fillBBox.height;
921 bool hasClip = aFrame->StyleDisplay()->IsScrollableOverflow();
922 if (hasClip) {
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);
932 } else {
933 if (clipPathFrame) {
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)
945 .ToThebesRect();
948 if (hasClip) {
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
960 !aToBoundsSpace) {
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));
966 return 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.
973 return gfxPoint();
976 // Leaf frames apply their own offset inside their user space.
977 if (FrameDoesNotIncludePositionInTM(aFrame)) {
978 return nsLayoutUtils::RectToGfxRect(aFrame->GetRect(),
979 AppUnitsPerCSSPixel())
980 .TopLeft();
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();
994 return gfxPoint();
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)) {
1032 return false;
1034 auto* content = aFrame->GetContent();
1035 if (!content->IsSVGGeometryElement() &&
1036 !content->IsSVGElement(nsGkAtoms::image)) {
1037 return false;
1039 if (aFrame->StyleEffects()->HasFilters()) {
1040 return false;
1042 // XXX The SVG WG is intending to allow fill, stroke and markers on <image>
1043 if (content->IsSVGElement(nsGkAtoms::image)) {
1044 return true;
1046 const nsStyleSVG* style = aFrame->StyleSVG();
1047 if (style->HasMarker() &&
1048 static_cast<SVGGeometryElement*>(content)->IsMarkable()) {
1049 return false;
1052 if (nsLayoutUtils::HasAnimationOfPropertySet(
1053 aFrame, nsCSSPropertyIDSet::OpacityProperties())) {
1054 return false;
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());
1068 return tm;
1070 return aMatrix;
1073 bool SVGUtils::GetNonScalingStrokeTransform(const nsIFrame* aFrame,
1074 gfxMatrix* aUserToOuterSVG) {
1075 if (aFrame->GetContent()->IsText()) {
1076 aFrame = aFrame->GetParent();
1079 if (!aFrame->StyleSVGReset()->HasNonScalingStroke()) {
1080 return false;
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;
1115 /*static*/
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,
1122 aMatrix);
1125 /*static*/
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 // ----------------------------------------------------------------------
1159 /* static */
1160 nscolor SVGUtils::GetFallbackOrPaintColor(
1161 const ComputedStyle& aStyle, StyleSVGPaint nsStyleSVG::*aFillOrStroke,
1162 nscolor aDefaultContextFallbackColor) {
1163 const auto& paint = aStyle.StyleSVG()->*aFillOrStroke;
1164 nscolor color;
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);
1170 break;
1171 case StyleSVGPaintKind::Tag::ContextStroke:
1172 case StyleSVGPaintKind::Tag::ContextFill:
1173 color = paint.fallback.IsColor()
1174 ? paint.fallback.AsColor().CalcColor(aStyle)
1175 : aDefaultContextFallbackColor;
1176 break;
1177 default:
1178 color = paint.kind.AsColor().CalcColor(aStyle);
1179 break;
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());
1197 return color;
1200 /* static */
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()) {
1207 return;
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);
1224 if (ps) {
1225 RefPtr<gfxPattern> pattern =
1226 ps->GetPaintServerPattern(aFrame, dt, aContext->CurrentMatrixDouble(),
1227 &nsStyleSVG::mFill, fillOpacity, aImgParams);
1228 if (pattern) {
1229 pattern->CacheColorStops(dt);
1230 aOutPattern->Init(*pattern->GetPattern(dt));
1231 return;
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);
1241 break;
1242 case StyleSVGPaintKind::Tag::ContextStroke:
1243 pattern = aContextPaint->GetStrokePattern(
1244 dt, fillOpacity, aContext->CurrentMatrixDouble(), aImgParams);
1245 break;
1246 default:;
1248 if (pattern) {
1249 aOutPattern->Init(*pattern->GetPattern(dt));
1250 return;
1254 if (style->mFill.fallback.IsNone()) {
1255 return;
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));
1267 /* static */
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()) {
1274 return;
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);
1291 if (ps) {
1292 RefPtr<gfxPattern> pattern = ps->GetPaintServerPattern(
1293 aFrame, dt, aContext->CurrentMatrixDouble(), &nsStyleSVG::mStroke,
1294 strokeOpacity, aImgParams);
1295 if (pattern) {
1296 pattern->CacheColorStops(dt);
1297 aOutPattern->Init(*pattern->GetPattern(dt));
1298 return;
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);
1308 break;
1309 case StyleSVGPaintKind::Tag::ContextStroke:
1310 pattern = aContextPaint->GetStrokePattern(
1311 dt, strokeOpacity, aContext->CurrentMatrixDouble(), aImgParams);
1312 break;
1313 default:;
1315 if (pattern) {
1316 aOutPattern->Init(*pattern->GetPattern(dt));
1317 return;
1321 if (style->mStroke.fallback.IsNone()) {
1322 return;
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));
1334 /* static */
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();
1345 break;
1346 case StyleSVGOpacity::Tag::ContextStrokeOpacity:
1347 if (aContextPaint) {
1348 opacity = aContextPaint->GetStrokeOpacity();
1350 break;
1352 return opacity;
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) {
1381 return;
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) {
1397 uint16_t flags = 0;
1399 switch (aFrame->Style()->PointerEvents()) {
1400 case StylePointerEvents::None:
1401 break;
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;
1412 break;
1413 case StylePointerEvents::Visiblefill:
1414 if (aFrame->StyleVisibility()->IsVisible()) {
1415 flags = SVG_HIT_TEST_FILL;
1417 break;
1418 case StylePointerEvents::Visiblestroke:
1419 if (aFrame->StyleVisibility()->IsVisible()) {
1420 flags = SVG_HIT_TEST_STROKE;
1422 break;
1423 case StylePointerEvents::Visible:
1424 if (aFrame->StyleVisibility()->IsVisible()) {
1425 flags = SVG_HIT_TEST_FILL | SVG_HIT_TEST_STROKE;
1427 break;
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;
1435 break;
1436 case StylePointerEvents::Fill:
1437 flags = SVG_HIT_TEST_FILL;
1438 break;
1439 case StylePointerEvents::Stroke:
1440 flags = SVG_HIT_TEST_STROKE;
1441 break;
1442 case StylePointerEvents::All:
1443 flags = SVG_HIT_TEST_FILL | SVG_HIT_TEST_STROKE;
1444 break;
1445 default:
1446 NS_ERROR("not reached");
1447 break;
1450 return flags;
1453 void SVGUtils::PaintSVGGlyph(Element* aElement, gfxContext* aContext) {
1454 nsIFrame* frame = aElement->GetPrimaryFrame();
1455 ISVGDisplayableFrame* svgFrame = do_QueryFrame(frame);
1456 if (!svgFrame) {
1457 return;
1459 gfxMatrix m;
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,
1474 gfxRect* aResult) {
1475 nsIFrame* frame = aElement->GetPrimaryFrame();
1476 ISVGDisplayableFrame* svgFrame = do_QueryFrame(frame);
1477 if (!svgFrame) {
1478 return false;
1481 gfxMatrix transform(aSVGToAppSpace);
1482 if (auto* svg = SVGElement::FromNode(frame->GetContent())) {
1483 transform = svg->PrependLocalTransformsTo(aSVGToAppSpace);
1486 *aResult =
1487 svgFrame
1488 ->GetBBoxContribution(gfx::ToMatrix(transform),
1489 SVGUtils::eBBoxIncludeFill |
1490 SVGUtils::eBBoxIncludeFillGeometry |
1491 SVGUtils::eBBoxIncludeStroke |
1492 SVGUtils::eBBoxIncludeStrokeGeometry |
1493 SVGUtils::eBBoxIncludeMarkers)
1494 .ToThebesRect();
1495 return true;
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()) {
1519 return {};
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;
1534 Matrix4x4 trans;
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());
1542 } else {
1543 trans = Matrix4x4::From2D(svgTransform);
1546 trans.ChangeBasis(svgTransformOrigin);
1548 Matrix mm;
1549 trans.ProjectTo2D();
1550 (void)trans.CanDraw2D(&mm);
1552 return ThebesMatrix(mm);
1555 } // namespace mozilla