Backed out 2 changesets (bug 1943998) for causing wd failures @ phases.py CLOSED...
[gecko.git] / layout / svg / SVGUtils.cpp
blob59f281935ece0b2eda62d42a24cdf89617bd37ab
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/PresShell.h"
38 #include "mozilla/StaticPrefs_svg.h"
39 #include "mozilla/SVGClipPathFrame.h"
40 #include "mozilla/SVGContainerFrame.h"
41 #include "mozilla/SVGContentUtils.h"
42 #include "mozilla/SVGContextPaint.h"
43 #include "mozilla/SVGForeignObjectFrame.h"
44 #include "mozilla/SVGIntegrationUtils.h"
45 #include "mozilla/SVGGeometryFrame.h"
46 #include "mozilla/SVGMaskFrame.h"
47 #include "mozilla/SVGObserverUtils.h"
48 #include "mozilla/SVGOuterSVGFrame.h"
49 #include "mozilla/SVGTextFrame.h"
50 #include "mozilla/Unused.h"
51 #include "mozilla/gfx/2D.h"
52 #include "mozilla/gfx/PatternHelpers.h"
53 #include "mozilla/dom/Document.h"
54 #include "mozilla/dom/SVGClipPathElement.h"
55 #include "mozilla/dom/SVGGeometryElement.h"
56 #include "mozilla/dom/SVGPathElement.h"
57 #include "mozilla/dom/SVGUnitTypesBinding.h"
58 #include "mozilla/dom/SVGViewportElement.h"
60 using namespace mozilla::dom;
61 using namespace mozilla::dom::SVGUnitTypes_Binding;
62 using namespace mozilla::gfx;
63 using namespace mozilla::image;
65 bool NS_SVGNewGetBBoxEnabled() {
66 return mozilla::StaticPrefs::svg_new_getBBox_enabled();
69 namespace mozilla {
71 // we only take the address of this:
72 static gfx::UserDataKey sSVGAutoRenderStateKey;
74 SVGAutoRenderState::SVGAutoRenderState(DrawTarget* aDrawTarget)
75 : mDrawTarget(aDrawTarget),
76 mOriginalRenderState(nullptr),
77 mPaintingToWindow(false) {
78 mOriginalRenderState = aDrawTarget->RemoveUserData(&sSVGAutoRenderStateKey);
79 // We always remove ourselves from aContext before it dies, so
80 // passing nullptr as the destroy function is okay.
81 aDrawTarget->AddUserData(&sSVGAutoRenderStateKey, this, nullptr);
84 SVGAutoRenderState::~SVGAutoRenderState() {
85 mDrawTarget->RemoveUserData(&sSVGAutoRenderStateKey);
86 if (mOriginalRenderState) {
87 mDrawTarget->AddUserData(&sSVGAutoRenderStateKey, mOriginalRenderState,
88 nullptr);
92 void SVGAutoRenderState::SetPaintingToWindow(bool aPaintingToWindow) {
93 mPaintingToWindow = aPaintingToWindow;
96 /* static */
97 bool SVGAutoRenderState::IsPaintingToWindow(DrawTarget* aDrawTarget) {
98 void* state = aDrawTarget->GetUserData(&sSVGAutoRenderStateKey);
99 if (state) {
100 return static_cast<SVGAutoRenderState*>(state)->mPaintingToWindow;
102 return false;
105 // Unlike containers, leaf frames do not include GetPosition() in
106 // GetCanvasTM().
107 static bool FrameDoesNotIncludePositionInTM(const nsIFrame* aFrame) {
108 return aFrame->IsSVGGeometryFrame() || aFrame->IsSVGImageFrame() ||
109 aFrame->IsInSVGTextSubtree();
112 nsRect SVGUtils::GetPostFilterInkOverflowRect(nsIFrame* aFrame,
113 const nsRect& aPreFilterRect) {
114 MOZ_ASSERT(aFrame->HasAnyStateBits(NS_FRAME_SVG_LAYOUT),
115 "Called on invalid frame type");
117 // Note: we do not return here for eHasNoRefs since we must still handle any
118 // CSS filter functions.
119 // in that case we disable painting of the element.
120 nsTArray<SVGFilterFrame*> filterFrames;
121 if (!aFrame->StyleEffects()->HasFilters() ||
122 SVGObserverUtils::GetAndObserveFilters(aFrame, &filterFrames) ==
123 SVGObserverUtils::eHasRefsSomeInvalid) {
124 return aPreFilterRect;
127 return FilterInstance::GetPostFilterBounds(aFrame, filterFrames, nullptr,
128 &aPreFilterRect)
129 .valueOr(aPreFilterRect);
132 bool SVGUtils::OuterSVGIsCallingReflowSVG(nsIFrame* aFrame) {
133 return GetOuterSVGFrame(aFrame)->IsCallingReflowSVG();
136 bool SVGUtils::AnyOuterSVGIsCallingReflowSVG(nsIFrame* aFrame) {
137 SVGOuterSVGFrame* outer = GetOuterSVGFrame(aFrame);
138 do {
139 if (outer->IsCallingReflowSVG()) {
140 return true;
142 outer = GetOuterSVGFrame(outer->GetParent());
143 } while (outer);
144 return false;
147 void SVGUtils::ScheduleReflowSVG(nsIFrame* aFrame) {
148 MOZ_ASSERT(aFrame->IsSVGFrame(), "Passed bad frame!");
150 // If this is triggered, the callers should be fixed to call us before
151 // ReflowSVG is called. If we try to mark dirty bits on frames while we're
152 // in the process of removing them, things will get messed up.
153 MOZ_ASSERT(!OuterSVGIsCallingReflowSVG(aFrame),
154 "Do not call under ISVGDisplayableFrame::ReflowSVG!");
156 // We don't call SVGObserverUtils::InvalidateRenderingObservers here because
157 // we should only be called under InvalidateAndScheduleReflowSVG (which
158 // calls InvalidateBounds) or SVGDisplayContainerFrame::InsertFrames
159 // (at which point the frame has no observers).
161 if (aFrame->HasAnyStateBits(NS_FRAME_IS_NONDISPLAY)) {
162 return;
165 if (aFrame->HasAnyStateBits(NS_FRAME_IS_DIRTY | NS_FRAME_FIRST_REFLOW)) {
166 // Nothing to do if we're already dirty, or if the outer-<svg>
167 // hasn't yet had its initial reflow.
168 return;
171 SVGOuterSVGFrame* outerSVGFrame = nullptr;
173 // We must not add dirty bits to the SVGOuterSVGFrame or else
174 // PresShell::FrameNeedsReflow won't work when we pass it in below.
175 if (aFrame->IsSVGOuterSVGFrame()) {
176 outerSVGFrame = static_cast<SVGOuterSVGFrame*>(aFrame);
177 } else {
178 aFrame->MarkSubtreeDirty();
180 nsIFrame* f = aFrame->GetParent();
181 while (f && !f->IsSVGOuterSVGFrame()) {
182 if (f->HasAnyStateBits(NS_FRAME_IS_DIRTY | NS_FRAME_HAS_DIRTY_CHILDREN)) {
183 return;
185 f->AddStateBits(NS_FRAME_HAS_DIRTY_CHILDREN);
186 f = f->GetParent();
187 MOZ_ASSERT(f->IsSVGFrame(), "IsSVGOuterSVGFrame check above not valid!");
190 outerSVGFrame = static_cast<SVGOuterSVGFrame*>(f);
192 MOZ_ASSERT(outerSVGFrame && outerSVGFrame->IsSVGOuterSVGFrame(),
193 "Did not find SVGOuterSVGFrame!");
196 if (outerSVGFrame->HasAnyStateBits(NS_FRAME_IN_REFLOW)) {
197 // We're currently under an SVGOuterSVGFrame::Reflow call so there is no
198 // need to call PresShell::FrameNeedsReflow, since we have an
199 // SVGOuterSVGFrame::DidReflow call pending.
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->IsSVGFrame(), "SVG uses bits differently!");
214 // The flags we test here may change, hence why we have this separate
215 // function.
216 return aFrame->IsSubtreeDirty();
219 Size SVGUtils::GetContextSize(const nsIFrame* aFrame) {
220 Size size;
222 MOZ_ASSERT(aFrame->GetContent()->IsSVGElement(), "bad cast");
223 const SVGElement* element = static_cast<SVGElement*>(aFrame->GetContent());
225 SVGViewportElement* ctx = element->GetCtx();
226 if (ctx) {
227 size.width = ctx->GetLength(SVGContentUtils::X);
228 size.height = ctx->GetLength(SVGContentUtils::Y);
230 return size;
233 float SVGUtils::ObjectSpace(const gfxRect& aRect,
234 const SVGAnimatedLength* aLength) {
235 float axis;
237 switch (aLength->GetCtxType()) {
238 case SVGContentUtils::X:
239 axis = aRect.Width();
240 break;
241 case SVGContentUtils::Y:
242 axis = aRect.Height();
243 break;
244 case SVGContentUtils::XY:
245 axis = float(SVGContentUtils::ComputeNormalizedHypotenuse(
246 aRect.Width(), aRect.Height()));
247 break;
248 default:
249 MOZ_ASSERT_UNREACHABLE("unexpected ctx type");
250 axis = 0.0f;
251 break;
253 if (aLength->IsPercentage()) {
254 // Multiply first to avoid precision errors:
255 return axis * aLength->GetAnimValInSpecifiedUnits() / 100;
257 return aLength->GetAnimValueWithZoom(
258 static_cast<SVGViewportElement*>(nullptr)) *
259 axis;
262 float SVGUtils::UserSpace(nsIFrame* aNonSVGContext,
263 const SVGAnimatedLength* aLength) {
264 MOZ_ASSERT(!aNonSVGContext->IsTextFrame(), "Not expecting text content");
265 return aLength->GetAnimValueWithZoom(aNonSVGContext);
268 float SVGUtils::UserSpace(const UserSpaceMetrics& aMetrics,
269 const SVGAnimatedLength* aLength) {
270 return aLength->GetAnimValueWithZoom(aMetrics);
273 SVGOuterSVGFrame* SVGUtils::GetOuterSVGFrame(nsIFrame* aFrame) {
274 return static_cast<SVGOuterSVGFrame*>(nsLayoutUtils::GetClosestFrameOfType(
275 aFrame, LayoutFrameType::SVGOuterSVG));
278 nsIFrame* SVGUtils::GetOuterSVGFrameAndCoveredRegion(nsIFrame* aFrame,
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();
291 return outer;
294 auto ctm = nsLayoutUtils::GetTransformToAncestor(RelativeTo{aFrame},
295 RelativeTo{outer});
297 Matrix mm;
298 ctm.ProjectTo2D();
299 ctm.CanDraw2D(&mm);
300 gfxMatrix m = ThebesMatrix(mm);
302 float appUnitsPerDevPixel = aFrame->PresContext()->AppUnitsPerDevPixel();
303 float devPixelPerCSSPixel =
304 float(AppUnitsPerCSSPixel()) / appUnitsPerDevPixel;
306 // The matrix that GetBBox accepts should operate on "user space",
307 // i.e. with CSS pixel unit.
308 m.PreScale(devPixelPerCSSPixel, devPixelPerCSSPixel);
310 auto initPosition = gfxPoint(
311 NSAppUnitsToFloatPixels(aFrame->GetPosition().x, AppUnitsPerCSSPixel()),
312 NSAppUnitsToFloatPixels(aFrame->GetPosition().y, AppUnitsPerCSSPixel()));
314 // Both SVGUtils::GetBBox and nsLayoutUtils::GetTransformToAncestor
315 // will count this displacement, we should remove it here to avoid
316 // double-counting.
317 m.PreTranslate(-initPosition);
319 uint32_t flags = SVGUtils::eForGetClientRects | SVGUtils::eBBoxIncludeFill |
320 SVGUtils::eBBoxIncludeStroke |
321 SVGUtils::eBBoxIncludeMarkers |
322 SVGUtils::eUseUserSpaceOfUseElement;
324 gfxRect bbox = SVGUtils::GetBBox(aFrame, flags, &m);
325 *aRect = nsLayoutUtils::RoundGfxRectToAppRect(bbox, appUnitsPerDevPixel);
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()->IsSVGContainerFrame());
347 auto* parent = static_cast<SVGContainerFrame*>(aFrame->GetParent());
348 auto* content = static_cast<SVGElement*>(aFrame->GetContent());
350 return content->ChildToUserSpaceTransform() * parent->GetCanvasTM();
353 bool SVGUtils::GetParentSVGTransforms(const nsIFrame* aFrame,
354 gfx::Matrix* aFromParentTransform) {
355 MOZ_ASSERT(aFrame->HasAllStateBits(NS_FRAME_SVG_LAYOUT |
356 NS_FRAME_MAY_BE_TRANSFORMED),
357 "Expecting an SVG frame that can be transformed");
358 if (SVGContainerFrame* parent = do_QueryFrame(aFrame->GetParent())) {
359 return parent->HasChildrenOnlyTransform(aFromParentTransform);
361 return false;
364 void SVGUtils::NotifyChildrenOfSVGChange(nsIFrame* aFrame, uint32_t aFlags) {
365 for (nsIFrame* kid : aFrame->PrincipalChildList()) {
366 ISVGDisplayableFrame* SVGFrame = do_QueryFrame(kid);
367 if (SVGFrame) {
368 SVGFrame->NotifySVGChanged(aFlags);
369 } else {
370 NS_ASSERTION(kid->IsSVGFrame() || kid->IsInSVGTextSubtree(),
371 "SVG frame expected");
372 // recurse into the children of container frames e.g. <clipPath>, <mask>
373 // in case they have child frames with transformation matrices
374 if (kid->IsSVGFrame()) {
375 NotifyChildrenOfSVGChange(kid, aFlags);
381 // ************************************************************
383 float SVGUtils::ComputeOpacity(const nsIFrame* aFrame, bool aHandleOpacity) {
384 const auto* styleEffects = aFrame->StyleEffects();
386 if (!styleEffects->IsOpaque() &&
387 (SVGUtils::CanOptimizeOpacity(aFrame) || !aHandleOpacity)) {
388 return 1.0f;
391 return styleEffects->mOpacity;
394 SVGUtils::MaskUsage SVGUtils::DetermineMaskUsage(const nsIFrame* aFrame,
395 bool aHandleOpacity) {
396 MaskUsage usage;
398 using ClipPathType = StyleClipPath::Tag;
400 usage.mOpacity = ComputeOpacity(aFrame, aHandleOpacity);
402 nsIFrame* firstFrame =
403 nsLayoutUtils::FirstContinuationOrIBSplitSibling(aFrame);
405 const nsStyleSVGReset* svgReset = firstFrame->StyleSVGReset();
407 if (SVGObserverUtils::GetAndObserveMasks(firstFrame, nullptr) !=
408 SVGObserverUtils::eHasNoRefs) {
409 usage.mShouldGenerateMaskLayer = true;
412 SVGClipPathFrame* clipPathFrame;
413 // XXX check return value?
414 SVGObserverUtils::GetAndObserveClipPath(firstFrame, &clipPathFrame);
415 MOZ_ASSERT(!clipPathFrame || svgReset->mClipPath.IsUrl());
417 switch (svgReset->mClipPath.tag) {
418 case ClipPathType::Url:
419 if (clipPathFrame) {
420 if (clipPathFrame->IsTrivial()) {
421 usage.mShouldApplyClipPath = true;
422 } else {
423 usage.mShouldGenerateClipMaskLayer = true;
426 break;
427 case ClipPathType::Shape: {
428 usage.mShouldApplyBasicShapeOrPath = true;
429 const auto& shape = svgReset->mClipPath.AsShape()._0;
430 usage.mIsSimpleClipShape =
431 !usage.mShouldGenerateMaskLayer &&
432 (shape->IsRect() || shape->IsCircle() || shape->IsEllipse());
433 break;
435 case ClipPathType::Box:
436 usage.mShouldApplyBasicShapeOrPath = true;
437 break;
438 case ClipPathType::None:
439 MOZ_ASSERT(!usage.mShouldGenerateClipMaskLayer &&
440 !usage.mShouldApplyClipPath &&
441 !usage.mShouldApplyBasicShapeOrPath);
442 break;
443 default:
444 MOZ_ASSERT_UNREACHABLE("Unsupported clip-path type.");
445 break;
447 return usage;
450 class MixModeBlender {
451 public:
452 using Factory = gfx::Factory;
454 MixModeBlender(nsIFrame* aFrame, gfxContext* aContext)
455 : mFrame(aFrame), mSourceCtx(aContext) {
456 MOZ_ASSERT(mFrame && mSourceCtx);
459 bool ShouldCreateDrawTargetForBlend() const {
460 return mFrame->StyleEffects()->HasMixBlendMode();
463 gfxContext* CreateBlendTarget(const gfxMatrix& aTransform) {
464 MOZ_ASSERT(ShouldCreateDrawTargetForBlend());
466 // Create a temporary context to draw to so we can blend it back with
467 // another operator.
468 IntRect drawRect = ComputeClipExtsInDeviceSpace(aTransform);
469 if (drawRect.IsEmpty()) {
470 return nullptr;
473 RefPtr<DrawTarget> targetDT =
474 mSourceCtx->GetDrawTarget()->CreateSimilarDrawTarget(
475 drawRect.Size(), SurfaceFormat::B8G8R8A8);
476 if (!targetDT || !targetDT->IsValid()) {
477 return nullptr;
480 MOZ_ASSERT(!mTargetCtx,
481 "CreateBlendTarget is designed to be used once only.");
483 mTargetCtx = gfxContext::CreateOrNull(targetDT);
484 MOZ_ASSERT(mTargetCtx); // already checked the draw target above
485 mTargetCtx->SetMatrix(mSourceCtx->CurrentMatrix() *
486 Matrix::Translation(-drawRect.TopLeft()));
488 mTargetOffset = drawRect.TopLeft();
490 return mTargetCtx.get();
493 void BlendToTarget() {
494 MOZ_ASSERT(ShouldCreateDrawTargetForBlend());
495 MOZ_ASSERT(mTargetCtx,
496 "BlendToTarget should be used after CreateBlendTarget.");
498 RefPtr<SourceSurface> targetSurf = mTargetCtx->GetDrawTarget()->Snapshot();
500 gfxContextAutoSaveRestore save(mSourceCtx);
501 mSourceCtx->SetMatrix(Matrix()); // This will be restored right after.
502 auto pattern = MakeRefPtr<gfxPattern>(
503 targetSurf, Matrix::Translation(mTargetOffset.x, mTargetOffset.y));
504 mSourceCtx->SetPattern(pattern);
505 mSourceCtx->Paint();
508 private:
509 MixModeBlender() = delete;
511 IntRect ComputeClipExtsInDeviceSpace(const gfxMatrix& aTransform) {
512 // These are used if we require a temporary surface for a custom blend
513 // mode. Clip the source context first, so that we can generate a smaller
514 // temporary surface. (Since we will clip this context in
515 // SetupContextMatrix, a pair of save/restore is needed.)
516 gfxContextAutoSaveRestore saver;
518 if (!mFrame->HasAnyStateBits(NS_FRAME_IS_NONDISPLAY)) {
519 saver.SetContext(mSourceCtx);
520 // aFrame has a valid ink overflow rect, so clip to it before calling
521 // PushGroup() to minimize the size of the surfaces we'll composite:
522 gfxContextMatrixAutoSaveRestore matrixAutoSaveRestore(mSourceCtx);
523 mSourceCtx->Multiply(aTransform);
524 nsRect overflowRect = mFrame->InkOverflowRectRelativeToSelf();
525 if (FrameDoesNotIncludePositionInTM(mFrame)) {
526 overflowRect = overflowRect + mFrame->GetPosition();
528 mSourceCtx->Clip(NSRectToSnappedRect(
529 overflowRect, mFrame->PresContext()->AppUnitsPerDevPixel(),
530 *mSourceCtx->GetDrawTarget()));
533 // Get the clip extents in device space.
534 gfxRect clippedFrameSurfaceRect =
535 mSourceCtx->GetClipExtents(gfxContext::eDeviceSpace);
536 clippedFrameSurfaceRect.RoundOut();
538 IntRect result;
539 ToRect(clippedFrameSurfaceRect).ToIntRect(&result);
541 return Factory::CheckSurfaceSize(result.Size()) ? result : IntRect();
544 nsIFrame* mFrame;
545 gfxContext* mSourceCtx;
546 UniquePtr<gfxContext> mTargetCtx;
547 IntPoint mTargetOffset;
550 void SVGUtils::PaintFrameWithEffects(nsIFrame* aFrame, gfxContext& aContext,
551 const gfxMatrix& aTransform,
552 imgDrawingParams& aImgParams) {
553 NS_ASSERTION(aFrame->HasAnyStateBits(NS_FRAME_IS_NONDISPLAY) ||
554 aFrame->PresContext()->Document()->IsSVGGlyphsDocument(),
555 "Only painting of non-display SVG should take this code path");
557 ISVGDisplayableFrame* svgFrame = do_QueryFrame(aFrame);
558 if (!svgFrame) {
559 return;
562 MaskUsage maskUsage = DetermineMaskUsage(aFrame, true);
563 if (maskUsage.IsTransparent()) {
564 return;
567 if (auto* svg = SVGElement::FromNode(aFrame->GetContent())) {
568 if (!svg->HasValidDimensions()) {
569 return;
571 if (aFrame->IsSVGSymbolFrame() && !svg->IsInSVGUseShadowTree()) {
572 return;
576 /* SVG defines the following rendering model:
578 * 1. Render fill
579 * 2. Render stroke
580 * 3. Render markers
581 * 4. Apply filter
582 * 5. Apply clipping, masking, group opacity
584 * We follow this, but perform a couple of optimizations:
586 * + Use cairo's clipPath when representable natively (single object
587 * clip region).
589 * + Merge opacity and masking if both used together.
592 /* Properties are added lazily and may have been removed by a restyle,
593 so make sure all applicable ones are set again. */
594 SVGClipPathFrame* clipPathFrame;
595 nsTArray<SVGMaskFrame*> maskFrames;
596 nsTArray<SVGFilterFrame*> filterFrames;
597 const bool hasInvalidFilter =
598 SVGObserverUtils::GetAndObserveFilters(aFrame, &filterFrames) ==
599 SVGObserverUtils::eHasRefsSomeInvalid;
600 SVGObserverUtils::GetAndObserveClipPath(aFrame, &clipPathFrame);
601 SVGObserverUtils::GetAndObserveMasks(aFrame, &maskFrames);
603 SVGMaskFrame* maskFrame = maskFrames.IsEmpty() ? nullptr : maskFrames[0];
605 MixModeBlender blender(aFrame, &aContext);
606 gfxContext* target = blender.ShouldCreateDrawTargetForBlend()
607 ? blender.CreateBlendTarget(aTransform)
608 : &aContext;
610 if (!target) {
611 return;
614 /* Check if we need to do additional operations on this child's
615 * rendering, which necessitates rendering into another surface. */
616 bool shouldPushMask = false;
618 if (maskUsage.ShouldGenerateMask()) {
619 RefPtr<SourceSurface> maskSurface;
621 // maskFrame can be nullptr even if maskUsage.ShouldGenerateMaskLayer() is
622 // true. That happens when a user gives an unresolvable mask-id, such as
623 // mask:url()
624 // mask:url(#id-which-does-not-exist)
625 // Since we only uses SVGUtils with SVG elements, not like mask on an
626 // HTML element, we should treat an unresolvable mask as no-mask here.
627 if (maskUsage.ShouldGenerateMaskLayer() && maskFrame) {
628 StyleMaskMode maskMode =
629 aFrame->StyleSVGReset()->mMask.mLayers[0].mMaskMode;
630 SVGMaskFrame::MaskParams params(aContext.GetDrawTarget(), aFrame,
631 aTransform, maskUsage.Opacity(), maskMode,
632 aImgParams);
634 maskSurface = maskFrame->GetMaskForMaskedFrame(params);
636 if (!maskSurface) {
637 // Either entire surface is clipped out, or gfx buffer allocation
638 // failure in SVGMaskFrame::GetMaskForMaskedFrame.
639 return;
641 shouldPushMask = true;
644 if (maskUsage.ShouldGenerateClipMaskLayer()) {
645 RefPtr<SourceSurface> clipMaskSurface =
646 clipPathFrame->GetClipMask(aContext, aFrame, aTransform, maskSurface);
647 if (clipMaskSurface) {
648 maskSurface = clipMaskSurface;
649 } else {
650 // Either entire surface is clipped out, or gfx buffer allocation
651 // failure in SVGClipPathFrame::GetClipMask.
652 return;
654 shouldPushMask = true;
657 if (!maskUsage.ShouldGenerateLayer()) {
658 shouldPushMask = true;
661 // SVG mask multiply opacity into maskSurface already, so we do not bother
662 // to apply opacity again.
663 if (shouldPushMask) {
664 // We want the mask to be untransformed so use the inverse of the
665 // current transform as the maskTransform to compensate.
666 Matrix maskTransform = aContext.CurrentMatrix();
667 maskTransform.Invert();
668 target->PushGroupForBlendBack(gfxContentType::COLOR_ALPHA,
669 maskFrame ? 1.0f : maskUsage.Opacity(),
670 maskSurface, maskTransform);
674 /* If this frame has only a trivial clipPath, set up cairo's clipping now so
675 * we can just do normal painting and get it clipped appropriately.
677 if (maskUsage.ShouldApplyClipPath() ||
678 maskUsage.ShouldApplyBasicShapeOrPath()) {
679 if (maskUsage.ShouldApplyClipPath()) {
680 clipPathFrame->ApplyClipPath(aContext, aFrame, aTransform);
681 } else {
682 CSSClipPathInstance::ApplyBasicShapeOrPathClip(aContext, aFrame,
683 aTransform);
687 /* Paint the child */
689 // Invalid filters should render the unfiltered contents per spec.
690 if (aFrame->StyleEffects()->HasFilters() && !hasInvalidFilter) {
691 gfxContextMatrixAutoSaveRestore autoSR(target);
693 // 'target' is currently scaled such that its user space units are CSS
694 // pixels (SVG user space units). But PaintFilteredFrame expects it to be
695 // scaled in such a way that its user space units are device pixels. So we
696 // have to adjust the scale.
697 gfxMatrix reverseScaleMatrix = SVGUtils::GetCSSPxToDevPxMatrix(aFrame);
698 DebugOnly<bool> invertible = reverseScaleMatrix.Invert();
699 target->SetMatrixDouble(reverseScaleMatrix * aTransform *
700 target->CurrentMatrixDouble());
702 auto callback = [&](gfxContext& aContext, imgDrawingParams& aImgParams,
703 const gfxMatrix* aFilterTransform,
704 const nsIntRect* aDirtyRect) {
705 svgFrame->PaintSVG(aContext,
706 aFilterTransform
707 ? SVGUtils::GetCSSPxToDevPxMatrix(aFrame)
708 : aTransform,
709 aImgParams);
711 // If we're masking a userSpaceOnUse mask we may need to include the
712 // stroke too. Err on the side of caution and include it always.
713 gfxRect bbox = GetBBox(aFrame, SVGUtils::eUseFrameBoundsForOuterSVG |
714 SVGUtils::eBBoxIncludeFillGeometry |
715 SVGUtils::eBBoxIncludeStroke);
716 FilterInstance::PaintFilteredFrame(
717 aFrame, aFrame->StyleEffects()->mFilters.AsSpan(), filterFrames, target,
718 callback, nullptr, aImgParams, 1.0f, &bbox);
719 } else {
720 svgFrame->PaintSVG(*target, aTransform, aImgParams);
723 if (maskUsage.ShouldApplyClipPath() ||
724 maskUsage.ShouldApplyBasicShapeOrPath()) {
725 aContext.PopClip();
728 if (shouldPushMask) {
729 target->PopGroupAndBlend();
732 if (blender.ShouldCreateDrawTargetForBlend()) {
733 MOZ_ASSERT(target != &aContext);
734 blender.BlendToTarget();
738 bool SVGUtils::HitTestClip(nsIFrame* aFrame, const gfxPoint& aPoint) {
739 const nsStyleSVGReset* svgReset = aFrame->StyleSVGReset();
740 if (!svgReset->HasClipPath()) {
741 return true;
743 if (svgReset->mClipPath.IsUrl()) {
744 // If the clip-path property references non-existent or invalid clipPath
745 // element(s) we ignore it.
746 SVGClipPathFrame* clipPathFrame;
747 SVGObserverUtils::GetAndObserveClipPath(aFrame, &clipPathFrame);
748 return !clipPathFrame ||
749 clipPathFrame->PointIsInsideClipPath(aFrame, aPoint);
751 return CSSClipPathInstance::HitTestBasicShapeOrPathClip(aFrame, aPoint);
754 IntSize SVGUtils::ConvertToSurfaceSize(const gfxSize& aSize,
755 bool* aResultOverflows) {
756 IntSize surfaceSize(ClampToInt(ceil(aSize.width)),
757 ClampToInt(ceil(aSize.height)));
759 *aResultOverflows = surfaceSize.width != ceil(aSize.width) ||
760 surfaceSize.height != ceil(aSize.height);
762 if (!Factory::AllowedSurfaceSize(surfaceSize)) {
763 surfaceSize.width =
764 std::min(NS_SVG_OFFSCREEN_MAX_DIMENSION, surfaceSize.width);
765 surfaceSize.height =
766 std::min(NS_SVG_OFFSCREEN_MAX_DIMENSION, surfaceSize.height);
767 *aResultOverflows = true;
770 return surfaceSize;
773 bool SVGUtils::HitTestRect(const gfx::Matrix& aMatrix, float aRX, float aRY,
774 float aRWidth, float aRHeight, float aX, float aY) {
775 gfx::Rect rect(aRX, aRY, aRWidth, aRHeight);
776 if (rect.IsEmpty() || aMatrix.IsSingular()) {
777 return false;
779 gfx::Matrix toRectSpace = aMatrix;
780 toRectSpace.Invert();
781 gfx::Point p = toRectSpace.TransformPoint(gfx::Point(aX, aY));
782 return rect.x <= p.x && p.x <= rect.XMost() && rect.y <= p.y &&
783 p.y <= rect.YMost();
786 gfxRect SVGUtils::GetClipRectForFrame(const nsIFrame* aFrame, float aX,
787 float aY, float aWidth, float aHeight) {
788 const nsStyleDisplay* disp = aFrame->StyleDisplay();
789 const nsStyleEffects* effects = aFrame->StyleEffects();
791 bool clipApplies = disp->mOverflowX == StyleOverflow::Hidden ||
792 disp->mOverflowY == StyleOverflow::Hidden;
794 if (!clipApplies || effects->mClip.IsAuto()) {
795 return gfxRect(aX, aY, aWidth, aHeight);
798 const auto& rect = effects->mClip.AsRect();
799 nsRect coordClipRect = rect.ToLayoutRect();
800 nsIntRect clipPxRect = coordClipRect.ToOutsidePixels(AppUnitsPerCSSPixel());
801 gfxRect clipRect =
802 gfxRect(clipPxRect.x, clipPxRect.y, clipPxRect.width, clipPxRect.height);
803 if (rect.right.IsAuto()) {
804 clipRect.width = aWidth - clipRect.X();
806 if (rect.bottom.IsAuto()) {
807 clipRect.height = aHeight - clipRect.Y();
809 if (disp->mOverflowX != StyleOverflow::Hidden) {
810 clipRect.x = aX;
811 clipRect.width = aWidth;
813 if (disp->mOverflowY != StyleOverflow::Hidden) {
814 clipRect.y = aY;
815 clipRect.height = aHeight;
817 return clipRect;
820 gfxRect SVGUtils::GetBBox(nsIFrame* aFrame, uint32_t aFlags,
821 const gfxMatrix* aToBoundsSpace) {
822 if (aFrame->IsTextFrame()) {
823 aFrame = aFrame->GetParent();
826 if (aFrame->IsInSVGTextSubtree()) {
827 // It is possible to apply a gradient, pattern, clipping path, mask or
828 // filter to text. When one of these facilities is applied to text
829 // the bounding box is the entire text element in all cases.
830 aFrame =
831 nsLayoutUtils::GetClosestFrameOfType(aFrame, LayoutFrameType::SVGText);
834 ISVGDisplayableFrame* svg = do_QueryFrame(aFrame);
835 const bool hasSVGLayout = aFrame->HasAnyStateBits(NS_FRAME_SVG_LAYOUT);
836 if (hasSVGLayout && !svg) {
837 // An SVG frame, but not one that can be displayed directly (for
838 // example, nsGradientFrame). These can't contribute to the bbox.
839 return gfxRect();
842 const bool isOuterSVG = svg && !hasSVGLayout;
843 MOZ_ASSERT(!isOuterSVG || aFrame->IsSVGOuterSVGFrame());
844 if (!svg || (isOuterSVG && (aFlags & eUseFrameBoundsForOuterSVG))) {
845 // An HTML element or an SVG outer frame.
846 MOZ_ASSERT(!hasSVGLayout);
847 bool onlyCurrentFrame = aFlags & eIncludeOnlyCurrentFrameForNonSVGElement;
848 return SVGIntegrationUtils::GetSVGBBoxForNonSVGFrame(
849 aFrame,
850 /* aUnionContinuations = */ !onlyCurrentFrame);
853 MOZ_ASSERT(svg);
855 if (auto* element = SVGElement::FromNodeOrNull(aFrame->GetContent())) {
856 if (!element->HasValidDimensions()) {
857 return gfxRect();
861 // Clean out flags which have no effects on returning bbox from now, so that
862 // we can cache and reuse ObjectBoundingBoxProperty() in the code below.
863 aFlags &=
864 ~(eIncludeOnlyCurrentFrameForNonSVGElement | eUseFrameBoundsForOuterSVG);
865 if (!aFrame->IsSVGUseFrame()) {
866 aFlags &= ~eUseUserSpaceOfUseElement;
869 if (aFlags == eBBoxIncludeFillGeometry &&
870 // We only cache bbox in element's own user space
871 !aToBoundsSpace) {
872 gfxRect* prop = aFrame->GetProperty(ObjectBoundingBoxProperty());
873 if (prop) {
874 return *prop;
878 gfxMatrix matrix;
879 if (aToBoundsSpace) {
880 matrix = *aToBoundsSpace;
883 if (aFrame->IsSVGForeignObjectFrame() ||
884 aFlags & SVGUtils::eUseUserSpaceOfUseElement) {
885 // The spec says getBBox "Returns the tight bounding box in *current user
886 // space*". So we should really be doing this for all elements, but that
887 // needs investigation to check that we won't break too much content.
888 // NOTE: When changing this to apply to other frame types, make sure to
889 // also update SVGUtils::FrameSpaceInCSSPxToUserSpaceOffset.
890 MOZ_ASSERT(aFrame->GetContent()->IsSVGElement(), "bad cast");
891 auto* element = static_cast<SVGElement*>(aFrame->GetContent());
892 matrix = element->ChildToUserSpaceTransform() * matrix;
894 gfxRect bbox =
895 svg->GetBBoxContribution(ToMatrix(matrix), aFlags).ToThebesRect();
896 // Account for 'clipped'.
897 if (aFlags & SVGUtils::eBBoxIncludeClipped) {
898 gfxRect clipRect;
899 gfxRect fillBBox =
900 svg->GetBBoxContribution({}, SVGUtils::eBBoxIncludeFill).ToThebesRect();
901 // XXX Should probably check for overflow: clip too.
902 bool hasClip = aFrame->StyleDisplay()->IsScrollableOverflow();
903 if (hasClip) {
904 clipRect = SVGUtils::GetClipRectForFrame(aFrame, 0.0f, 0.0f,
905 fillBBox.width, fillBBox.height);
906 clipRect.MoveBy(fillBBox.TopLeft());
907 if (aFrame->IsSVGForeignObjectFrame() || aFrame->IsSVGUseFrame()) {
908 clipRect = matrix.TransformBounds(clipRect);
911 SVGClipPathFrame* clipPathFrame;
912 if (SVGObserverUtils::GetAndObserveClipPath(aFrame, &clipPathFrame) ==
913 SVGObserverUtils::eHasRefsSomeInvalid) {
914 bbox = gfxRect();
915 } else {
916 if (clipPathFrame) {
917 SVGClipPathElement* clipContent =
918 static_cast<SVGClipPathElement*>(clipPathFrame->GetContent());
919 if (clipContent->IsUnitsObjectBoundingBox()) {
920 matrix.PreTranslate(fillBBox.TopLeft());
921 matrix.PreScale(fillBBox.width, fillBBox.height);
922 } else if (aFrame->IsSVGForeignObjectFrame()) {
923 matrix = gfxMatrix();
925 matrix *= SVGUtils::GetTransformMatrixInUserSpace(clipPathFrame);
927 bbox = clipPathFrame->GetBBoxForClipPathFrame(bbox, matrix, aFlags)
928 .ToThebesRect();
931 if (hasClip) {
932 bbox = bbox.Intersect(clipRect);
935 if (bbox.IsEmpty()) {
936 bbox = gfxRect();
941 if (aFlags == eBBoxIncludeFillGeometry &&
942 // We only cache bbox in element's own user space
943 !aToBoundsSpace) {
944 // Obtaining the bbox for objectBoundingBox calculations is common so we
945 // cache the result for future calls, since calculation can be expensive:
946 aFrame->SetProperty(ObjectBoundingBoxProperty(), new gfxRect(bbox));
949 return bbox;
952 gfxPoint SVGUtils::FrameSpaceInCSSPxToUserSpaceOffset(const nsIFrame* aFrame) {
953 if (!aFrame->HasAnyStateBits(NS_FRAME_SVG_LAYOUT)) {
954 // The user space for non-SVG frames is defined as the bounding box of the
955 // frame's border-box rects over all continuations.
956 return gfxPoint();
959 // Leaf frames apply their own offset inside their user space.
960 if (FrameDoesNotIncludePositionInTM(aFrame)) {
961 return nsLayoutUtils::RectToGfxRect(aFrame->GetRect(),
962 AppUnitsPerCSSPixel())
963 .TopLeft();
966 // For foreignObject frames, SVGUtils::GetBBox applies their local
967 // transform, so we need to do the same here.
968 if (aFrame->IsSVGForeignObjectFrame()) {
969 gfxMatrix transform = static_cast<SVGElement*>(aFrame->GetContent())
970 ->ChildToUserSpaceTransform();
971 NS_ASSERTION(!transform.HasNonTranslation(),
972 "we're relying on this being an offset-only transform");
973 return transform.GetTranslation();
976 return gfxPoint();
979 static gfxRect GetBoundingBoxRelativeRect(const SVGAnimatedLength* aXYWH,
980 const gfxRect& aBBox) {
981 return gfxRect(aBBox.x + SVGUtils::ObjectSpace(aBBox, &aXYWH[0]),
982 aBBox.y + SVGUtils::ObjectSpace(aBBox, &aXYWH[1]),
983 SVGUtils::ObjectSpace(aBBox, &aXYWH[2]),
984 SVGUtils::ObjectSpace(aBBox, &aXYWH[3]));
987 gfxRect SVGUtils::GetRelativeRect(uint16_t aUnits,
988 const SVGAnimatedLength* aXYWH,
989 const gfxRect& aBBox,
990 const UserSpaceMetrics& aMetrics) {
991 if (aUnits == SVG_UNIT_TYPE_OBJECTBOUNDINGBOX) {
992 return GetBoundingBoxRelativeRect(aXYWH, aBBox);
994 return gfxRect(UserSpace(aMetrics, &aXYWH[0]), UserSpace(aMetrics, &aXYWH[1]),
995 UserSpace(aMetrics, &aXYWH[2]),
996 UserSpace(aMetrics, &aXYWH[3]));
999 gfxRect SVGUtils::GetRelativeRect(uint16_t aUnits,
1000 const SVGAnimatedLength* aXYWH,
1001 const gfxRect& aBBox, nsIFrame* aFrame) {
1002 if (aUnits == SVG_UNIT_TYPE_OBJECTBOUNDINGBOX) {
1003 return GetBoundingBoxRelativeRect(aXYWH, aBBox);
1005 if (SVGElement* svgElement = SVGElement::FromNode(aFrame->GetContent())) {
1006 return GetRelativeRect(aUnits, aXYWH, aBBox, SVGElementMetrics(svgElement));
1008 return GetRelativeRect(aUnits, aXYWH, aBBox,
1009 NonSVGFrameUserSpaceMetrics(aFrame));
1012 bool SVGUtils::CanOptimizeOpacity(const nsIFrame* aFrame) {
1013 if (!aFrame->HasAnyStateBits(NS_FRAME_SVG_LAYOUT)) {
1014 return false;
1016 auto* content = aFrame->GetContent();
1017 if (!content->IsSVGGeometryElement() &&
1018 !content->IsSVGElement(nsGkAtoms::image)) {
1019 return false;
1021 if (aFrame->StyleEffects()->HasFilters()) {
1022 return false;
1024 // XXX The SVG WG is intending to allow fill, stroke and markers on <image>
1025 if (content->IsSVGElement(nsGkAtoms::image)) {
1026 return true;
1028 const nsStyleSVG* style = aFrame->StyleSVG();
1029 if (style->HasMarker() &&
1030 static_cast<SVGGeometryElement*>(content)->IsMarkable()) {
1031 return false;
1034 if (nsLayoutUtils::HasAnimationOfPropertySet(
1035 aFrame, nsCSSPropertyIDSet::OpacityProperties())) {
1036 return false;
1039 return !style->HasFill() || !HasStroke(aFrame);
1042 gfxMatrix SVGUtils::AdjustMatrixForUnits(const gfxMatrix& aMatrix,
1043 const SVGAnimatedEnumeration* aUnits,
1044 nsIFrame* aFrame, uint32_t aFlags) {
1045 if (aFrame && aUnits->GetAnimValue() == SVG_UNIT_TYPE_OBJECTBOUNDINGBOX) {
1046 gfxRect bbox = GetBBox(aFrame, aFlags);
1047 gfxMatrix tm = aMatrix;
1048 tm.PreTranslate(gfxPoint(bbox.X(), bbox.Y()));
1049 tm.PreScale(bbox.Width(), bbox.Height());
1050 return tm;
1052 return aMatrix;
1055 bool SVGUtils::GetNonScalingStrokeTransform(const nsIFrame* aFrame,
1056 gfxMatrix* aUserToOuterSVG) {
1057 if (aFrame->GetContent()->IsText()) {
1058 aFrame = aFrame->GetParent();
1061 if (!aFrame->StyleSVGReset()->HasNonScalingStroke()) {
1062 return false;
1065 MOZ_ASSERT(aFrame->GetContent()->IsSVGElement(), "should be an SVG element");
1067 SVGElement* content = static_cast<SVGElement*>(aFrame->GetContent());
1068 *aUserToOuterSVG =
1069 ThebesMatrix(SVGContentUtils::GetNonScalingStrokeCTM(content));
1071 return aUserToOuterSVG->HasNonTranslation() && !aUserToOuterSVG->IsSingular();
1074 void SVGUtils::UpdateNonScalingStrokeStateBit(nsIFrame* aFrame) {
1075 MOZ_ASSERT(aFrame->HasAnyStateBits(NS_FRAME_SVG_LAYOUT),
1076 "Called on invalid frame type");
1077 MOZ_ASSERT(aFrame->StyleSVGReset()->HasNonScalingStroke(),
1078 "Expecting initial frame to have non-scaling-stroke style");
1080 do {
1081 MOZ_ASSERT(aFrame->IsSVGFrame(), "Unexpected frame type");
1082 aFrame->AddStateBits(NS_STATE_SVG_MAY_CONTAIN_NON_SCALING_STROKE);
1083 if (aFrame->IsSVGOuterSVGFrame()) {
1084 return;
1086 } while ((aFrame = aFrame->GetParent()));
1089 // The logic here comes from _cairo_stroke_style_max_distance_from_path
1090 static gfxRect PathExtentsToMaxStrokeExtents(const gfxRect& aPathExtents,
1091 const nsIFrame* aFrame,
1092 double aStyleExpansionFactor,
1093 const gfxMatrix& aMatrix) {
1094 double style_expansion =
1095 aStyleExpansionFactor * SVGUtils::GetStrokeWidth(aFrame);
1097 gfxMatrix matrix = aMatrix;
1099 gfxMatrix outerSVGToUser;
1100 if (SVGUtils::GetNonScalingStrokeTransform(aFrame, &outerSVGToUser)) {
1101 outerSVGToUser.Invert();
1102 matrix.PreMultiply(outerSVGToUser);
1105 double dx = style_expansion * (fabs(matrix._11) + fabs(matrix._21));
1106 double dy = style_expansion * (fabs(matrix._22) + fabs(matrix._12));
1108 gfxRect strokeExtents = aPathExtents;
1109 strokeExtents.Inflate(dx, dy);
1110 return strokeExtents;
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->ChildToUserSpaceTransform() * transform;
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 if (properties.HasTransform()) {
1534 trans = nsStyleTransformMatrix::ReadTransforms(
1535 properties.mTranslate, properties.mRotate, properties.mScale,
1536 properties.mMotion.ptrOr(nullptr), properties.mTransform, refBox,
1537 AppUnitsPerCSSPixel());
1540 trans.ChangeBasis(svgTransformOrigin);
1542 Matrix mm;
1543 trans.ProjectTo2D();
1544 (void)trans.CanDraw2D(&mm);
1546 return ThebesMatrix(mm);
1549 } // namespace mozilla