Backed out changeset 2960ea3e50ca (bug 1881157) for causing crashtest assertion failu...
[gecko.git] / layout / svg / SVGUtils.cpp
blob2967bac780101771fc8d4a89c41b8cfe973643da
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->IsSVGFrame(), "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->IsSVGFrame(), "IsSVGOuterSVGFrame check above not valid!");
189 outerSVGFrame = static_cast<SVGOuterSVGFrame*>(f);
191 MOZ_ASSERT(outerSVGFrame && outerSVGFrame->IsSVGOuterSVGFrame(),
192 "Did not find SVGOuterSVGFrame!");
195 if (outerSVGFrame->HasAnyStateBits(NS_FRAME_IN_REFLOW)) {
196 // We're currently under an SVGOuterSVGFrame::Reflow call so there is no
197 // need to call PresShell::FrameNeedsReflow, since we have an
198 // SVGOuterSVGFrame::DidReflow call pending.
199 return;
202 nsFrameState dirtyBit =
203 (outerSVGFrame == aFrame ? NS_FRAME_IS_DIRTY
204 : NS_FRAME_HAS_DIRTY_CHILDREN);
206 aFrame->PresShell()->FrameNeedsReflow(outerSVGFrame, IntrinsicDirty::None,
207 dirtyBit);
210 bool SVGUtils::NeedsReflowSVG(const nsIFrame* aFrame) {
211 MOZ_ASSERT(aFrame->IsSVGFrame(), "SVG uses bits differently!");
213 // The flags we test here may change, hence why we have this separate
214 // function.
215 return aFrame->IsSubtreeDirty();
218 Size SVGUtils::GetContextSize(const nsIFrame* aFrame) {
219 Size size;
221 MOZ_ASSERT(aFrame->GetContent()->IsSVGElement(), "bad cast");
222 const SVGElement* element = static_cast<SVGElement*>(aFrame->GetContent());
224 SVGViewportElement* ctx = element->GetCtx();
225 if (ctx) {
226 size.width = ctx->GetLength(SVGContentUtils::X);
227 size.height = ctx->GetLength(SVGContentUtils::Y);
229 return size;
232 float SVGUtils::ObjectSpace(const gfxRect& aRect,
233 const SVGAnimatedLength* aLength) {
234 float axis;
236 switch (aLength->GetCtxType()) {
237 case SVGContentUtils::X:
238 axis = aRect.Width();
239 break;
240 case SVGContentUtils::Y:
241 axis = aRect.Height();
242 break;
243 case SVGContentUtils::XY:
244 axis = float(SVGContentUtils::ComputeNormalizedHypotenuse(
245 aRect.Width(), aRect.Height()));
246 break;
247 default:
248 MOZ_ASSERT_UNREACHABLE("unexpected ctx type");
249 axis = 0.0f;
250 break;
252 if (aLength->IsPercentage()) {
253 // Multiply first to avoid precision errors:
254 return axis * aLength->GetAnimValInSpecifiedUnits() / 100;
256 return aLength->GetAnimValue(static_cast<SVGViewportElement*>(nullptr)) *
257 axis;
260 float SVGUtils::UserSpace(nsIFrame* aNonSVGContext,
261 const SVGAnimatedLength* aLength) {
262 MOZ_ASSERT(!aNonSVGContext->IsTextFrame(), "Not expecting text content");
263 return aLength->GetAnimValue(aNonSVGContext);
266 float SVGUtils::UserSpace(const UserSpaceMetrics& aMetrics,
267 const SVGAnimatedLength* aLength) {
268 return aLength->GetAnimValue(aMetrics);
271 SVGOuterSVGFrame* SVGUtils::GetOuterSVGFrame(nsIFrame* aFrame) {
272 return static_cast<SVGOuterSVGFrame*>(nsLayoutUtils::GetClosestFrameOfType(
273 aFrame, LayoutFrameType::SVGOuterSVG));
276 nsIFrame* SVGUtils::GetOuterSVGFrameAndCoveredRegion(nsIFrame* aFrame,
277 nsRect* aRect) {
278 ISVGDisplayableFrame* svg = do_QueryFrame(aFrame);
279 if (!svg) {
280 return nullptr;
282 SVGOuterSVGFrame* outer = GetOuterSVGFrame(aFrame);
283 if (outer == svg) {
284 return nullptr;
287 if (aFrame->HasAnyStateBits(NS_FRAME_IS_NONDISPLAY)) {
288 *aRect = nsRect(0, 0, 0, 0);
289 } else {
290 uint32_t flags = SVGUtils::eForGetClientRects | SVGUtils::eBBoxIncludeFill |
291 SVGUtils::eBBoxIncludeStroke |
292 SVGUtils::eBBoxIncludeMarkers |
293 SVGUtils::eUseUserSpaceOfUseElement;
295 auto ctm = nsLayoutUtils::GetTransformToAncestor(RelativeTo{aFrame},
296 RelativeTo{outer});
298 float initPositionX = NSAppUnitsToFloatPixels(aFrame->GetPosition().x,
299 AppUnitsPerCSSPixel()),
300 initPositionY = NSAppUnitsToFloatPixels(aFrame->GetPosition().y,
301 AppUnitsPerCSSPixel());
303 Matrix mm;
304 ctm.ProjectTo2D();
305 ctm.CanDraw2D(&mm);
306 gfxMatrix m = ThebesMatrix(mm);
308 float appUnitsPerDevPixel = aFrame->PresContext()->AppUnitsPerDevPixel();
309 float devPixelPerCSSPixel =
310 float(AppUnitsPerCSSPixel()) / appUnitsPerDevPixel;
312 // The matrix that GetBBox accepts should operate on "user space",
313 // i.e. with CSS pixel unit.
314 m = m.PreScale(devPixelPerCSSPixel, devPixelPerCSSPixel);
316 // Both SVGUtils::GetBBox and nsLayoutUtils::GetTransformToAncestor
317 // will count this displacement, we should remove it here to avoid
318 // double-counting.
319 m = m.PreTranslate(-initPositionX, -initPositionY);
321 gfxRect bbox = SVGUtils::GetBBox(aFrame, flags, &m);
322 *aRect = nsLayoutUtils::RoundGfxRectToAppRect(bbox, appUnitsPerDevPixel);
325 return outer;
328 gfxMatrix SVGUtils::GetCanvasTM(nsIFrame* aFrame) {
329 // XXX yuck, we really need a common interface for GetCanvasTM
331 if (!aFrame->HasAnyStateBits(NS_FRAME_SVG_LAYOUT)) {
332 return GetCSSPxToDevPxMatrix(aFrame);
335 if (aFrame->IsSVGForeignObjectFrame()) {
336 return static_cast<SVGForeignObjectFrame*>(aFrame)->GetCanvasTM();
339 if (SVGContainerFrame* containerFrame = do_QueryFrame(aFrame)) {
340 return containerFrame->GetCanvasTM();
343 MOZ_ASSERT(aFrame->GetParent()->IsSVGContainerFrame());
345 auto* parent = static_cast<SVGContainerFrame*>(aFrame->GetParent());
346 auto* content = static_cast<SVGElement*>(aFrame->GetContent());
348 return content->PrependLocalTransformsTo(parent->GetCanvasTM());
351 bool SVGUtils::IsSVGTransformed(const nsIFrame* aFrame,
352 gfx::Matrix* aOwnTransform,
353 gfx::Matrix* aFromParentTransform) {
354 MOZ_ASSERT(aFrame->HasAllStateBits(NS_FRAME_SVG_LAYOUT |
355 NS_FRAME_MAY_BE_TRANSFORMED),
356 "Expecting an SVG frame that can be transformed");
357 bool foundTransform = false;
359 // Check if our parent has children-only transforms:
360 if (SVGContainerFrame* parent = do_QueryFrame(aFrame->GetParent())) {
361 foundTransform = parent->HasChildrenOnlyTransform(aFromParentTransform);
364 if (auto* content = SVGElement::FromNode(aFrame->GetContent())) {
365 auto* transformList = content->GetAnimatedTransformList();
366 if ((transformList && transformList->HasTransform()) ||
367 content->GetAnimateMotionTransform()) {
368 if (aOwnTransform) {
369 *aOwnTransform = gfx::ToMatrix(
370 content->PrependLocalTransformsTo(gfxMatrix(), eUserSpaceToParent));
372 foundTransform = true;
375 return foundTransform;
378 void SVGUtils::NotifyChildrenOfSVGChange(nsIFrame* aFrame, uint32_t aFlags) {
379 for (nsIFrame* kid : aFrame->PrincipalChildList()) {
380 ISVGDisplayableFrame* SVGFrame = do_QueryFrame(kid);
381 if (SVGFrame) {
382 SVGFrame->NotifySVGChanged(aFlags);
383 } else {
384 NS_ASSERTION(kid->IsSVGFrame() || kid->IsInSVGTextSubtree(),
385 "SVG frame expected");
386 // recurse into the children of container frames e.g. <clipPath>, <mask>
387 // in case they have child frames with transformation matrices
388 if (kid->IsSVGFrame()) {
389 NotifyChildrenOfSVGChange(kid, aFlags);
395 // ************************************************************
397 float SVGUtils::ComputeOpacity(const nsIFrame* aFrame, bool aHandleOpacity) {
398 const auto* styleEffects = aFrame->StyleEffects();
400 if (!styleEffects->IsOpaque() &&
401 (SVGUtils::CanOptimizeOpacity(aFrame) || !aHandleOpacity)) {
402 return 1.0f;
405 return styleEffects->mOpacity;
408 SVGUtils::MaskUsage SVGUtils::DetermineMaskUsage(const nsIFrame* aFrame,
409 bool aHandleOpacity) {
410 MaskUsage usage;
412 using ClipPathType = StyleClipPath::Tag;
414 usage.mOpacity = ComputeOpacity(aFrame, aHandleOpacity);
416 nsIFrame* firstFrame =
417 nsLayoutUtils::FirstContinuationOrIBSplitSibling(aFrame);
419 const nsStyleSVGReset* svgReset = firstFrame->StyleSVGReset();
421 if (SVGObserverUtils::GetAndObserveMasks(firstFrame, nullptr) !=
422 SVGObserverUtils::eHasNoRefs) {
423 usage.mShouldGenerateMaskLayer = true;
426 SVGClipPathFrame* clipPathFrame;
427 // XXX check return value?
428 SVGObserverUtils::GetAndObserveClipPath(firstFrame, &clipPathFrame);
429 MOZ_ASSERT(!clipPathFrame || svgReset->mClipPath.IsUrl());
431 switch (svgReset->mClipPath.tag) {
432 case ClipPathType::Url:
433 if (clipPathFrame) {
434 if (clipPathFrame->IsTrivial()) {
435 usage.mShouldApplyClipPath = true;
436 } else {
437 usage.mShouldGenerateClipMaskLayer = true;
440 break;
441 case ClipPathType::Shape: {
442 usage.mShouldApplyBasicShapeOrPath = true;
443 const auto& shape = svgReset->mClipPath.AsShape()._0;
444 usage.mIsSimpleClipShape =
445 !usage.mShouldGenerateMaskLayer &&
446 (shape->IsRect() || shape->IsCircle() || shape->IsEllipse());
447 break;
449 case ClipPathType::Box:
450 usage.mShouldApplyBasicShapeOrPath = true;
451 break;
452 case ClipPathType::None:
453 MOZ_ASSERT(!usage.mShouldGenerateClipMaskLayer &&
454 !usage.mShouldApplyClipPath &&
455 !usage.mShouldApplyBasicShapeOrPath);
456 break;
457 default:
458 MOZ_ASSERT_UNREACHABLE("Unsupported clip-path type.");
459 break;
461 return usage;
464 class MixModeBlender {
465 public:
466 using Factory = gfx::Factory;
468 MixModeBlender(nsIFrame* aFrame, gfxContext* aContext)
469 : mFrame(aFrame), mSourceCtx(aContext) {
470 MOZ_ASSERT(mFrame && mSourceCtx);
473 bool ShouldCreateDrawTargetForBlend() const {
474 return mFrame->StyleEffects()->HasMixBlendMode();
477 gfxContext* CreateBlendTarget(const gfxMatrix& aTransform) {
478 MOZ_ASSERT(ShouldCreateDrawTargetForBlend());
480 // Create a temporary context to draw to so we can blend it back with
481 // another operator.
482 IntRect drawRect = ComputeClipExtsInDeviceSpace(aTransform);
483 if (drawRect.IsEmpty()) {
484 return nullptr;
487 RefPtr<DrawTarget> targetDT =
488 mSourceCtx->GetDrawTarget()->CreateSimilarDrawTarget(
489 drawRect.Size(), SurfaceFormat::B8G8R8A8);
490 if (!targetDT || !targetDT->IsValid()) {
491 return nullptr;
494 MOZ_ASSERT(!mTargetCtx,
495 "CreateBlendTarget is designed to be used once only.");
497 mTargetCtx = gfxContext::CreateOrNull(targetDT);
498 MOZ_ASSERT(mTargetCtx); // already checked the draw target above
499 mTargetCtx->SetMatrix(mSourceCtx->CurrentMatrix() *
500 Matrix::Translation(-drawRect.TopLeft()));
502 mTargetOffset = drawRect.TopLeft();
504 return mTargetCtx.get();
507 void BlendToTarget() {
508 MOZ_ASSERT(ShouldCreateDrawTargetForBlend());
509 MOZ_ASSERT(mTargetCtx,
510 "BlendToTarget should be used after CreateBlendTarget.");
512 RefPtr<SourceSurface> targetSurf = mTargetCtx->GetDrawTarget()->Snapshot();
514 gfxContextAutoSaveRestore save(mSourceCtx);
515 mSourceCtx->SetMatrix(Matrix()); // This will be restored right after.
516 RefPtr<gfxPattern> pattern = new gfxPattern(
517 targetSurf, Matrix::Translation(mTargetOffset.x, mTargetOffset.y));
518 mSourceCtx->SetPattern(pattern);
519 mSourceCtx->Paint();
522 private:
523 MixModeBlender() = delete;
525 IntRect ComputeClipExtsInDeviceSpace(const gfxMatrix& aTransform) {
526 // These are used if we require a temporary surface for a custom blend
527 // mode. Clip the source context first, so that we can generate a smaller
528 // temporary surface. (Since we will clip this context in
529 // SetupContextMatrix, a pair of save/restore is needed.)
530 gfxContextAutoSaveRestore saver;
532 if (!mFrame->HasAnyStateBits(NS_FRAME_IS_NONDISPLAY)) {
533 saver.SetContext(mSourceCtx);
534 // aFrame has a valid ink overflow rect, so clip to it before calling
535 // PushGroup() to minimize the size of the surfaces we'll composite:
536 gfxContextMatrixAutoSaveRestore matrixAutoSaveRestore(mSourceCtx);
537 mSourceCtx->Multiply(aTransform);
538 nsRect overflowRect = mFrame->InkOverflowRectRelativeToSelf();
539 if (FrameDoesNotIncludePositionInTM(mFrame)) {
540 overflowRect = overflowRect + mFrame->GetPosition();
542 mSourceCtx->Clip(NSRectToSnappedRect(
543 overflowRect, mFrame->PresContext()->AppUnitsPerDevPixel(),
544 *mSourceCtx->GetDrawTarget()));
547 // Get the clip extents in device space.
548 gfxRect clippedFrameSurfaceRect =
549 mSourceCtx->GetClipExtents(gfxContext::eDeviceSpace);
550 clippedFrameSurfaceRect.RoundOut();
552 IntRect result;
553 ToRect(clippedFrameSurfaceRect).ToIntRect(&result);
555 return Factory::CheckSurfaceSize(result.Size()) ? result : IntRect();
558 nsIFrame* mFrame;
559 gfxContext* mSourceCtx;
560 UniquePtr<gfxContext> mTargetCtx;
561 IntPoint mTargetOffset;
564 void SVGUtils::PaintFrameWithEffects(nsIFrame* aFrame, gfxContext& aContext,
565 const gfxMatrix& aTransform,
566 imgDrawingParams& aImgParams) {
567 NS_ASSERTION(aFrame->HasAnyStateBits(NS_FRAME_IS_NONDISPLAY) ||
568 aFrame->PresContext()->Document()->IsSVGGlyphsDocument(),
569 "Only painting of non-display SVG should take this code path");
571 ISVGDisplayableFrame* svgFrame = do_QueryFrame(aFrame);
572 if (!svgFrame) {
573 return;
576 MaskUsage maskUsage = DetermineMaskUsage(aFrame, true);
577 if (maskUsage.IsTransparent()) {
578 return;
581 if (auto* svg = SVGElement::FromNode(aFrame->GetContent())) {
582 if (!svg->HasValidDimensions()) {
583 return;
587 /* SVG defines the following rendering model:
589 * 1. Render fill
590 * 2. Render stroke
591 * 3. Render markers
592 * 4. Apply filter
593 * 5. Apply clipping, masking, group opacity
595 * We follow this, but perform a couple of optimizations:
597 * + Use cairo's clipPath when representable natively (single object
598 * clip region).
600 * + Merge opacity and masking if both used together.
603 /* Properties are added lazily and may have been removed by a restyle,
604 so make sure all applicable ones are set again. */
605 SVGClipPathFrame* clipPathFrame;
606 nsTArray<SVGMaskFrame*> maskFrames;
607 nsTArray<SVGFilterFrame*> filterFrames;
608 const bool hasInvalidFilter =
609 SVGObserverUtils::GetAndObserveFilters(aFrame, &filterFrames) ==
610 SVGObserverUtils::eHasRefsSomeInvalid;
611 SVGObserverUtils::GetAndObserveClipPath(aFrame, &clipPathFrame);
612 SVGObserverUtils::GetAndObserveMasks(aFrame, &maskFrames);
614 SVGMaskFrame* maskFrame = maskFrames.IsEmpty() ? nullptr : maskFrames[0];
616 MixModeBlender blender(aFrame, &aContext);
617 gfxContext* target = blender.ShouldCreateDrawTargetForBlend()
618 ? blender.CreateBlendTarget(aTransform)
619 : &aContext;
621 if (!target) {
622 return;
625 /* Check if we need to do additional operations on this child's
626 * rendering, which necessitates rendering into another surface. */
627 bool shouldPushMask = false;
629 if (maskUsage.ShouldGenerateMask()) {
630 RefPtr<SourceSurface> maskSurface;
632 // maskFrame can be nullptr even if maskUsage.ShouldGenerateMaskLayer() is
633 // true. That happens when a user gives an unresolvable mask-id, such as
634 // mask:url()
635 // mask:url(#id-which-does-not-exist)
636 // Since we only uses SVGUtils with SVG elements, not like mask on an
637 // HTML element, we should treat an unresolvable mask as no-mask here.
638 if (maskUsage.ShouldGenerateMaskLayer() && maskFrame) {
639 StyleMaskMode maskMode =
640 aFrame->StyleSVGReset()->mMask.mLayers[0].mMaskMode;
641 SVGMaskFrame::MaskParams params(aContext.GetDrawTarget(), aFrame,
642 aTransform, maskUsage.Opacity(), maskMode,
643 aImgParams);
645 maskSurface = maskFrame->GetMaskForMaskedFrame(params);
647 if (!maskSurface) {
648 // Either entire surface is clipped out, or gfx buffer allocation
649 // failure in SVGMaskFrame::GetMaskForMaskedFrame.
650 return;
652 shouldPushMask = true;
655 if (maskUsage.ShouldGenerateClipMaskLayer()) {
656 RefPtr<SourceSurface> clipMaskSurface =
657 clipPathFrame->GetClipMask(aContext, aFrame, aTransform, maskSurface);
658 if (clipMaskSurface) {
659 maskSurface = clipMaskSurface;
660 } else {
661 // Either entire surface is clipped out, or gfx buffer allocation
662 // failure in SVGClipPathFrame::GetClipMask.
663 return;
665 shouldPushMask = true;
668 if (!maskUsage.ShouldGenerateLayer()) {
669 shouldPushMask = true;
672 // SVG mask multiply opacity into maskSurface already, so we do not bother
673 // to apply opacity again.
674 if (shouldPushMask) {
675 // We want the mask to be untransformed so use the inverse of the
676 // current transform as the maskTransform to compensate.
677 Matrix maskTransform = aContext.CurrentMatrix();
678 maskTransform.Invert();
679 target->PushGroupForBlendBack(gfxContentType::COLOR_ALPHA,
680 maskFrame ? 1.0f : maskUsage.Opacity(),
681 maskSurface, maskTransform);
685 /* If this frame has only a trivial clipPath, set up cairo's clipping now so
686 * we can just do normal painting and get it clipped appropriately.
688 if (maskUsage.ShouldApplyClipPath() ||
689 maskUsage.ShouldApplyBasicShapeOrPath()) {
690 if (maskUsage.ShouldApplyClipPath()) {
691 clipPathFrame->ApplyClipPath(aContext, aFrame, aTransform);
692 } else {
693 CSSClipPathInstance::ApplyBasicShapeOrPathClip(aContext, aFrame,
694 aTransform);
698 /* Paint the child */
700 // Invalid filters should render the unfiltered contents per spec.
701 if (aFrame->StyleEffects()->HasFilters() && !hasInvalidFilter) {
702 gfxContextMatrixAutoSaveRestore autoSR(target);
704 // 'target' is currently scaled such that its user space units are CSS
705 // pixels (SVG user space units). But PaintFilteredFrame expects it to be
706 // scaled in such a way that its user space units are device pixels. So we
707 // have to adjust the scale.
708 gfxMatrix reverseScaleMatrix = SVGUtils::GetCSSPxToDevPxMatrix(aFrame);
709 DebugOnly<bool> invertible = reverseScaleMatrix.Invert();
710 target->SetMatrixDouble(reverseScaleMatrix * aTransform *
711 target->CurrentMatrixDouble());
713 auto callback = [&](gfxContext& aContext, imgDrawingParams& aImgParams,
714 const gfxMatrix* aFilterTransform,
715 const nsIntRect* aDirtyRect) {
716 svgFrame->PaintSVG(aContext,
717 aFilterTransform
718 ? SVGUtils::GetCSSPxToDevPxMatrix(aFrame)
719 : aTransform,
720 aImgParams);
722 // If we're masking a userSpaceOnUse mask we may need to include the
723 // stroke too. Err on the side of caution and include it always.
724 gfxRect bbox = GetBBox(aFrame, SVGUtils::eUseFrameBoundsForOuterSVG |
725 SVGUtils::eBBoxIncludeFillGeometry |
726 SVGUtils::eBBoxIncludeStroke);
727 FilterInstance::PaintFilteredFrame(
728 aFrame, aFrame->StyleEffects()->mFilters.AsSpan(), filterFrames, target,
729 callback, nullptr, aImgParams, 1.0f, &bbox);
730 } else {
731 svgFrame->PaintSVG(*target, aTransform, aImgParams);
734 if (maskUsage.ShouldApplyClipPath() ||
735 maskUsage.ShouldApplyBasicShapeOrPath()) {
736 aContext.PopClip();
739 if (shouldPushMask) {
740 target->PopGroupAndBlend();
743 if (blender.ShouldCreateDrawTargetForBlend()) {
744 MOZ_ASSERT(target != &aContext);
745 blender.BlendToTarget();
749 bool SVGUtils::HitTestClip(nsIFrame* aFrame, const gfxPoint& aPoint) {
750 const nsStyleSVGReset* svgReset = aFrame->StyleSVGReset();
751 if (!svgReset->HasClipPath()) {
752 return true;
754 if (svgReset->mClipPath.IsUrl()) {
755 // If the clip-path property references non-existent or invalid clipPath
756 // element(s) we ignore it.
757 SVGClipPathFrame* clipPathFrame;
758 SVGObserverUtils::GetAndObserveClipPath(aFrame, &clipPathFrame);
759 return !clipPathFrame ||
760 clipPathFrame->PointIsInsideClipPath(aFrame, aPoint);
762 return CSSClipPathInstance::HitTestBasicShapeOrPathClip(aFrame, aPoint);
765 IntSize SVGUtils::ConvertToSurfaceSize(const gfxSize& aSize,
766 bool* aResultOverflows) {
767 IntSize surfaceSize(ClampToInt(ceil(aSize.width)),
768 ClampToInt(ceil(aSize.height)));
770 *aResultOverflows = surfaceSize.width != ceil(aSize.width) ||
771 surfaceSize.height != ceil(aSize.height);
773 if (!Factory::AllowedSurfaceSize(surfaceSize)) {
774 surfaceSize.width =
775 std::min(NS_SVG_OFFSCREEN_MAX_DIMENSION, surfaceSize.width);
776 surfaceSize.height =
777 std::min(NS_SVG_OFFSCREEN_MAX_DIMENSION, surfaceSize.height);
778 *aResultOverflows = true;
781 return surfaceSize;
784 bool SVGUtils::HitTestRect(const gfx::Matrix& aMatrix, float aRX, float aRY,
785 float aRWidth, float aRHeight, float aX, float aY) {
786 gfx::Rect rect(aRX, aRY, aRWidth, aRHeight);
787 if (rect.IsEmpty() || aMatrix.IsSingular()) {
788 return false;
790 gfx::Matrix toRectSpace = aMatrix;
791 toRectSpace.Invert();
792 gfx::Point p = toRectSpace.TransformPoint(gfx::Point(aX, aY));
793 return rect.x <= p.x && p.x <= rect.XMost() && rect.y <= p.y &&
794 p.y <= rect.YMost();
797 gfxRect SVGUtils::GetClipRectForFrame(const nsIFrame* aFrame, float aX,
798 float aY, float aWidth, float aHeight) {
799 const nsStyleDisplay* disp = aFrame->StyleDisplay();
800 const nsStyleEffects* effects = aFrame->StyleEffects();
802 bool clipApplies = disp->mOverflowX == StyleOverflow::Hidden ||
803 disp->mOverflowY == StyleOverflow::Hidden;
805 if (!clipApplies || effects->mClip.IsAuto()) {
806 return gfxRect(aX, aY, aWidth, aHeight);
809 const auto& rect = effects->mClip.AsRect();
810 nsRect coordClipRect = rect.ToLayoutRect();
811 nsIntRect clipPxRect = coordClipRect.ToOutsidePixels(
812 aFrame->PresContext()->AppUnitsPerDevPixel());
813 gfxRect clipRect =
814 gfxRect(clipPxRect.x, clipPxRect.y, clipPxRect.width, clipPxRect.height);
815 if (rect.right.IsAuto()) {
816 clipRect.width = aWidth - clipRect.X();
818 if (rect.bottom.IsAuto()) {
819 clipRect.height = aHeight - clipRect.Y();
821 if (disp->mOverflowX != StyleOverflow::Hidden) {
822 clipRect.x = aX;
823 clipRect.width = aWidth;
825 if (disp->mOverflowY != StyleOverflow::Hidden) {
826 clipRect.y = aY;
827 clipRect.height = aHeight;
829 return clipRect;
832 gfxRect SVGUtils::GetBBox(nsIFrame* aFrame, uint32_t aFlags,
833 const gfxMatrix* aToBoundsSpace) {
834 if (aFrame->IsTextFrame()) {
835 aFrame = aFrame->GetParent();
838 if (aFrame->IsInSVGTextSubtree()) {
839 // It is possible to apply a gradient, pattern, clipping path, mask or
840 // filter to text. When one of these facilities is applied to text
841 // the bounding box is the entire text element in all cases.
842 aFrame =
843 nsLayoutUtils::GetClosestFrameOfType(aFrame, LayoutFrameType::SVGText);
846 ISVGDisplayableFrame* svg = do_QueryFrame(aFrame);
847 const bool hasSVGLayout = aFrame->HasAnyStateBits(NS_FRAME_SVG_LAYOUT);
848 if (hasSVGLayout && !svg) {
849 // An SVG frame, but not one that can be displayed directly (for
850 // example, nsGradientFrame). These can't contribute to the bbox.
851 return gfxRect();
854 const bool isOuterSVG = svg && !hasSVGLayout;
855 MOZ_ASSERT(!isOuterSVG || aFrame->IsSVGOuterSVGFrame());
856 if (!svg || (isOuterSVG && (aFlags & eUseFrameBoundsForOuterSVG))) {
857 // An HTML element or an SVG outer frame.
858 MOZ_ASSERT(!hasSVGLayout);
859 bool onlyCurrentFrame = aFlags & eIncludeOnlyCurrentFrameForNonSVGElement;
860 return SVGIntegrationUtils::GetSVGBBoxForNonSVGFrame(
861 aFrame,
862 /* aUnionContinuations = */ !onlyCurrentFrame);
865 MOZ_ASSERT(svg);
867 if (auto* element = SVGElement::FromNodeOrNull(aFrame->GetContent())) {
868 if (!element->HasValidDimensions()) {
869 return gfxRect();
873 // Clean out flags which have no effects on returning bbox from now, so that
874 // we can cache and reuse ObjectBoundingBoxProperty() in the code below.
875 aFlags &=
876 ~(eIncludeOnlyCurrentFrameForNonSVGElement | eUseFrameBoundsForOuterSVG);
877 if (!aFrame->IsSVGUseFrame()) {
878 aFlags &= ~eUseUserSpaceOfUseElement;
881 if (aFlags == eBBoxIncludeFillGeometry &&
882 // We only cache bbox in element's own user space
883 !aToBoundsSpace) {
884 gfxRect* prop = aFrame->GetProperty(ObjectBoundingBoxProperty());
885 if (prop) {
886 return *prop;
890 gfxMatrix matrix;
891 if (aToBoundsSpace) {
892 matrix = *aToBoundsSpace;
895 if (aFrame->IsSVGForeignObjectFrame() ||
896 aFlags & SVGUtils::eUseUserSpaceOfUseElement) {
897 // The spec says getBBox "Returns the tight bounding box in *current user
898 // space*". So we should really be doing this for all elements, but that
899 // needs investigation to check that we won't break too much content.
900 // NOTE: When changing this to apply to other frame types, make sure to
901 // also update SVGUtils::FrameSpaceInCSSPxToUserSpaceOffset.
902 MOZ_ASSERT(aFrame->GetContent()->IsSVGElement(), "bad cast");
903 SVGElement* element = static_cast<SVGElement*>(aFrame->GetContent());
904 matrix = element->PrependLocalTransformsTo(matrix, eChildToUserSpace);
906 gfxRect bbox =
907 svg->GetBBoxContribution(ToMatrix(matrix), aFlags).ToThebesRect();
908 // Account for 'clipped'.
909 if (aFlags & SVGUtils::eBBoxIncludeClipped) {
910 gfxRect clipRect;
911 float x, y, width, height;
912 gfxRect fillBBox =
913 svg->GetBBoxContribution({}, SVGUtils::eBBoxIncludeFill).ToThebesRect();
914 x = fillBBox.x;
915 y = fillBBox.y;
916 width = fillBBox.width;
917 height = fillBBox.height;
918 // XXX Should probably check for overflow: clip too.
919 bool hasClip = aFrame->StyleDisplay()->IsScrollableOverflow();
920 if (hasClip) {
921 clipRect = SVGUtils::GetClipRectForFrame(aFrame, x, y, width, height);
922 if (aFrame->IsSVGForeignObjectFrame() || aFrame->IsSVGUseFrame()) {
923 clipRect = matrix.TransformBounds(clipRect);
926 SVGClipPathFrame* clipPathFrame;
927 if (SVGObserverUtils::GetAndObserveClipPath(aFrame, &clipPathFrame) ==
928 SVGObserverUtils::eHasRefsSomeInvalid) {
929 bbox = gfxRect(0, 0, 0, 0);
930 } else {
931 if (clipPathFrame) {
932 SVGClipPathElement* clipContent =
933 static_cast<SVGClipPathElement*>(clipPathFrame->GetContent());
934 if (clipContent->IsUnitsObjectBoundingBox()) {
935 matrix.PreTranslate(gfxPoint(x, y));
936 matrix.PreScale(width, height);
937 } else if (aFrame->IsSVGForeignObjectFrame()) {
938 matrix = gfxMatrix();
940 matrix *= SVGUtils::GetTransformMatrixInUserSpace(clipPathFrame);
942 bbox = clipPathFrame->GetBBoxForClipPathFrame(bbox, matrix, aFlags)
943 .ToThebesRect();
946 if (hasClip) {
947 bbox = bbox.Intersect(clipRect);
950 if (bbox.IsEmpty()) {
951 bbox = gfxRect(0, 0, 0, 0);
956 if (aFlags == eBBoxIncludeFillGeometry &&
957 // We only cache bbox in element's own user space
958 !aToBoundsSpace) {
959 // Obtaining the bbox for objectBoundingBox calculations is common so we
960 // cache the result for future calls, since calculation can be expensive:
961 aFrame->SetProperty(ObjectBoundingBoxProperty(), new gfxRect(bbox));
964 return bbox;
967 gfxPoint SVGUtils::FrameSpaceInCSSPxToUserSpaceOffset(const nsIFrame* aFrame) {
968 if (!aFrame->HasAnyStateBits(NS_FRAME_SVG_LAYOUT)) {
969 // The user space for non-SVG frames is defined as the bounding box of the
970 // frame's border-box rects over all continuations.
971 return gfxPoint();
974 // Leaf frames apply their own offset inside their user space.
975 if (FrameDoesNotIncludePositionInTM(aFrame)) {
976 return nsLayoutUtils::RectToGfxRect(aFrame->GetRect(),
977 AppUnitsPerCSSPixel())
978 .TopLeft();
981 // For foreignObject frames, SVGUtils::GetBBox applies their local
982 // transform, so we need to do the same here.
983 if (aFrame->IsSVGForeignObjectFrame()) {
984 gfxMatrix transform =
985 static_cast<SVGElement*>(aFrame->GetContent())
986 ->PrependLocalTransformsTo(gfxMatrix(), eChildToUserSpace);
987 NS_ASSERTION(!transform.HasNonTranslation(),
988 "we're relying on this being an offset-only transform");
989 return transform.GetTranslation();
992 return gfxPoint();
995 static gfxRect GetBoundingBoxRelativeRect(const SVGAnimatedLength* aXYWH,
996 const gfxRect& aBBox) {
997 return gfxRect(aBBox.x + SVGUtils::ObjectSpace(aBBox, &aXYWH[0]),
998 aBBox.y + SVGUtils::ObjectSpace(aBBox, &aXYWH[1]),
999 SVGUtils::ObjectSpace(aBBox, &aXYWH[2]),
1000 SVGUtils::ObjectSpace(aBBox, &aXYWH[3]));
1003 gfxRect SVGUtils::GetRelativeRect(uint16_t aUnits,
1004 const SVGAnimatedLength* aXYWH,
1005 const gfxRect& aBBox,
1006 const UserSpaceMetrics& aMetrics) {
1007 if (aUnits == SVG_UNIT_TYPE_OBJECTBOUNDINGBOX) {
1008 return GetBoundingBoxRelativeRect(aXYWH, aBBox);
1010 return gfxRect(UserSpace(aMetrics, &aXYWH[0]), UserSpace(aMetrics, &aXYWH[1]),
1011 UserSpace(aMetrics, &aXYWH[2]),
1012 UserSpace(aMetrics, &aXYWH[3]));
1015 gfxRect SVGUtils::GetRelativeRect(uint16_t aUnits,
1016 const SVGAnimatedLength* aXYWH,
1017 const gfxRect& aBBox, nsIFrame* aFrame) {
1018 if (aUnits == SVG_UNIT_TYPE_OBJECTBOUNDINGBOX) {
1019 return GetBoundingBoxRelativeRect(aXYWH, aBBox);
1021 if (SVGElement* svgElement = SVGElement::FromNode(aFrame->GetContent())) {
1022 return GetRelativeRect(aUnits, aXYWH, aBBox, SVGElementMetrics(svgElement));
1024 return GetRelativeRect(aUnits, aXYWH, aBBox,
1025 NonSVGFrameUserSpaceMetrics(aFrame));
1028 bool SVGUtils::CanOptimizeOpacity(const nsIFrame* aFrame) {
1029 if (!aFrame->HasAnyStateBits(NS_FRAME_SVG_LAYOUT)) {
1030 return false;
1032 auto* content = aFrame->GetContent();
1033 if (!content->IsSVGGeometryElement() &&
1034 !content->IsSVGElement(nsGkAtoms::image)) {
1035 return false;
1037 if (aFrame->StyleEffects()->HasFilters()) {
1038 return false;
1040 // XXX The SVG WG is intending to allow fill, stroke and markers on <image>
1041 if (content->IsSVGElement(nsGkAtoms::image)) {
1042 return true;
1044 const nsStyleSVG* style = aFrame->StyleSVG();
1045 if (style->HasMarker() &&
1046 static_cast<SVGGeometryElement*>(content)->IsMarkable()) {
1047 return false;
1050 if (nsLayoutUtils::HasAnimationOfPropertySet(
1051 aFrame, nsCSSPropertyIDSet::OpacityProperties())) {
1052 return false;
1055 return !style->HasFill() || !HasStroke(aFrame);
1058 gfxMatrix SVGUtils::AdjustMatrixForUnits(const gfxMatrix& aMatrix,
1059 const SVGAnimatedEnumeration* aUnits,
1060 nsIFrame* aFrame, uint32_t aFlags) {
1061 if (aFrame && aUnits->GetAnimValue() == SVG_UNIT_TYPE_OBJECTBOUNDINGBOX) {
1062 gfxRect bbox = GetBBox(aFrame, aFlags);
1063 gfxMatrix tm = aMatrix;
1064 tm.PreTranslate(gfxPoint(bbox.X(), bbox.Y()));
1065 tm.PreScale(bbox.Width(), bbox.Height());
1066 return tm;
1068 return aMatrix;
1071 bool SVGUtils::GetNonScalingStrokeTransform(const nsIFrame* aFrame,
1072 gfxMatrix* aUserToOuterSVG) {
1073 if (aFrame->GetContent()->IsText()) {
1074 aFrame = aFrame->GetParent();
1077 if (!aFrame->StyleSVGReset()->HasNonScalingStroke()) {
1078 return false;
1081 MOZ_ASSERT(aFrame->GetContent()->IsSVGElement(), "should be an SVG element");
1083 *aUserToOuterSVG = ThebesMatrix(SVGContentUtils::GetCTM(
1084 static_cast<SVGElement*>(aFrame->GetContent()), true));
1086 return aUserToOuterSVG->HasNonTranslation();
1089 // The logic here comes from _cairo_stroke_style_max_distance_from_path
1090 static gfxRect PathExtentsToMaxStrokeExtents(const gfxRect& aPathExtents,
1091 const nsIFrame* aFrame,
1092 double aStyleExpansionFactor,
1093 const gfxMatrix& aMatrix) {
1094 double style_expansion =
1095 aStyleExpansionFactor * SVGUtils::GetStrokeWidth(aFrame);
1097 gfxMatrix matrix = aMatrix;
1099 gfxMatrix outerSVGToUser;
1100 if (SVGUtils::GetNonScalingStrokeTransform(aFrame, &outerSVGToUser)) {
1101 outerSVGToUser.Invert();
1102 matrix.PreMultiply(outerSVGToUser);
1105 double dx = style_expansion * (fabs(matrix._11) + fabs(matrix._21));
1106 double dy = style_expansion * (fabs(matrix._22) + fabs(matrix._12));
1108 gfxRect strokeExtents = aPathExtents;
1109 strokeExtents.Inflate(dx, dy);
1110 return strokeExtents;
1113 /*static*/
1114 gfxRect SVGUtils::PathExtentsToMaxStrokeExtents(const gfxRect& aPathExtents,
1115 const nsTextFrame* aFrame,
1116 const gfxMatrix& aMatrix) {
1117 NS_ASSERTION(aFrame->IsInSVGTextSubtree(),
1118 "expected an nsTextFrame for SVG text");
1119 return mozilla::PathExtentsToMaxStrokeExtents(aPathExtents, aFrame, 0.5,
1120 aMatrix);
1123 /*static*/
1124 gfxRect SVGUtils::PathExtentsToMaxStrokeExtents(const gfxRect& aPathExtents,
1125 const SVGGeometryFrame* aFrame,
1126 const gfxMatrix& aMatrix) {
1127 bool strokeMayHaveCorners =
1128 !SVGContentUtils::ShapeTypeHasNoCorners(aFrame->GetContent());
1130 // For a shape without corners the stroke can only extend half the stroke
1131 // width from the path in the x/y-axis directions. For shapes with corners
1132 // the stroke can extend by sqrt(1/2) (think 45 degree rotated rect, or line
1133 // with stroke-linecaps="square").
1134 double styleExpansionFactor = strokeMayHaveCorners ? M_SQRT1_2 : 0.5;
1136 // The stroke can extend even further for paths that can be affected by
1137 // stroke-miterlimit.
1138 // We only need to do this if the limit is greater than 1, but it's probably
1139 // not worth optimizing for that.
1140 bool affectedByMiterlimit = aFrame->GetContent()->IsAnyOfSVGElements(
1141 nsGkAtoms::path, nsGkAtoms::polyline, nsGkAtoms::polygon);
1143 if (affectedByMiterlimit) {
1144 const nsStyleSVG* style = aFrame->StyleSVG();
1145 if (style->mStrokeLinejoin == StyleStrokeLinejoin::Miter &&
1146 styleExpansionFactor < style->mStrokeMiterlimit / 2.0) {
1147 styleExpansionFactor = style->mStrokeMiterlimit / 2.0;
1151 return mozilla::PathExtentsToMaxStrokeExtents(aPathExtents, aFrame,
1152 styleExpansionFactor, aMatrix);
1155 // ----------------------------------------------------------------------
1157 /* static */
1158 nscolor SVGUtils::GetFallbackOrPaintColor(
1159 const ComputedStyle& aStyle, StyleSVGPaint nsStyleSVG::*aFillOrStroke,
1160 nscolor aDefaultContextFallbackColor) {
1161 const auto& paint = aStyle.StyleSVG()->*aFillOrStroke;
1162 nscolor color;
1163 switch (paint.kind.tag) {
1164 case StyleSVGPaintKind::Tag::PaintServer:
1165 color = paint.fallback.IsColor()
1166 ? paint.fallback.AsColor().CalcColor(aStyle)
1167 : NS_RGBA(0, 0, 0, 0);
1168 break;
1169 case StyleSVGPaintKind::Tag::ContextStroke:
1170 case StyleSVGPaintKind::Tag::ContextFill:
1171 color = paint.fallback.IsColor()
1172 ? paint.fallback.AsColor().CalcColor(aStyle)
1173 : aDefaultContextFallbackColor;
1174 break;
1175 default:
1176 color = paint.kind.AsColor().CalcColor(aStyle);
1177 break;
1179 if (const auto* styleIfVisited = aStyle.GetStyleIfVisited()) {
1180 const auto& paintIfVisited = styleIfVisited->StyleSVG()->*aFillOrStroke;
1181 // To prevent Web content from detecting if a user has visited a URL
1182 // (via URL loading triggered by paint servers or performance
1183 // differences between paint servers or between a paint server and a
1184 // color), we do not allow whether links are visited to change which
1185 // paint server is used or switch between paint servers and simple
1186 // colors. A :visited style may only override a simple color with
1187 // another simple color.
1188 if (paintIfVisited.kind.IsColor() && paint.kind.IsColor()) {
1189 nscolor colors[2] = {
1190 color, paintIfVisited.kind.AsColor().CalcColor(*styleIfVisited)};
1191 return ComputedStyle::CombineVisitedColors(colors,
1192 aStyle.RelevantLinkVisited());
1195 return color;
1198 /* static */
1199 void SVGUtils::MakeFillPatternFor(nsIFrame* aFrame, gfxContext* aContext,
1200 GeneralPattern* aOutPattern,
1201 imgDrawingParams& aImgParams,
1202 SVGContextPaint* aContextPaint) {
1203 const nsStyleSVG* style = aFrame->StyleSVG();
1204 if (style->mFill.kind.IsNone()) {
1205 return;
1208 const auto* styleEffects = aFrame->StyleEffects();
1210 float fillOpacity = GetOpacity(style->mFillOpacity, aContextPaint);
1211 if (!styleEffects->IsOpaque() && SVGUtils::CanOptimizeOpacity(aFrame)) {
1212 // Combine the group opacity into the fill opacity (we will have skipped
1213 // creating an offscreen surface to apply the group opacity).
1214 fillOpacity *= styleEffects->mOpacity;
1217 const DrawTarget* dt = aContext->GetDrawTarget();
1219 SVGPaintServerFrame* ps =
1220 SVGObserverUtils::GetAndObservePaintServer(aFrame, &nsStyleSVG::mFill);
1222 if (ps) {
1223 RefPtr<gfxPattern> pattern =
1224 ps->GetPaintServerPattern(aFrame, dt, aContext->CurrentMatrixDouble(),
1225 &nsStyleSVG::mFill, fillOpacity, aImgParams);
1226 if (pattern) {
1227 pattern->CacheColorStops(dt);
1228 aOutPattern->Init(*pattern->GetPattern(dt));
1229 return;
1233 if (aContextPaint) {
1234 RefPtr<gfxPattern> pattern;
1235 switch (style->mFill.kind.tag) {
1236 case StyleSVGPaintKind::Tag::ContextFill:
1237 pattern = aContextPaint->GetFillPattern(
1238 dt, fillOpacity, aContext->CurrentMatrixDouble(), aImgParams);
1239 break;
1240 case StyleSVGPaintKind::Tag::ContextStroke:
1241 pattern = aContextPaint->GetStrokePattern(
1242 dt, fillOpacity, aContext->CurrentMatrixDouble(), aImgParams);
1243 break;
1244 default:;
1246 if (pattern) {
1247 aOutPattern->Init(*pattern->GetPattern(dt));
1248 return;
1252 if (style->mFill.fallback.IsNone()) {
1253 return;
1256 // On failure, use the fallback colour in case we have an
1257 // objectBoundingBox where the width or height of the object is zero.
1258 // See http://www.w3.org/TR/SVG11/coords.html#ObjectBoundingBox
1259 sRGBColor color(sRGBColor::FromABGR(GetFallbackOrPaintColor(
1260 *aFrame->Style(), &nsStyleSVG::mFill, NS_RGB(0, 0, 0))));
1261 color.a *= fillOpacity;
1262 aOutPattern->InitColorPattern(ToDeviceColor(color));
1265 /* static */
1266 void SVGUtils::MakeStrokePatternFor(nsIFrame* aFrame, gfxContext* aContext,
1267 GeneralPattern* aOutPattern,
1268 imgDrawingParams& aImgParams,
1269 SVGContextPaint* aContextPaint) {
1270 const nsStyleSVG* style = aFrame->StyleSVG();
1271 if (style->mStroke.kind.IsNone()) {
1272 return;
1275 const auto* styleEffects = aFrame->StyleEffects();
1277 float strokeOpacity = GetOpacity(style->mStrokeOpacity, aContextPaint);
1278 if (!styleEffects->IsOpaque() && SVGUtils::CanOptimizeOpacity(aFrame)) {
1279 // Combine the group opacity into the stroke opacity (we will have skipped
1280 // creating an offscreen surface to apply the group opacity).
1281 strokeOpacity *= styleEffects->mOpacity;
1284 const DrawTarget* dt = aContext->GetDrawTarget();
1286 SVGPaintServerFrame* ps =
1287 SVGObserverUtils::GetAndObservePaintServer(aFrame, &nsStyleSVG::mStroke);
1289 if (ps) {
1290 RefPtr<gfxPattern> pattern = ps->GetPaintServerPattern(
1291 aFrame, dt, aContext->CurrentMatrixDouble(), &nsStyleSVG::mStroke,
1292 strokeOpacity, aImgParams);
1293 if (pattern) {
1294 pattern->CacheColorStops(dt);
1295 aOutPattern->Init(*pattern->GetPattern(dt));
1296 return;
1300 if (aContextPaint) {
1301 RefPtr<gfxPattern> pattern;
1302 switch (style->mStroke.kind.tag) {
1303 case StyleSVGPaintKind::Tag::ContextFill:
1304 pattern = aContextPaint->GetFillPattern(
1305 dt, strokeOpacity, aContext->CurrentMatrixDouble(), aImgParams);
1306 break;
1307 case StyleSVGPaintKind::Tag::ContextStroke:
1308 pattern = aContextPaint->GetStrokePattern(
1309 dt, strokeOpacity, aContext->CurrentMatrixDouble(), aImgParams);
1310 break;
1311 default:;
1313 if (pattern) {
1314 aOutPattern->Init(*pattern->GetPattern(dt));
1315 return;
1319 if (style->mStroke.fallback.IsNone()) {
1320 return;
1323 // On failure, use the fallback colour in case we have an
1324 // objectBoundingBox where the width or height of the object is zero.
1325 // See http://www.w3.org/TR/SVG11/coords.html#ObjectBoundingBox
1326 sRGBColor color(sRGBColor::FromABGR(GetFallbackOrPaintColor(
1327 *aFrame->Style(), &nsStyleSVG::mStroke, NS_RGBA(0, 0, 0, 0))));
1328 color.a *= strokeOpacity;
1329 aOutPattern->InitColorPattern(ToDeviceColor(color));
1332 /* static */
1333 float SVGUtils::GetOpacity(const StyleSVGOpacity& aOpacity,
1334 const SVGContextPaint* aContextPaint) {
1335 float opacity = 1.0f;
1336 switch (aOpacity.tag) {
1337 case StyleSVGOpacity::Tag::Opacity:
1338 return aOpacity.AsOpacity();
1339 case StyleSVGOpacity::Tag::ContextFillOpacity:
1340 if (aContextPaint) {
1341 opacity = aContextPaint->GetFillOpacity();
1343 break;
1344 case StyleSVGOpacity::Tag::ContextStrokeOpacity:
1345 if (aContextPaint) {
1346 opacity = aContextPaint->GetStrokeOpacity();
1348 break;
1350 return opacity;
1353 bool SVGUtils::HasStroke(const nsIFrame* aFrame,
1354 const SVGContextPaint* aContextPaint) {
1355 const nsStyleSVG* style = aFrame->StyleSVG();
1356 return style->HasStroke() && GetStrokeWidth(aFrame, aContextPaint) > 0;
1359 float SVGUtils::GetStrokeWidth(const nsIFrame* aFrame,
1360 const SVGContextPaint* aContextPaint) {
1361 nsIContent* content = aFrame->GetContent();
1362 if (content->IsText()) {
1363 content = content->GetParent();
1366 auto* ctx = SVGElement::FromNode(content);
1367 return SVGContentUtils::GetStrokeWidth(ctx, aFrame->Style(), aContextPaint);
1370 void SVGUtils::SetupStrokeGeometry(nsIFrame* aFrame, gfxContext* aContext,
1371 SVGContextPaint* aContextPaint) {
1372 MOZ_ASSERT(aFrame->GetContent()->IsSVGElement(), "bad cast");
1373 SVGContentUtils::AutoStrokeOptions strokeOptions;
1374 SVGContentUtils::GetStrokeOptions(&strokeOptions,
1375 SVGElement::FromNode(aFrame->GetContent()),
1376 aFrame->Style(), aContextPaint);
1378 if (strokeOptions.mLineWidth <= 0) {
1379 return;
1382 // SVGContentUtils::GetStrokeOptions gets the stroke options in CSS px;
1383 // convert to device pixels for gfxContext.
1384 float devPxPerCSSPx = aFrame->PresContext()->CSSToDevPixelScale().scale;
1386 aContext->SetLineWidth(strokeOptions.mLineWidth * devPxPerCSSPx);
1387 aContext->SetLineCap(strokeOptions.mLineCap);
1388 aContext->SetMiterLimit(strokeOptions.mMiterLimit);
1389 aContext->SetLineJoin(strokeOptions.mLineJoin);
1390 aContext->SetDash(strokeOptions.mDashPattern, strokeOptions.mDashLength,
1391 strokeOptions.mDashOffset, devPxPerCSSPx);
1394 uint16_t SVGUtils::GetGeometryHitTestFlags(const nsIFrame* aFrame) {
1395 uint16_t flags = 0;
1397 switch (aFrame->Style()->PointerEvents()) {
1398 case StylePointerEvents::None:
1399 break;
1400 case StylePointerEvents::Auto:
1401 case StylePointerEvents::Visiblepainted:
1402 if (aFrame->StyleVisibility()->IsVisible()) {
1403 if (!aFrame->StyleSVG()->mFill.kind.IsNone()) {
1404 flags = SVG_HIT_TEST_FILL;
1406 if (!aFrame->StyleSVG()->mStroke.kind.IsNone()) {
1407 flags |= SVG_HIT_TEST_STROKE;
1410 break;
1411 case StylePointerEvents::Visiblefill:
1412 if (aFrame->StyleVisibility()->IsVisible()) {
1413 flags = SVG_HIT_TEST_FILL;
1415 break;
1416 case StylePointerEvents::Visiblestroke:
1417 if (aFrame->StyleVisibility()->IsVisible()) {
1418 flags = SVG_HIT_TEST_STROKE;
1420 break;
1421 case StylePointerEvents::Visible:
1422 if (aFrame->StyleVisibility()->IsVisible()) {
1423 flags = SVG_HIT_TEST_FILL | SVG_HIT_TEST_STROKE;
1425 break;
1426 case StylePointerEvents::Painted:
1427 if (!aFrame->StyleSVG()->mFill.kind.IsNone()) {
1428 flags = SVG_HIT_TEST_FILL;
1430 if (!aFrame->StyleSVG()->mStroke.kind.IsNone()) {
1431 flags |= SVG_HIT_TEST_STROKE;
1433 break;
1434 case StylePointerEvents::Fill:
1435 flags = SVG_HIT_TEST_FILL;
1436 break;
1437 case StylePointerEvents::Stroke:
1438 flags = SVG_HIT_TEST_STROKE;
1439 break;
1440 case StylePointerEvents::All:
1441 flags = SVG_HIT_TEST_FILL | SVG_HIT_TEST_STROKE;
1442 break;
1443 default:
1444 NS_ERROR("not reached");
1445 break;
1448 return flags;
1451 void SVGUtils::PaintSVGGlyph(Element* aElement, gfxContext* aContext) {
1452 nsIFrame* frame = aElement->GetPrimaryFrame();
1453 ISVGDisplayableFrame* svgFrame = do_QueryFrame(frame);
1454 if (!svgFrame) {
1455 return;
1457 gfxMatrix m;
1458 if (frame->GetContent()->IsSVGElement()) {
1459 // PaintSVG() expects the passed transform to be the transform to its own
1460 // SVG user space, so we need to account for any 'transform' attribute:
1461 m = SVGUtils::GetTransformMatrixInUserSpace(frame);
1464 // SVG-in-OpenType is not allowed to paint external resources, so we can
1465 // just pass a dummy params into PatintSVG.
1466 imgDrawingParams dummy;
1467 svgFrame->PaintSVG(*aContext, m, dummy);
1470 bool SVGUtils::GetSVGGlyphExtents(const Element* aElement,
1471 const gfxMatrix& aSVGToAppSpace,
1472 gfxRect* aResult) {
1473 nsIFrame* frame = aElement->GetPrimaryFrame();
1474 ISVGDisplayableFrame* svgFrame = do_QueryFrame(frame);
1475 if (!svgFrame) {
1476 return false;
1479 gfxMatrix transform(aSVGToAppSpace);
1480 if (auto* svg = SVGElement::FromNode(frame->GetContent())) {
1481 transform = svg->PrependLocalTransformsTo(aSVGToAppSpace);
1484 *aResult =
1485 svgFrame
1486 ->GetBBoxContribution(gfx::ToMatrix(transform),
1487 SVGUtils::eBBoxIncludeFill |
1488 SVGUtils::eBBoxIncludeFillGeometry |
1489 SVGUtils::eBBoxIncludeStroke |
1490 SVGUtils::eBBoxIncludeStrokeGeometry |
1491 SVGUtils::eBBoxIncludeMarkers)
1492 .ToThebesRect();
1493 return true;
1496 nsRect SVGUtils::ToCanvasBounds(const gfxRect& aUserspaceRect,
1497 const gfxMatrix& aToCanvas,
1498 const nsPresContext* presContext) {
1499 return nsLayoutUtils::RoundGfxRectToAppRect(
1500 aToCanvas.TransformBounds(aUserspaceRect),
1501 presContext->AppUnitsPerDevPixel());
1504 gfxMatrix SVGUtils::GetCSSPxToDevPxMatrix(const nsIFrame* aNonSVGFrame) {
1505 float devPxPerCSSPx = aNonSVGFrame->PresContext()->CSSToDevPixelScale().scale;
1507 return gfxMatrix(devPxPerCSSPx, 0.0, 0.0, devPxPerCSSPx, 0.0, 0.0);
1510 gfxMatrix SVGUtils::GetTransformMatrixInUserSpace(const nsIFrame* aFrame) {
1511 // We check element instead of aFrame directly because SVG element
1512 // may have non-SVG frame, <tspan> for example.
1513 MOZ_ASSERT(aFrame->GetContent() && aFrame->GetContent()->IsSVGElement(),
1514 "Only use this wrapper for SVG elements");
1516 if (!aFrame->IsTransformed()) {
1517 return {};
1520 nsStyleTransformMatrix::TransformReferenceBox refBox(aFrame);
1521 nsDisplayTransform::FrameTransformProperties properties{
1522 aFrame, refBox, AppUnitsPerCSSPixel()};
1524 // SVG elements can have x/y offset, their default transform origin
1525 // is the origin of user space, not the top left point of the frame.
1526 Point3D svgTransformOrigin{
1527 properties.mToTransformOrigin.x - CSSPixel::FromAppUnits(refBox.X()),
1528 properties.mToTransformOrigin.y - CSSPixel::FromAppUnits(refBox.Y()),
1529 properties.mToTransformOrigin.z};
1531 Matrix svgTransform;
1532 Matrix4x4 trans;
1533 (void)aFrame->IsSVGTransformed(&svgTransform);
1535 if (properties.HasTransform()) {
1536 trans = nsStyleTransformMatrix::ReadTransforms(
1537 properties.mTranslate, properties.mRotate, properties.mScale,
1538 properties.mMotion.ptrOr(nullptr), properties.mTransform, refBox,
1539 AppUnitsPerCSSPixel());
1540 } else {
1541 trans = Matrix4x4::From2D(svgTransform);
1544 trans.ChangeBasis(svgTransformOrigin);
1546 Matrix mm;
1547 trans.ProjectTo2D();
1548 (void)trans.CanDraw2D(&mm);
1550 return ThebesMatrix(mm);
1553 } // namespace mozilla