Bug 1568157 - Part 5: Move the NodePicker initialization into a getter. r=yulia
[gecko.git] / layout / svg / nsSVGUtils.cpp
blobd85f33a00eb59af8b6d33ffcef023e4b2759cf9e
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 "nsSVGUtils.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 "nsCSSClipPathInstance.h"
20 #include "nsCSSFrameConstructor.h"
21 #include "nsDisplayList.h"
22 #include "nsFilterInstance.h"
23 #include "nsFrameList.h"
24 #include "nsGkAtoms.h"
25 #include "nsIContent.h"
26 #include "nsIFrame.h"
27 #include "nsLayoutUtils.h"
28 #include "nsPresContext.h"
29 #include "nsStyleStruct.h"
30 #include "nsStyleTransformMatrix.h"
31 #include "SVGAnimatedLength.h"
32 #include "nsSVGClipPathFrame.h"
33 #include "nsSVGContainerFrame.h"
34 #include "SVGContentUtils.h"
35 #include "nsSVGDisplayableFrame.h"
36 #include "nsSVGFilterPaintCallback.h"
37 #include "nsSVGForeignObjectFrame.h"
38 #include "SVGGeometryFrame.h"
39 #include "nsSVGInnerSVGFrame.h"
40 #include "nsSVGIntegrationUtils.h"
41 #include "nsSVGMaskFrame.h"
42 #include "SVGObserverUtils.h"
43 #include "nsSVGOuterSVGFrame.h"
44 #include "nsSVGPaintServerFrame.h"
45 #include "SVGTextFrame.h"
46 #include "nsTextFrame.h"
47 #include "mozilla/Preferences.h"
48 #include "mozilla/StaticPrefs_svg.h"
49 #include "mozilla/SVGContextPaint.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;
61 using namespace mozilla::dom;
62 using namespace mozilla::dom::SVGUnitTypes_Binding;
63 using namespace mozilla::gfx;
64 using namespace mozilla::image;
66 bool NS_SVGDisplayListHitTestingEnabled() {
67 return StaticPrefs::svg_display_lists_hit_testing_enabled();
70 bool NS_SVGDisplayListPaintingEnabled() {
71 return StaticPrefs::svg_display_lists_painting_enabled();
74 bool NS_SVGNewGetBBoxEnabled() {
75 return StaticPrefs::svg_new_getBBox_enabled();
78 // we only take the address of this:
79 static mozilla::gfx::UserDataKey sSVGAutoRenderStateKey;
81 SVGAutoRenderState::SVGAutoRenderState(
82 DrawTarget* aDrawTarget MOZ_GUARD_OBJECT_NOTIFIER_PARAM_IN_IMPL)
83 : mDrawTarget(aDrawTarget),
84 mOriginalRenderState(nullptr),
85 mPaintingToWindow(false) {
86 MOZ_GUARD_OBJECT_NOTIFIER_INIT;
87 mOriginalRenderState = aDrawTarget->RemoveUserData(&sSVGAutoRenderStateKey);
88 // We always remove ourselves from aContext before it dies, so
89 // passing nullptr as the destroy function is okay.
90 aDrawTarget->AddUserData(&sSVGAutoRenderStateKey, this, nullptr);
93 SVGAutoRenderState::~SVGAutoRenderState() {
94 mDrawTarget->RemoveUserData(&sSVGAutoRenderStateKey);
95 if (mOriginalRenderState) {
96 mDrawTarget->AddUserData(&sSVGAutoRenderStateKey, mOriginalRenderState,
97 nullptr);
101 void SVGAutoRenderState::SetPaintingToWindow(bool aPaintingToWindow) {
102 mPaintingToWindow = aPaintingToWindow;
105 /* static */
106 bool SVGAutoRenderState::IsPaintingToWindow(DrawTarget* aDrawTarget) {
107 void* state = aDrawTarget->GetUserData(&sSVGAutoRenderStateKey);
108 if (state) {
109 return static_cast<SVGAutoRenderState*>(state)->mPaintingToWindow;
111 return false;
114 nsRect nsSVGUtils::GetPostFilterVisualOverflowRect(
115 nsIFrame* aFrame, const nsRect& aPreFilterRect) {
116 MOZ_ASSERT(aFrame->GetStateBits() & NS_FRAME_SVG_LAYOUT,
117 "Called on invalid frame type");
119 // Note: we do not return here for eHasNoRefs since we must still handle any
120 // CSS filter functions.
121 // TODO: We currently pass nullptr instead of an nsTArray* here, but we
122 // actually should get the filter frames and then pass them into
123 // GetPostFilterBounds below! See bug 1494263.
124 // TODO: we should really return an empty rect for eHasRefsSomeInvalid since
125 // in that case we disable painting of the element.
126 if (!aFrame->StyleEffects()->HasFilters() ||
127 SVGObserverUtils::GetAndObserveFilters(aFrame, nullptr) ==
128 SVGObserverUtils::eHasRefsSomeInvalid) {
129 return aPreFilterRect;
132 return nsFilterInstance::GetPostFilterBounds(aFrame, nullptr,
133 &aPreFilterRect);
136 bool nsSVGUtils::OuterSVGIsCallingReflowSVG(nsIFrame* aFrame) {
137 return GetOuterSVGFrame(aFrame)->IsCallingReflowSVG();
140 bool nsSVGUtils::AnyOuterSVGIsCallingReflowSVG(nsIFrame* aFrame) {
141 nsSVGOuterSVGFrame* outer = GetOuterSVGFrame(aFrame);
142 do {
143 if (outer->IsCallingReflowSVG()) {
144 return true;
146 outer = GetOuterSVGFrame(outer->GetParent());
147 } while (outer);
148 return false;
151 void nsSVGUtils::ScheduleReflowSVG(nsIFrame* aFrame) {
152 MOZ_ASSERT(aFrame->IsFrameOfType(nsIFrame::eSVG), "Passed bad frame!");
154 // If this is triggered, the callers should be fixed to call us before
155 // ReflowSVG is called. If we try to mark dirty bits on frames while we're
156 // in the process of removing them, things will get messed up.
157 NS_ASSERTION(!OuterSVGIsCallingReflowSVG(aFrame),
158 "Do not call under nsSVGDisplayableFrame::ReflowSVG!");
160 // We don't call SVGObserverUtils::InvalidateRenderingObservers here because
161 // we should only be called under InvalidateAndScheduleReflowSVG (which
162 // calls InvalidateBounds) or nsSVGDisplayContainerFrame::InsertFrames
163 // (at which point the frame has no observers).
165 if (aFrame->GetStateBits() & NS_FRAME_IS_NONDISPLAY) {
166 return;
169 if (aFrame->GetStateBits() & (NS_FRAME_IS_DIRTY | NS_FRAME_FIRST_REFLOW)) {
170 // Nothing to do if we're already dirty, or if the outer-<svg>
171 // hasn't yet had its initial reflow.
172 return;
175 nsSVGOuterSVGFrame* outerSVGFrame = nullptr;
177 // We must not add dirty bits to the nsSVGOuterSVGFrame or else
178 // PresShell::FrameNeedsReflow won't work when we pass it in below.
179 if (aFrame->IsSVGOuterSVGFrame()) {
180 outerSVGFrame = static_cast<nsSVGOuterSVGFrame*>(aFrame);
181 } else {
182 aFrame->MarkSubtreeDirty();
184 nsIFrame* f = aFrame->GetParent();
185 while (f && !f->IsSVGOuterSVGFrame()) {
186 if (f->GetStateBits() &
187 (NS_FRAME_IS_DIRTY | NS_FRAME_HAS_DIRTY_CHILDREN)) {
188 return;
190 f->AddStateBits(NS_FRAME_HAS_DIRTY_CHILDREN);
191 f = f->GetParent();
192 MOZ_ASSERT(f->IsFrameOfType(nsIFrame::eSVG),
193 "IsSVGOuterSVGFrame check above not valid!");
196 outerSVGFrame = static_cast<nsSVGOuterSVGFrame*>(f);
198 MOZ_ASSERT(outerSVGFrame && outerSVGFrame->IsSVGOuterSVGFrame(),
199 "Did not find nsSVGOuterSVGFrame!");
202 if (outerSVGFrame->GetStateBits() & NS_FRAME_IN_REFLOW) {
203 // We're currently under an nsSVGOuterSVGFrame::Reflow call so there is no
204 // need to call PresShell::FrameNeedsReflow, since we have an
205 // nsSVGOuterSVGFrame::DidReflow call pending.
206 return;
209 nsFrameState dirtyBit =
210 (outerSVGFrame == aFrame ? NS_FRAME_IS_DIRTY
211 : NS_FRAME_HAS_DIRTY_CHILDREN);
213 aFrame->PresShell()->FrameNeedsReflow(outerSVGFrame, IntrinsicDirty::Resize,
214 dirtyBit);
217 bool nsSVGUtils::NeedsReflowSVG(nsIFrame* aFrame) {
218 MOZ_ASSERT(aFrame->IsFrameOfType(nsIFrame::eSVG),
219 "SVG uses bits differently!");
221 // The flags we test here may change, hence why we have this separate
222 // function.
223 return NS_SUBTREE_DIRTY(aFrame);
226 Size nsSVGUtils::GetContextSize(const nsIFrame* aFrame) {
227 Size size;
229 MOZ_ASSERT(aFrame->GetContent()->IsSVGElement(), "bad cast");
230 const SVGElement* element = static_cast<SVGElement*>(aFrame->GetContent());
232 SVGViewportElement* ctx = element->GetCtx();
233 if (ctx) {
234 size.width = ctx->GetLength(SVGContentUtils::X);
235 size.height = ctx->GetLength(SVGContentUtils::Y);
237 return size;
240 float nsSVGUtils::ObjectSpace(const gfxRect& aRect,
241 const SVGAnimatedLength* aLength) {
242 float axis;
244 switch (aLength->GetCtxType()) {
245 case SVGContentUtils::X:
246 axis = aRect.Width();
247 break;
248 case SVGContentUtils::Y:
249 axis = aRect.Height();
250 break;
251 case SVGContentUtils::XY:
252 axis = float(SVGContentUtils::ComputeNormalizedHypotenuse(
253 aRect.Width(), aRect.Height()));
254 break;
255 default:
256 MOZ_ASSERT_UNREACHABLE("unexpected ctx type");
257 axis = 0.0f;
258 break;
260 if (aLength->IsPercentage()) {
261 // Multiply first to avoid precision errors:
262 return axis * aLength->GetAnimValInSpecifiedUnits() / 100;
264 return aLength->GetAnimValue(static_cast<SVGViewportElement*>(nullptr)) *
265 axis;
268 float nsSVGUtils::UserSpace(SVGElement* aSVGElement,
269 const SVGAnimatedLength* aLength) {
270 return aLength->GetAnimValue(aSVGElement);
273 float nsSVGUtils::UserSpace(nsIFrame* aNonSVGContext,
274 const SVGAnimatedLength* aLength) {
275 return aLength->GetAnimValue(aNonSVGContext);
278 float nsSVGUtils::UserSpace(const UserSpaceMetrics& aMetrics,
279 const SVGAnimatedLength* aLength) {
280 return aLength->GetAnimValue(aMetrics);
283 nsSVGOuterSVGFrame* nsSVGUtils::GetOuterSVGFrame(nsIFrame* aFrame) {
284 while (aFrame) {
285 if (aFrame->IsSVGOuterSVGFrame()) {
286 return static_cast<nsSVGOuterSVGFrame*>(aFrame);
288 aFrame = aFrame->GetParent();
291 return nullptr;
294 nsIFrame* nsSVGUtils::GetOuterSVGFrameAndCoveredRegion(nsIFrame* aFrame,
295 nsRect* aRect) {
296 nsSVGDisplayableFrame* svg = do_QueryFrame(aFrame);
297 if (!svg) {
298 return nullptr;
300 nsSVGOuterSVGFrame* outer = GetOuterSVGFrame(aFrame);
301 if (outer == svg) {
302 return nullptr;
305 if (aFrame->GetStateBits() & NS_FRAME_IS_NONDISPLAY) {
306 *aRect = nsRect(0, 0, 0, 0);
307 } else {
308 uint32_t flags =
309 nsSVGUtils::eForGetClientRects | nsSVGUtils::eBBoxIncludeFill |
310 nsSVGUtils::eBBoxIncludeStroke | nsSVGUtils::eBBoxIncludeMarkers;
312 auto ctm = nsLayoutUtils::GetTransformToAncestor(aFrame, outer);
314 float initPositionX = NSAppUnitsToFloatPixels(aFrame->GetPosition().x,
315 AppUnitsPerCSSPixel()),
316 initPositionY = NSAppUnitsToFloatPixels(aFrame->GetPosition().y,
317 AppUnitsPerCSSPixel());
319 Matrix mm;
320 ctm.ProjectTo2D();
321 ctm.CanDraw2D(&mm);
322 gfxMatrix m = ThebesMatrix(mm);
324 float appUnitsPerDevPixel = aFrame->PresContext()->AppUnitsPerDevPixel();
325 float devPixelPerCSSPixel =
326 float(AppUnitsPerCSSPixel()) / appUnitsPerDevPixel;
328 // The matrix that GetBBox accepts should operate on "user space",
329 // i.e. with CSS pixel unit.
330 m = m.PreScale(devPixelPerCSSPixel, devPixelPerCSSPixel);
332 // Both nsSVGUtils::GetBBox and nsLayoutUtils::GetTransformToAncestor
333 // will count this displacement, we should remove it here to avoid
334 // double-counting.
335 m = m.PreTranslate(-initPositionX, -initPositionY);
337 SVGBBox bbox = nsSVGUtils::GetBBox(aFrame, flags, &m);
338 *aRect = nsLayoutUtils::RoundGfxRectToAppRect(bbox, appUnitsPerDevPixel);
341 return outer;
344 gfxMatrix nsSVGUtils::GetCanvasTM(nsIFrame* aFrame) {
345 // XXX yuck, we really need a common interface for GetCanvasTM
347 if (!aFrame->IsFrameOfType(nsIFrame::eSVG)) {
348 return GetCSSPxToDevPxMatrix(aFrame);
351 LayoutFrameType type = aFrame->Type();
352 if (type == LayoutFrameType::SVGForeignObject) {
353 return static_cast<nsSVGForeignObjectFrame*>(aFrame)->GetCanvasTM();
355 if (type == LayoutFrameType::SVGOuterSVG) {
356 return GetCSSPxToDevPxMatrix(aFrame);
359 nsSVGContainerFrame* containerFrame = do_QueryFrame(aFrame);
360 if (containerFrame) {
361 return containerFrame->GetCanvasTM();
364 return static_cast<SVGGeometryFrame*>(aFrame)->GetCanvasTM();
367 void nsSVGUtils::NotifyChildrenOfSVGChange(nsIFrame* aFrame, uint32_t aFlags) {
368 for (nsIFrame* kid : aFrame->PrincipalChildList()) {
369 nsSVGDisplayableFrame* SVGFrame = do_QueryFrame(kid);
370 if (SVGFrame) {
371 SVGFrame->NotifySVGChanged(aFlags);
372 } else {
373 NS_ASSERTION(kid->IsFrameOfType(nsIFrame::eSVG) ||
374 nsSVGUtils::IsInSVGTextSubtree(kid),
375 "SVG frame expected");
376 // recurse into the children of container frames e.g. <clipPath>, <mask>
377 // in case they have child frames with transformation matrices
378 if (kid->IsFrameOfType(nsIFrame::eSVG)) {
379 NotifyChildrenOfSVGChange(kid, aFlags);
385 // ************************************************************
387 class SVGPaintCallback : public nsSVGFilterPaintCallback {
388 public:
389 virtual void Paint(gfxContext& aContext, nsIFrame* aTarget,
390 const gfxMatrix& aTransform, const nsIntRect* aDirtyRect,
391 imgDrawingParams& aImgParams) override {
392 nsSVGDisplayableFrame* svgFrame = do_QueryFrame(aTarget);
393 NS_ASSERTION(svgFrame, "Expected SVG frame here");
395 nsIntRect* dirtyRect = nullptr;
396 nsIntRect tmpDirtyRect;
398 // aDirtyRect is in user-space pixels, we need to convert to
399 // outer-SVG-frame-relative device pixels.
400 if (aDirtyRect) {
401 gfxMatrix userToDeviceSpace = aTransform;
402 if (userToDeviceSpace.IsSingular()) {
403 return;
405 gfxRect dirtyBounds = userToDeviceSpace.TransformBounds(gfxRect(
406 aDirtyRect->x, aDirtyRect->y, aDirtyRect->width, aDirtyRect->height));
407 dirtyBounds.RoundOut();
408 if (gfxUtils::GfxRectToIntRect(dirtyBounds, &tmpDirtyRect)) {
409 dirtyRect = &tmpDirtyRect;
413 svgFrame->PaintSVG(aContext, nsSVGUtils::GetCSSPxToDevPxMatrix(aTarget),
414 aImgParams, dirtyRect);
418 float nsSVGUtils::ComputeOpacity(nsIFrame* aFrame, bool aHandleOpacity) {
419 float opacity = aFrame->StyleEffects()->mOpacity;
421 if (opacity != 1.0f &&
422 (nsSVGUtils::CanOptimizeOpacity(aFrame) || !aHandleOpacity)) {
423 return 1.0f;
426 return opacity;
429 void nsSVGUtils::DetermineMaskUsage(nsIFrame* aFrame, bool aHandleOpacity,
430 MaskUsage& aUsage) {
431 aUsage.opacity = ComputeOpacity(aFrame, aHandleOpacity);
433 nsIFrame* firstFrame =
434 nsLayoutUtils::FirstContinuationOrIBSplitSibling(aFrame);
436 const nsStyleSVGReset* svgReset = firstFrame->StyleSVGReset();
438 nsTArray<nsSVGMaskFrame*> maskFrames;
439 // XXX check return value?
440 SVGObserverUtils::GetAndObserveMasks(firstFrame, &maskFrames);
441 aUsage.shouldGenerateMaskLayer = (maskFrames.Length() > 0);
443 nsSVGClipPathFrame* clipPathFrame;
444 // XXX check return value?
445 SVGObserverUtils::GetAndObserveClipPath(firstFrame, &clipPathFrame);
446 MOZ_ASSERT(!clipPathFrame ||
447 svgReset->mClipPath.GetType() == StyleShapeSourceType::Image);
449 switch (svgReset->mClipPath.GetType()) {
450 case StyleShapeSourceType::Image:
451 if (clipPathFrame) {
452 if (clipPathFrame->IsTrivial()) {
453 aUsage.shouldApplyClipPath = true;
454 } else {
455 aUsage.shouldGenerateClipMaskLayer = true;
458 break;
459 case StyleShapeSourceType::Shape:
460 case StyleShapeSourceType::Box:
461 case StyleShapeSourceType::Path:
462 aUsage.shouldApplyBasicShapeOrPath = true;
463 break;
464 case StyleShapeSourceType::None:
465 MOZ_ASSERT(!aUsage.shouldGenerateClipMaskLayer &&
466 !aUsage.shouldApplyClipPath &&
467 !aUsage.shouldApplyBasicShapeOrPath);
468 break;
469 default:
470 MOZ_ASSERT_UNREACHABLE("Unsupported clip-path type.");
471 break;
475 class MixModeBlender {
476 public:
477 typedef mozilla::gfx::Factory Factory;
479 MixModeBlender(nsIFrame* aFrame, gfxContext* aContext)
480 : mFrame(aFrame), mSourceCtx(aContext) {
481 MOZ_ASSERT(mFrame && mSourceCtx);
484 bool ShouldCreateDrawTargetForBlend() const {
485 return mFrame->StyleEffects()->mMixBlendMode != NS_STYLE_BLEND_NORMAL;
488 gfxContext* CreateBlendTarget(const gfxMatrix& aTransform) {
489 MOZ_ASSERT(ShouldCreateDrawTargetForBlend());
491 // Create a temporary context to draw to so we can blend it back with
492 // another operator.
493 IntRect drawRect = ComputeClipExtsInDeviceSpace(aTransform);
495 RefPtr<DrawTarget> targetDT =
496 mSourceCtx->GetDrawTarget()->CreateSimilarDrawTarget(
497 drawRect.Size(), SurfaceFormat::B8G8R8A8);
498 if (!targetDT || !targetDT->IsValid()) {
499 return nullptr;
502 MOZ_ASSERT(!mTargetCtx,
503 "CreateBlendTarget is designed to be used once only.");
505 mTargetCtx = gfxContext::CreateOrNull(targetDT);
506 MOZ_ASSERT(mTargetCtx); // already checked the draw target above
507 mTargetCtx->SetMatrix(mSourceCtx->CurrentMatrix() *
508 Matrix::Translation(-drawRect.TopLeft()));
510 mTargetOffset = drawRect.TopLeft();
512 return mTargetCtx;
515 void BlendToTarget() {
516 MOZ_ASSERT(ShouldCreateDrawTargetForBlend());
517 MOZ_ASSERT(mTargetCtx,
518 "BlendToTarget should be used after CreateBlendTarget.");
520 RefPtr<SourceSurface> targetSurf = mTargetCtx->GetDrawTarget()->Snapshot();
522 gfxContextAutoSaveRestore save(mSourceCtx);
523 mSourceCtx->SetMatrix(Matrix()); // This will be restored right after.
524 RefPtr<gfxPattern> pattern = new gfxPattern(
525 targetSurf, Matrix::Translation(mTargetOffset.x, mTargetOffset.y));
526 mSourceCtx->SetPattern(pattern);
527 mSourceCtx->Paint();
530 private:
531 MixModeBlender() = delete;
533 IntRect ComputeClipExtsInDeviceSpace(const gfxMatrix& aTransform) {
534 // These are used if we require a temporary surface for a custom blend
535 // mode. Clip the source context first, so that we can generate a smaller
536 // temporary surface. (Since we will clip this context in
537 // SetupContextMatrix, a pair of save/restore is needed.)
538 gfxContextAutoSaveRestore saver(mSourceCtx);
540 if (!(mFrame->GetStateBits() & NS_FRAME_IS_NONDISPLAY)) {
541 // aFrame has a valid visual overflow rect, so clip to it before calling
542 // PushGroup() to minimize the size of the surfaces we'll composite:
543 gfxContextMatrixAutoSaveRestore matrixAutoSaveRestore(mSourceCtx);
544 mSourceCtx->Multiply(aTransform);
545 nsRect overflowRect = mFrame->GetVisualOverflowRectRelativeToSelf();
546 if (mFrame->IsFrameOfType(nsIFrame::eSVGGeometry) ||
547 nsSVGUtils::IsInSVGTextSubtree(mFrame)) {
548 // Unlike containers, leaf frames do not include GetPosition() in
549 // GetCanvasTM().
550 overflowRect = overflowRect + mFrame->GetPosition();
552 mSourceCtx->Clip(NSRectToSnappedRect(
553 overflowRect, mFrame->PresContext()->AppUnitsPerDevPixel(),
554 *mSourceCtx->GetDrawTarget()));
557 // Get the clip extents in device space.
558 gfxRect clippedFrameSurfaceRect =
559 mSourceCtx->GetClipExtents(gfxContext::eDeviceSpace);
560 clippedFrameSurfaceRect.RoundOut();
562 IntRect result;
563 ToRect(clippedFrameSurfaceRect).ToIntRect(&result);
565 return Factory::CheckSurfaceSize(result.Size()) ? result : IntRect();
568 nsIFrame* mFrame;
569 gfxContext* mSourceCtx;
570 RefPtr<gfxContext> mTargetCtx;
571 IntPoint mTargetOffset;
574 void nsSVGUtils::PaintFrameWithEffects(nsIFrame* aFrame, gfxContext& aContext,
575 const gfxMatrix& aTransform,
576 imgDrawingParams& aImgParams,
577 const nsIntRect* aDirtyRect) {
578 NS_ASSERTION(!NS_SVGDisplayListPaintingEnabled() ||
579 (aFrame->GetStateBits() & NS_FRAME_IS_NONDISPLAY) ||
580 aFrame->PresContext()->Document()->IsSVGGlyphsDocument(),
581 "If display lists are enabled, only painting of non-display "
582 "SVG should take this code path");
584 nsSVGDisplayableFrame* svgFrame = do_QueryFrame(aFrame);
585 if (!svgFrame) {
586 return;
589 MaskUsage maskUsage;
590 DetermineMaskUsage(aFrame, true, maskUsage);
591 if (maskUsage.opacity == 0.0f) {
592 return;
595 const nsIContent* content = aFrame->GetContent();
596 if (content->IsSVGElement() &&
597 !static_cast<const SVGElement*>(content)->HasValidDimensions()) {
598 return;
601 if (aDirtyRect && !(aFrame->GetStateBits() & NS_FRAME_IS_NONDISPLAY)) {
602 // Here we convert aFrame's paint bounds to outer-<svg> device space,
603 // compare it to aDirtyRect, and return early if they don't intersect.
604 // We don't do this optimization for nondisplay SVG since nondisplay
605 // SVG doesn't maintain bounds/overflow rects.
606 nsRect overflowRect = aFrame->GetVisualOverflowRectRelativeToSelf();
607 if (aFrame->IsFrameOfType(nsIFrame::eSVGGeometry) ||
608 nsSVGUtils::IsInSVGTextSubtree(aFrame)) {
609 // Unlike containers, leaf frames do not include GetPosition() in
610 // GetCanvasTM().
611 overflowRect = overflowRect + aFrame->GetPosition();
613 int32_t appUnitsPerDevPx = aFrame->PresContext()->AppUnitsPerDevPixel();
614 gfxMatrix tm = aTransform;
615 if (aFrame->IsFrameOfType(nsIFrame::eSVG | nsIFrame::eSVGContainer)) {
616 gfx::Matrix childrenOnlyTM;
617 if (static_cast<nsSVGContainerFrame*>(aFrame)->HasChildrenOnlyTransform(
618 &childrenOnlyTM)) {
619 // Undo the children-only transform:
620 if (!childrenOnlyTM.Invert()) {
621 return;
623 tm = ThebesMatrix(childrenOnlyTM) * tm;
626 nsIntRect bounds =
627 TransformFrameRectToOuterSVG(overflowRect, tm, aFrame->PresContext())
628 .ToOutsidePixels(appUnitsPerDevPx);
629 if (!aDirtyRect->Intersects(bounds)) {
630 return;
634 /* SVG defines the following rendering model:
636 * 1. Render fill
637 * 2. Render stroke
638 * 3. Render markers
639 * 4. Apply filter
640 * 5. Apply clipping, masking, group opacity
642 * We follow this, but perform a couple of optimizations:
644 * + Use cairo's clipPath when representable natively (single object
645 * clip region).
647 * + Merge opacity and masking if both used together.
650 /* Properties are added lazily and may have been removed by a restyle,
651 so make sure all applicable ones are set again. */
652 nsSVGClipPathFrame* clipPathFrame;
653 nsTArray<nsSVGMaskFrame*> maskFrames;
654 // TODO: We currently pass nullptr instead of an nsTArray* here, but we
655 // actually should get the filter frames and then pass them into
656 // PaintFilteredFrame below! See bug 1494263.
657 if (SVGObserverUtils::GetAndObserveFilters(aFrame, nullptr) ==
658 SVGObserverUtils::eHasRefsSomeInvalid ||
659 SVGObserverUtils::GetAndObserveClipPath(aFrame, &clipPathFrame) ==
660 SVGObserverUtils::eHasRefsSomeInvalid ||
661 SVGObserverUtils::GetAndObserveMasks(aFrame, &maskFrames) ==
662 SVGObserverUtils::eHasRefsSomeInvalid) {
663 // Some resource is invalid. We shouldn't paint anything.
664 return;
667 nsSVGMaskFrame* maskFrame = maskFrames.IsEmpty() ? nullptr : maskFrames[0];
669 MixModeBlender blender(aFrame, &aContext);
670 gfxContext* target = blender.ShouldCreateDrawTargetForBlend()
671 ? blender.CreateBlendTarget(aTransform)
672 : &aContext;
674 if (!target) {
675 return;
678 /* Check if we need to do additional operations on this child's
679 * rendering, which necessitates rendering into another surface. */
680 bool shouldGenerateMask =
681 (maskUsage.opacity != 1.0f || maskUsage.shouldGenerateClipMaskLayer ||
682 maskUsage.shouldGenerateMaskLayer);
683 bool shouldPushMask = false;
685 if (shouldGenerateMask) {
686 Matrix maskTransform;
687 RefPtr<SourceSurface> maskSurface;
689 // maskFrame can be nullptr even if maskUsage.shouldGenerateMaskLayer is
690 // true. That happens when a user gives an unresolvable mask-id, such as
691 // mask:url()
692 // mask:url(#id-which-does-not-exist)
693 // Since we only uses nsSVGUtils with SVG elements, not like mask on an
694 // HTML element, we should treat an unresolvable mask as no-mask here.
695 if (maskUsage.shouldGenerateMaskLayer && maskFrame) {
696 StyleMaskMode maskMode =
697 aFrame->StyleSVGReset()->mMask.mLayers[0].mMaskMode;
698 nsSVGMaskFrame::MaskParams params(&aContext, aFrame, aTransform,
699 maskUsage.opacity, maskMode,
700 aImgParams);
701 // We want the mask to be untransformed so use the inverse of the current
702 // transform as the maskTransform to compensate.
703 maskTransform = aContext.CurrentMatrix();
704 maskTransform.Invert();
706 maskSurface = maskFrame->GetMaskForMaskedFrame(params);
708 if (!maskSurface) {
709 // Either entire surface is clipped out, or gfx buffer allocation
710 // failure in nsSVGMaskFrame::GetMaskForMaskedFrame.
711 return;
713 shouldPushMask = true;
716 if (maskUsage.shouldGenerateClipMaskLayer) {
717 RefPtr<SourceSurface> clipMaskSurface = clipPathFrame->GetClipMask(
718 aContext, aFrame, aTransform, maskSurface, maskTransform);
719 if (clipMaskSurface) {
720 // We want the mask to be untransformed so use the inverse of the
721 // current transform as the maskTransform to compensate.
722 maskTransform = aContext.CurrentMatrix();
723 maskTransform.Invert();
724 maskSurface = clipMaskSurface;
725 } else {
726 // Either entire surface is clipped out, or gfx buffer allocation
727 // failure in nsSVGClipPathFrame::GetClipMask.
728 return;
730 shouldPushMask = true;
733 if (!maskUsage.shouldGenerateClipMaskLayer &&
734 !maskUsage.shouldGenerateMaskLayer) {
735 shouldPushMask = true;
738 // SVG mask multiply opacity into maskSurface already, so we do not bother
739 // to apply opacity again.
740 if (shouldPushMask) {
741 target->PushGroupForBlendBack(gfxContentType::COLOR_ALPHA,
742 maskFrame ? 1.0 : maskUsage.opacity,
743 maskSurface, maskTransform);
747 /* If this frame has only a trivial clipPath, set up cairo's clipping now so
748 * we can just do normal painting and get it clipped appropriately.
750 if (maskUsage.shouldApplyClipPath || maskUsage.shouldApplyBasicShapeOrPath) {
751 if (maskUsage.shouldApplyClipPath) {
752 clipPathFrame->ApplyClipPath(aContext, aFrame, aTransform);
753 } else {
754 nsCSSClipPathInstance::ApplyBasicShapeOrPathClip(aContext, aFrame,
755 aTransform);
759 /* Paint the child */
761 // We know we don't have eHasRefsSomeInvalid due to the check above. We
762 // don't test for eHasNoRefs here though since even if we have that we may
763 // still have CSS filter functions to handle. We have to check the style.
764 if (aFrame->StyleEffects()->HasFilters()) {
765 nsRegion* dirtyRegion = nullptr;
766 nsRegion tmpDirtyRegion;
767 if (aDirtyRect) {
768 // aDirtyRect is in outer-<svg> device pixels, but the filter code needs
769 // it in frame space.
770 gfxMatrix userToDeviceSpace = aTransform;
771 if (userToDeviceSpace.IsSingular()) {
772 return;
774 gfxMatrix deviceToUserSpace = userToDeviceSpace;
775 deviceToUserSpace.Invert();
776 gfxRect dirtyBounds = deviceToUserSpace.TransformBounds(gfxRect(
777 aDirtyRect->x, aDirtyRect->y, aDirtyRect->width, aDirtyRect->height));
778 tmpDirtyRegion = nsLayoutUtils::RoundGfxRectToAppRect(
779 dirtyBounds, AppUnitsPerCSSPixel()) -
780 aFrame->GetPosition();
781 dirtyRegion = &tmpDirtyRegion;
784 gfxContextMatrixAutoSaveRestore autoSR(target);
786 // 'target' is currently scaled such that its user space units are CSS
787 // pixels (SVG user space units). But PaintFilteredFrame expects it to be
788 // scaled in such a way that its user space units are device pixels. So we
789 // have to adjust the scale.
790 gfxMatrix reverseScaleMatrix = nsSVGUtils::GetCSSPxToDevPxMatrix(aFrame);
791 DebugOnly<bool> invertible = reverseScaleMatrix.Invert();
792 target->SetMatrixDouble(reverseScaleMatrix * aTransform *
793 target->CurrentMatrixDouble());
795 SVGPaintCallback paintCallback;
796 nsFilterInstance::PaintFilteredFrame(aFrame, target, &paintCallback,
797 dirtyRegion, aImgParams);
798 } else {
799 svgFrame->PaintSVG(*target, aTransform, aImgParams, aDirtyRect);
802 if (maskUsage.shouldApplyClipPath || maskUsage.shouldApplyBasicShapeOrPath) {
803 aContext.PopClip();
806 if (shouldPushMask) {
807 target->PopGroupAndBlend();
810 if (blender.ShouldCreateDrawTargetForBlend()) {
811 MOZ_ASSERT(target != &aContext);
812 blender.BlendToTarget();
816 bool nsSVGUtils::HitTestClip(nsIFrame* aFrame, const gfxPoint& aPoint) {
817 // If the clip-path property references non-existent or invalid clipPath
818 // element(s) we ignore it.
819 nsSVGClipPathFrame* clipPathFrame;
820 SVGObserverUtils::GetAndObserveClipPath(aFrame, &clipPathFrame);
821 if (clipPathFrame) {
822 return clipPathFrame->PointIsInsideClipPath(aFrame, aPoint);
824 if (aFrame->StyleSVGReset()->HasClipPath()) {
825 return nsCSSClipPathInstance::HitTestBasicShapeOrPathClip(aFrame, aPoint);
827 return true;
830 nsIFrame* nsSVGUtils::HitTestChildren(nsSVGDisplayContainerFrame* aFrame,
831 const gfxPoint& aPoint) {
832 // First we transform aPoint into the coordinate space established by aFrame
833 // for its children (e.g. take account of any 'viewBox' attribute):
834 gfxPoint point = aPoint;
835 if (aFrame->GetContent()->IsSVGElement()) { // must check before cast
836 gfxMatrix m =
837 static_cast<const SVGElement*>(aFrame->GetContent())
838 ->PrependLocalTransformsTo(gfxMatrix(), eChildToUserSpace);
839 if (!m.IsIdentity()) {
840 if (!m.Invert()) {
841 return nullptr;
843 point = m.TransformPoint(point);
847 // Traverse the list in reverse order, so that if we get a hit we know that's
848 // the topmost frame that intersects the point; then we can just return it.
849 nsIFrame* result = nullptr;
850 for (nsIFrame* current = aFrame->PrincipalChildList().LastChild(); current;
851 current = current->GetPrevSibling()) {
852 nsSVGDisplayableFrame* SVGFrame = do_QueryFrame(current);
853 if (SVGFrame) {
854 const nsIContent* content = current->GetContent();
855 if (content->IsSVGElement() &&
856 !static_cast<const SVGElement*>(content)->HasValidDimensions()) {
857 continue;
859 // GetFrameForPoint() expects a point in its frame's SVG user space, so
860 // we need to convert to that space:
861 gfxPoint p = point;
862 if (content->IsSVGElement()) { // must check before cast
863 gfxMatrix m =
864 static_cast<const SVGElement*>(content)->PrependLocalTransformsTo(
865 gfxMatrix(), eUserSpaceToParent);
866 if (!m.IsIdentity()) {
867 if (!m.Invert()) {
868 continue;
870 p = m.TransformPoint(p);
873 result = SVGFrame->GetFrameForPoint(p);
874 if (result) break;
878 if (result && !HitTestClip(aFrame, aPoint)) result = nullptr;
880 return result;
883 nsRect nsSVGUtils::TransformFrameRectToOuterSVG(const nsRect& aRect,
884 const gfxMatrix& aMatrix,
885 nsPresContext* aPresContext) {
886 gfxRect r(aRect.x, aRect.y, aRect.width, aRect.height);
887 r.Scale(1.0 / AppUnitsPerCSSPixel());
888 return nsLayoutUtils::RoundGfxRectToAppRect(
889 aMatrix.TransformBounds(r), aPresContext->AppUnitsPerDevPixel());
892 IntSize nsSVGUtils::ConvertToSurfaceSize(const gfxSize& aSize,
893 bool* aResultOverflows) {
894 IntSize surfaceSize(ClampToInt(ceil(aSize.width)),
895 ClampToInt(ceil(aSize.height)));
897 *aResultOverflows = surfaceSize.width != ceil(aSize.width) ||
898 surfaceSize.height != ceil(aSize.height);
900 if (!Factory::AllowedSurfaceSize(surfaceSize)) {
901 surfaceSize.width =
902 std::min(NS_SVG_OFFSCREEN_MAX_DIMENSION, surfaceSize.width);
903 surfaceSize.height =
904 std::min(NS_SVG_OFFSCREEN_MAX_DIMENSION, surfaceSize.height);
905 *aResultOverflows = true;
908 return surfaceSize;
911 bool nsSVGUtils::HitTestRect(const gfx::Matrix& aMatrix, float aRX, float aRY,
912 float aRWidth, float aRHeight, float aX,
913 float aY) {
914 gfx::Rect rect(aRX, aRY, aRWidth, aRHeight);
915 if (rect.IsEmpty() || aMatrix.IsSingular()) {
916 return false;
918 gfx::Matrix toRectSpace = aMatrix;
919 toRectSpace.Invert();
920 gfx::Point p = toRectSpace.TransformPoint(gfx::Point(aX, aY));
921 return rect.x <= p.x && p.x <= rect.XMost() && rect.y <= p.y &&
922 p.y <= rect.YMost();
925 gfxRect nsSVGUtils::GetClipRectForFrame(nsIFrame* aFrame, float aX, float aY,
926 float aWidth, float aHeight) {
927 const nsStyleDisplay* disp = aFrame->StyleDisplay();
928 const nsStyleEffects* effects = aFrame->StyleEffects();
930 bool clipApplies =
931 disp->mOverflowX == StyleOverflow::Hidden ||
932 disp->mOverflowY == StyleOverflow::Hidden;
934 if (!clipApplies || effects->mClip.IsAuto()) {
935 return gfxRect(aX, aY, aWidth, aHeight);
938 auto& rect = effects->mClip.AsRect();
939 nsRect coordClipRect = rect.ToLayoutRect();
940 nsIntRect clipPxRect = coordClipRect.ToOutsidePixels(
941 aFrame->PresContext()->AppUnitsPerDevPixel());
942 gfxRect clipRect = gfxRect(clipPxRect.x, clipPxRect.y, clipPxRect.width,
943 clipPxRect.height);
944 if (rect.right.IsAuto()) {
945 clipRect.width = aWidth - clipRect.X();
947 if (rect.bottom.IsAuto()) {
948 clipRect.height = aHeight - clipRect.Y();
950 if (disp->mOverflowX != StyleOverflow::Hidden) {
951 clipRect.x = aX;
952 clipRect.width = aWidth;
954 if (disp->mOverflowY != StyleOverflow::Hidden) {
955 clipRect.y = aY;
956 clipRect.height = aHeight;
958 return clipRect;
961 void nsSVGUtils::SetClipRect(gfxContext* aContext, const gfxMatrix& aCTM,
962 const gfxRect& aRect) {
963 if (aCTM.IsSingular()) {
964 return;
967 gfxContextMatrixAutoSaveRestore matrixAutoSaveRestore(aContext);
968 aContext->Multiply(aCTM);
969 aContext->Clip(aRect);
972 gfxRect nsSVGUtils::GetBBox(nsIFrame* aFrame, uint32_t aFlags,
973 const gfxMatrix* aToBoundsSpace) {
974 if (aFrame->GetContent()->IsText()) {
975 aFrame = aFrame->GetParent();
978 if (nsSVGUtils::IsInSVGTextSubtree(aFrame)) {
979 // It is possible to apply a gradient, pattern, clipping path, mask or
980 // filter to text. When one of these facilities is applied to text
981 // the bounding box is the entire text element in all
982 // cases.
983 nsIFrame* ancestor = GetFirstNonAAncestorFrame(aFrame);
984 if (ancestor && nsSVGUtils::IsInSVGTextSubtree(ancestor)) {
985 while (!ancestor->IsSVGTextFrame()) {
986 ancestor = ancestor->GetParent();
989 aFrame = ancestor;
992 nsSVGDisplayableFrame* svg = do_QueryFrame(aFrame);
993 const bool hasSVGLayout = aFrame->GetStateBits() & NS_FRAME_SVG_LAYOUT;
994 if (hasSVGLayout && !svg) {
995 // An SVG frame, but not one that can be displayed directly (for
996 // example, nsGradientFrame). These can't contribute to the bbox.
997 return gfxRect();
1000 const bool isOuterSVG = svg && !hasSVGLayout;
1001 MOZ_ASSERT(!isOuterSVG || aFrame->IsSVGOuterSVGFrame());
1002 if (!svg || (isOuterSVG && (aFlags & eUseFrameBoundsForOuterSVG))) {
1003 // An HTML element or an SVG outer frame.
1004 MOZ_ASSERT(!hasSVGLayout);
1005 bool onlyCurrentFrame = aFlags & eIncludeOnlyCurrentFrameForNonSVGElement;
1006 return nsSVGIntegrationUtils::GetSVGBBoxForNonSVGFrame(
1007 aFrame,
1008 /* aUnionContinuations = */ !onlyCurrentFrame);
1011 MOZ_ASSERT(svg);
1013 nsIContent* content = aFrame->GetContent();
1014 if (content->IsSVGElement() &&
1015 !static_cast<const SVGElement*>(content)->HasValidDimensions()) {
1016 return gfxRect();
1019 // Clean out flags which have no effects on returning bbox from now, so that
1020 // we can cache and reuse ObjectBoundingBoxProperty() in the code below.
1021 aFlags &= ~eIncludeOnlyCurrentFrameForNonSVGElement;
1022 aFlags &= ~eUseFrameBoundsForOuterSVG;
1023 if (!aFrame->IsSVGUseFrame()) {
1024 aFlags &= ~eUseUserSpaceOfUseElement;
1027 if (aFlags == eBBoxIncludeFillGeometry &&
1028 // We only cache bbox in element's own user space
1029 !aToBoundsSpace) {
1030 gfxRect* prop = aFrame->GetProperty(ObjectBoundingBoxProperty());
1031 if (prop) {
1032 return *prop;
1036 gfxMatrix matrix;
1037 if (aToBoundsSpace) {
1038 matrix = *aToBoundsSpace;
1041 if (aFrame->IsSVGForeignObjectFrame() ||
1042 aFlags & nsSVGUtils::eUseUserSpaceOfUseElement) {
1043 // The spec says getBBox "Returns the tight bounding box in *current user
1044 // space*". So we should really be doing this for all elements, but that
1045 // needs investigation to check that we won't break too much content.
1046 // NOTE: When changing this to apply to other frame types, make sure to
1047 // also update nsSVGUtils::FrameSpaceInCSSPxToUserSpaceOffset.
1048 MOZ_ASSERT(content->IsSVGElement(), "bad cast");
1049 SVGElement* element = static_cast<SVGElement*>(content);
1050 matrix = element->PrependLocalTransformsTo(matrix, eChildToUserSpace);
1052 gfxRect bbox =
1053 svg->GetBBoxContribution(ToMatrix(matrix), aFlags).ToThebesRect();
1054 // Account for 'clipped'.
1055 if (aFlags & nsSVGUtils::eBBoxIncludeClipped) {
1056 gfxRect clipRect(0, 0, 0, 0);
1057 float x, y, width, height;
1058 gfxMatrix tm;
1059 gfxRect fillBBox =
1060 svg->GetBBoxContribution(ToMatrix(tm), nsSVGUtils::eBBoxIncludeFill)
1061 .ToThebesRect();
1062 x = fillBBox.x;
1063 y = fillBBox.y;
1064 width = fillBBox.width;
1065 height = fillBBox.height;
1066 bool hasClip = aFrame->StyleDisplay()->IsScrollableOverflow();
1067 if (hasClip) {
1068 clipRect = nsSVGUtils::GetClipRectForFrame(aFrame, x, y, width, height);
1069 if (aFrame->IsSVGForeignObjectFrame() || aFrame->IsSVGUseFrame()) {
1070 clipRect = matrix.TransformBounds(clipRect);
1073 nsSVGClipPathFrame* clipPathFrame;
1074 if (SVGObserverUtils::GetAndObserveClipPath(aFrame, &clipPathFrame) ==
1075 SVGObserverUtils::eHasRefsSomeInvalid) {
1076 bbox = gfxRect(0, 0, 0, 0);
1077 } else {
1078 if (clipPathFrame) {
1079 SVGClipPathElement* clipContent =
1080 static_cast<SVGClipPathElement*>(clipPathFrame->GetContent());
1081 if (clipContent->IsUnitsObjectBoundingBox()) {
1082 matrix.PreTranslate(gfxPoint(x, y));
1083 matrix.PreScale(width, height);
1084 } else if (aFrame->IsSVGForeignObjectFrame()) {
1085 matrix = gfxMatrix();
1088 matrix =
1089 nsSVGUtils::GetTransformMatrixInUserSpace(clipPathFrame) * matrix;
1091 bbox = clipPathFrame->GetBBoxForClipPathFrame(bbox, matrix, aFlags)
1092 .ToThebesRect();
1095 if (hasClip) {
1096 bbox = bbox.Intersect(clipRect);
1099 if (bbox.IsEmpty()) {
1100 bbox = gfxRect(0, 0, 0, 0);
1105 if (aFlags == eBBoxIncludeFillGeometry &&
1106 // We only cache bbox in element's own user space
1107 !aToBoundsSpace) {
1108 // Obtaining the bbox for objectBoundingBox calculations is common so we
1109 // cache the result for future calls, since calculation can be expensive:
1110 aFrame->SetProperty(ObjectBoundingBoxProperty(), new gfxRect(bbox));
1113 return bbox;
1116 gfxPoint nsSVGUtils::FrameSpaceInCSSPxToUserSpaceOffset(nsIFrame* aFrame) {
1117 if (!(aFrame->GetStateBits() & NS_FRAME_SVG_LAYOUT)) {
1118 // The user space for non-SVG frames is defined as the bounding box of the
1119 // frame's border-box rects over all continuations.
1120 return gfxPoint();
1123 // Leaf frames apply their own offset inside their user space.
1124 if (aFrame->IsFrameOfType(nsIFrame::eSVGGeometry) ||
1125 nsSVGUtils::IsInSVGTextSubtree(aFrame)) {
1126 return nsLayoutUtils::RectToGfxRect(aFrame->GetRect(),
1127 AppUnitsPerCSSPixel())
1128 .TopLeft();
1131 // For foreignObject frames, nsSVGUtils::GetBBox applies their local
1132 // transform, so we need to do the same here.
1133 if (aFrame->IsSVGForeignObjectFrame()) {
1134 gfxMatrix transform =
1135 static_cast<SVGElement*>(aFrame->GetContent())
1136 ->PrependLocalTransformsTo(gfxMatrix(), eChildToUserSpace);
1137 NS_ASSERTION(!transform.HasNonTranslation(),
1138 "we're relying on this being an offset-only transform");
1139 return transform.GetTranslation();
1142 return gfxPoint();
1145 static gfxRect GetBoundingBoxRelativeRect(const SVGAnimatedLength* aXYWH,
1146 const gfxRect& aBBox) {
1147 return gfxRect(aBBox.x + nsSVGUtils::ObjectSpace(aBBox, &aXYWH[0]),
1148 aBBox.y + nsSVGUtils::ObjectSpace(aBBox, &aXYWH[1]),
1149 nsSVGUtils::ObjectSpace(aBBox, &aXYWH[2]),
1150 nsSVGUtils::ObjectSpace(aBBox, &aXYWH[3]));
1153 gfxRect nsSVGUtils::GetRelativeRect(uint16_t aUnits,
1154 const SVGAnimatedLength* aXYWH,
1155 const gfxRect& aBBox,
1156 const UserSpaceMetrics& aMetrics) {
1157 if (aUnits == SVG_UNIT_TYPE_OBJECTBOUNDINGBOX) {
1158 return GetBoundingBoxRelativeRect(aXYWH, aBBox);
1160 return gfxRect(UserSpace(aMetrics, &aXYWH[0]), UserSpace(aMetrics, &aXYWH[1]),
1161 UserSpace(aMetrics, &aXYWH[2]),
1162 UserSpace(aMetrics, &aXYWH[3]));
1165 gfxRect nsSVGUtils::GetRelativeRect(uint16_t aUnits,
1166 const SVGAnimatedLength* aXYWH,
1167 const gfxRect& aBBox, nsIFrame* aFrame) {
1168 if (aUnits == SVG_UNIT_TYPE_OBJECTBOUNDINGBOX) {
1169 return GetBoundingBoxRelativeRect(aXYWH, aBBox);
1171 nsIContent* content = aFrame->GetContent();
1172 if (content->IsSVGElement()) {
1173 SVGElement* svgElement = static_cast<SVGElement*>(content);
1174 return GetRelativeRect(aUnits, aXYWH, aBBox, SVGElementMetrics(svgElement));
1176 return GetRelativeRect(aUnits, aXYWH, aBBox,
1177 NonSVGFrameUserSpaceMetrics(aFrame));
1180 bool nsSVGUtils::CanOptimizeOpacity(nsIFrame* aFrame) {
1181 if (!(aFrame->GetStateBits() & NS_FRAME_SVG_LAYOUT)) {
1182 return false;
1184 LayoutFrameType type = aFrame->Type();
1185 if (type != LayoutFrameType::SVGImage &&
1186 type != LayoutFrameType::SVGGeometry) {
1187 return false;
1189 if (aFrame->StyleEffects()->HasFilters()) {
1190 return false;
1192 // XXX The SVG WG is intending to allow fill, stroke and markers on <image>
1193 if (type == LayoutFrameType::SVGImage) {
1194 return true;
1196 const nsStyleSVG* style = aFrame->StyleSVG();
1197 if (style->HasMarker()) {
1198 return false;
1201 if (nsLayoutUtils::HasAnimationOfPropertySet(
1202 aFrame, nsCSSPropertyIDSet::OpacityProperties())) {
1203 return false;
1206 return !style->HasFill() || !HasStroke(aFrame);
1209 gfxMatrix nsSVGUtils::AdjustMatrixForUnits(const gfxMatrix& aMatrix,
1210 SVGAnimatedEnumeration* aUnits,
1211 nsIFrame* aFrame, uint32_t aFlags) {
1212 if (aFrame && aUnits->GetAnimValue() == SVG_UNIT_TYPE_OBJECTBOUNDINGBOX) {
1213 gfxRect bbox = GetBBox(aFrame, aFlags);
1214 gfxMatrix tm = aMatrix;
1215 tm.PreTranslate(gfxPoint(bbox.X(), bbox.Y()));
1216 tm.PreScale(bbox.Width(), bbox.Height());
1217 return tm;
1219 return aMatrix;
1222 nsIFrame* nsSVGUtils::GetFirstNonAAncestorFrame(nsIFrame* aStartFrame) {
1223 for (nsIFrame* ancestorFrame = aStartFrame; ancestorFrame;
1224 ancestorFrame = ancestorFrame->GetParent()) {
1225 if (!ancestorFrame->IsSVGAFrame()) {
1226 return ancestorFrame;
1229 return nullptr;
1232 bool nsSVGUtils::GetNonScalingStrokeTransform(nsIFrame* aFrame,
1233 gfxMatrix* aUserToOuterSVG) {
1234 if (aFrame->GetContent()->IsText()) {
1235 aFrame = aFrame->GetParent();
1238 if (!aFrame->StyleSVGReset()->HasNonScalingStroke()) {
1239 return false;
1242 MOZ_ASSERT(aFrame->GetContent()->IsSVGElement(), "should be an SVG element");
1244 *aUserToOuterSVG = ThebesMatrix(SVGContentUtils::GetCTM(
1245 static_cast<SVGElement*>(aFrame->GetContent()), true));
1247 return aUserToOuterSVG->HasNonTranslation();
1250 // The logic here comes from _cairo_stroke_style_max_distance_from_path
1251 static gfxRect PathExtentsToMaxStrokeExtents(const gfxRect& aPathExtents,
1252 nsIFrame* aFrame,
1253 double aStyleExpansionFactor,
1254 const gfxMatrix& aMatrix) {
1255 double style_expansion =
1256 aStyleExpansionFactor * nsSVGUtils::GetStrokeWidth(aFrame);
1258 gfxMatrix matrix = aMatrix;
1260 gfxMatrix outerSVGToUser;
1261 if (nsSVGUtils::GetNonScalingStrokeTransform(aFrame, &outerSVGToUser)) {
1262 outerSVGToUser.Invert();
1263 matrix.PreMultiply(outerSVGToUser);
1266 double dx = style_expansion * (fabs(matrix._11) + fabs(matrix._21));
1267 double dy = style_expansion * (fabs(matrix._22) + fabs(matrix._12));
1269 gfxRect strokeExtents = aPathExtents;
1270 strokeExtents.Inflate(dx, dy);
1271 return strokeExtents;
1274 /*static*/
1275 gfxRect nsSVGUtils::PathExtentsToMaxStrokeExtents(const gfxRect& aPathExtents,
1276 nsTextFrame* aFrame,
1277 const gfxMatrix& aMatrix) {
1278 NS_ASSERTION(nsSVGUtils::IsInSVGTextSubtree(aFrame),
1279 "expected an nsTextFrame for SVG text");
1280 return ::PathExtentsToMaxStrokeExtents(aPathExtents, aFrame, 0.5, aMatrix);
1283 /*static*/
1284 gfxRect nsSVGUtils::PathExtentsToMaxStrokeExtents(const gfxRect& aPathExtents,
1285 SVGGeometryFrame* aFrame,
1286 const gfxMatrix& aMatrix) {
1287 bool strokeMayHaveCorners =
1288 !SVGContentUtils::ShapeTypeHasNoCorners(aFrame->GetContent());
1290 // For a shape without corners the stroke can only extend half the stroke
1291 // width from the path in the x/y-axis directions. For shapes with corners
1292 // the stroke can extend by sqrt(1/2) (think 45 degree rotated rect, or line
1293 // with stroke-linecaps="square").
1294 double styleExpansionFactor = strokeMayHaveCorners ? M_SQRT1_2 : 0.5;
1296 // The stroke can extend even further for paths that can be affected by
1297 // stroke-miterlimit.
1298 // We only need to do this if the limit is greater than 1, but it's probably
1299 // not worth optimizing for that.
1300 bool affectedByMiterlimit = aFrame->GetContent()->IsAnyOfSVGElements(
1301 nsGkAtoms::path, nsGkAtoms::polyline, nsGkAtoms::polygon);
1303 if (affectedByMiterlimit) {
1304 const nsStyleSVG* style = aFrame->StyleSVG();
1305 if (style->mStrokeLinejoin == NS_STYLE_STROKE_LINEJOIN_MITER &&
1306 styleExpansionFactor < style->mStrokeMiterlimit / 2.0) {
1307 styleExpansionFactor = style->mStrokeMiterlimit / 2.0;
1311 return ::PathExtentsToMaxStrokeExtents(aPathExtents, aFrame,
1312 styleExpansionFactor, aMatrix);
1315 // ----------------------------------------------------------------------
1317 /* static */
1318 nscolor nsSVGUtils::GetFallbackOrPaintColor(
1319 const ComputedStyle& aStyle, StyleSVGPaint nsStyleSVG::*aFillOrStroke) {
1320 const auto& paint = aStyle.StyleSVG()->*aFillOrStroke;
1321 nscolor color;
1322 switch (paint.kind.tag) {
1323 case StyleSVGPaintKind::Tag::PaintServer:
1324 case StyleSVGPaintKind::Tag::ContextStroke:
1325 color = paint.fallback.IsColor()
1326 ? paint.fallback.AsColor().CalcColor(aStyle)
1327 : NS_RGBA(0, 0, 0, 0);
1328 break;
1329 case StyleSVGPaintKind::Tag::ContextFill:
1330 color = paint.fallback.IsColor()
1331 ? paint.fallback.AsColor().CalcColor(aStyle)
1332 : NS_RGB(0, 0, 0);
1333 break;
1334 default:
1335 color = paint.kind.AsColor().CalcColor(aStyle);
1336 break;
1338 if (const auto* styleIfVisited = aStyle.GetStyleIfVisited()) {
1339 const auto& paintIfVisited = styleIfVisited->StyleSVG()->*aFillOrStroke;
1340 // To prevent Web content from detecting if a user has visited a URL
1341 // (via URL loading triggered by paint servers or performance
1342 // differences between paint servers or between a paint server and a
1343 // color), we do not allow whether links are visited to change which
1344 // paint server is used or switch between paint servers and simple
1345 // colors. A :visited style may only override a simple color with
1346 // another simple color.
1347 if (paintIfVisited.kind.IsColor() && paint.kind.IsColor()) {
1348 nscolor colors[2] = {
1349 color, paintIfVisited.kind.AsColor().CalcColor(*styleIfVisited)};
1350 return ComputedStyle::CombineVisitedColors(colors,
1351 aStyle.RelevantLinkVisited());
1354 return color;
1357 /* static */
1358 void nsSVGUtils::MakeFillPatternFor(nsIFrame* aFrame, gfxContext* aContext,
1359 GeneralPattern* aOutPattern,
1360 imgDrawingParams& aImgParams,
1361 SVGContextPaint* aContextPaint) {
1362 const nsStyleSVG* style = aFrame->StyleSVG();
1363 if (style->mFill.kind.IsNone()) {
1364 return;
1367 const float opacity = aFrame->StyleEffects()->mOpacity;
1369 float fillOpacity = GetOpacity(style->FillOpacitySource(),
1370 style->mFillOpacity, aContextPaint);
1371 if (opacity < 1.0f && nsSVGUtils::CanOptimizeOpacity(aFrame)) {
1372 // Combine the group opacity into the fill opacity (we will have skipped
1373 // creating an offscreen surface to apply the group opacity).
1374 fillOpacity *= opacity;
1377 const DrawTarget* dt = aContext->GetDrawTarget();
1379 nsSVGPaintServerFrame* ps =
1380 SVGObserverUtils::GetAndObservePaintServer(aFrame, &nsStyleSVG::mFill);
1382 if (ps) {
1383 RefPtr<gfxPattern> pattern =
1384 ps->GetPaintServerPattern(aFrame, dt, aContext->CurrentMatrixDouble(),
1385 &nsStyleSVG::mFill, fillOpacity, aImgParams);
1386 if (pattern) {
1387 pattern->CacheColorStops(dt);
1388 aOutPattern->Init(*pattern->GetPattern(dt));
1389 return;
1393 if (aContextPaint) {
1394 RefPtr<gfxPattern> pattern;
1395 switch (style->mFill.kind.tag) {
1396 case StyleSVGPaintKind::Tag::ContextFill:
1397 pattern = aContextPaint->GetFillPattern(
1398 dt, fillOpacity, aContext->CurrentMatrixDouble(), aImgParams);
1399 break;
1400 case StyleSVGPaintKind::Tag::ContextStroke:
1401 pattern = aContextPaint->GetStrokePattern(
1402 dt, fillOpacity, aContext->CurrentMatrixDouble(), aImgParams);
1403 break;
1404 default:;
1406 if (pattern) {
1407 aOutPattern->Init(*pattern->GetPattern(dt));
1408 return;
1412 if (style->mFill.fallback.IsNone()) {
1413 return;
1416 // On failure, use the fallback colour in case we have an
1417 // objectBoundingBox where the width or height of the object is zero.
1418 // See http://www.w3.org/TR/SVG11/coords.html#ObjectBoundingBox
1419 Color color(Color::FromABGR(
1420 GetFallbackOrPaintColor(*aFrame->Style(), &nsStyleSVG::mFill)));
1421 color.a *= fillOpacity;
1422 aOutPattern->InitColorPattern(ToDeviceColor(color));
1425 /* static */
1426 void nsSVGUtils::MakeStrokePatternFor(nsIFrame* aFrame, gfxContext* aContext,
1427 GeneralPattern* aOutPattern,
1428 imgDrawingParams& aImgParams,
1429 SVGContextPaint* aContextPaint) {
1430 const nsStyleSVG* style = aFrame->StyleSVG();
1431 if (style->mStroke.kind.IsNone()) {
1432 return;
1435 const float opacity = aFrame->StyleEffects()->mOpacity;
1437 float strokeOpacity = GetOpacity(style->StrokeOpacitySource(),
1438 style->mStrokeOpacity, aContextPaint);
1439 if (opacity < 1.0f && nsSVGUtils::CanOptimizeOpacity(aFrame)) {
1440 // Combine the group opacity into the stroke opacity (we will have skipped
1441 // creating an offscreen surface to apply the group opacity).
1442 strokeOpacity *= opacity;
1445 const DrawTarget* dt = aContext->GetDrawTarget();
1447 nsSVGPaintServerFrame* ps =
1448 SVGObserverUtils::GetAndObservePaintServer(aFrame, &nsStyleSVG::mStroke);
1450 if (ps) {
1451 RefPtr<gfxPattern> pattern = ps->GetPaintServerPattern(
1452 aFrame, dt, aContext->CurrentMatrixDouble(), &nsStyleSVG::mStroke,
1453 strokeOpacity, aImgParams);
1454 if (pattern) {
1455 pattern->CacheColorStops(dt);
1456 aOutPattern->Init(*pattern->GetPattern(dt));
1457 return;
1461 if (aContextPaint) {
1462 RefPtr<gfxPattern> pattern;
1463 switch (style->mStroke.kind.tag) {
1464 case StyleSVGPaintKind::Tag::ContextFill:
1465 pattern = aContextPaint->GetFillPattern(
1466 dt, strokeOpacity, aContext->CurrentMatrixDouble(), aImgParams);
1467 break;
1468 case StyleSVGPaintKind::Tag::ContextStroke:
1469 pattern = aContextPaint->GetStrokePattern(
1470 dt, strokeOpacity, aContext->CurrentMatrixDouble(), aImgParams);
1471 break;
1472 default:;
1474 if (pattern) {
1475 aOutPattern->Init(*pattern->GetPattern(dt));
1476 return;
1480 if (style->mStroke.fallback.IsNone()) {
1481 return;
1484 // On failure, use the fallback colour in case we have an
1485 // objectBoundingBox where the width or height of the object is zero.
1486 // See http://www.w3.org/TR/SVG11/coords.html#ObjectBoundingBox
1487 Color color(Color::FromABGR(
1488 GetFallbackOrPaintColor(*aFrame->Style(), &nsStyleSVG::mStroke)));
1489 color.a *= strokeOpacity;
1490 aOutPattern->InitColorPattern(ToDeviceColor(color));
1493 /* static */
1494 float nsSVGUtils::GetOpacity(nsStyleSVGOpacitySource aOpacityType,
1495 const float& aOpacity,
1496 SVGContextPaint* aContextPaint) {
1497 float opacity = 1.0f;
1498 switch (aOpacityType) {
1499 case eStyleSVGOpacitySource_Normal:
1500 opacity = aOpacity;
1501 break;
1502 case eStyleSVGOpacitySource_ContextFillOpacity:
1503 if (aContextPaint) {
1504 opacity = aContextPaint->GetFillOpacity();
1506 break;
1507 case eStyleSVGOpacitySource_ContextStrokeOpacity:
1508 if (aContextPaint) {
1509 opacity = aContextPaint->GetStrokeOpacity();
1511 break;
1512 default:
1513 MOZ_ASSERT_UNREACHABLE(
1514 "Unknown object opacity inheritance type for SVG "
1515 "glyph");
1517 return opacity;
1520 bool nsSVGUtils::HasStroke(nsIFrame* aFrame, SVGContextPaint* aContextPaint) {
1521 const nsStyleSVG* style = aFrame->StyleSVG();
1522 return style->HasStroke() && GetStrokeWidth(aFrame, aContextPaint) > 0;
1525 float nsSVGUtils::GetStrokeWidth(nsIFrame* aFrame,
1526 SVGContextPaint* aContextPaint) {
1527 const nsStyleSVG* style = aFrame->StyleSVG();
1528 if (aContextPaint && style->StrokeWidthFromObject()) {
1529 return aContextPaint->GetStrokeWidth();
1532 nsIContent* content = aFrame->GetContent();
1533 if (content->IsText()) {
1534 content = content->GetParent();
1537 SVGElement* ctx = static_cast<SVGElement*>(content);
1538 return SVGContentUtils::CoordToFloat(ctx, style->mStrokeWidth);
1541 void nsSVGUtils::SetupStrokeGeometry(nsIFrame* aFrame, gfxContext* aContext,
1542 SVGContextPaint* aContextPaint) {
1543 SVGContentUtils::AutoStrokeOptions strokeOptions;
1544 SVGContentUtils::GetStrokeOptions(
1545 &strokeOptions, static_cast<SVGElement*>(aFrame->GetContent()),
1546 aFrame->Style(), aContextPaint);
1548 if (strokeOptions.mLineWidth <= 0) {
1549 return;
1552 aContext->SetLineWidth(strokeOptions.mLineWidth);
1553 aContext->SetLineCap(strokeOptions.mLineCap);
1554 aContext->SetMiterLimit(strokeOptions.mMiterLimit);
1555 aContext->SetLineJoin(strokeOptions.mLineJoin);
1556 aContext->SetDash(strokeOptions.mDashPattern, strokeOptions.mDashLength,
1557 strokeOptions.mDashOffset);
1560 uint16_t nsSVGUtils::GetGeometryHitTestFlags(nsIFrame* aFrame) {
1561 uint16_t flags = 0;
1563 switch (aFrame->StyleUI()->mPointerEvents) {
1564 case NS_STYLE_POINTER_EVENTS_NONE:
1565 break;
1566 case NS_STYLE_POINTER_EVENTS_AUTO:
1567 case NS_STYLE_POINTER_EVENTS_VISIBLEPAINTED:
1568 if (aFrame->StyleVisibility()->IsVisible()) {
1569 if (!aFrame->StyleSVG()->mFill.kind.IsNone())
1570 flags |= SVG_HIT_TEST_FILL;
1571 if (!aFrame->StyleSVG()->mStroke.kind.IsNone())
1572 flags |= SVG_HIT_TEST_STROKE;
1573 if (aFrame->StyleSVG()->mStrokeOpacity > 0)
1574 flags |= SVG_HIT_TEST_CHECK_MRECT;
1576 break;
1577 case NS_STYLE_POINTER_EVENTS_VISIBLEFILL:
1578 if (aFrame->StyleVisibility()->IsVisible()) {
1579 flags |= SVG_HIT_TEST_FILL;
1581 break;
1582 case NS_STYLE_POINTER_EVENTS_VISIBLESTROKE:
1583 if (aFrame->StyleVisibility()->IsVisible()) {
1584 flags |= SVG_HIT_TEST_STROKE;
1586 break;
1587 case NS_STYLE_POINTER_EVENTS_VISIBLE:
1588 if (aFrame->StyleVisibility()->IsVisible()) {
1589 flags |= SVG_HIT_TEST_FILL | SVG_HIT_TEST_STROKE;
1591 break;
1592 case NS_STYLE_POINTER_EVENTS_PAINTED:
1593 if (!aFrame->StyleSVG()->mFill.kind.IsNone()) flags |= SVG_HIT_TEST_FILL;
1594 if (!aFrame->StyleSVG()->mStroke.kind.IsNone())
1595 flags |= SVG_HIT_TEST_STROKE;
1596 if (aFrame->StyleSVG()->mStrokeOpacity) flags |= SVG_HIT_TEST_CHECK_MRECT;
1597 break;
1598 case NS_STYLE_POINTER_EVENTS_FILL:
1599 flags |= SVG_HIT_TEST_FILL;
1600 break;
1601 case NS_STYLE_POINTER_EVENTS_STROKE:
1602 flags |= SVG_HIT_TEST_STROKE;
1603 break;
1604 case NS_STYLE_POINTER_EVENTS_ALL:
1605 flags |= SVG_HIT_TEST_FILL | SVG_HIT_TEST_STROKE;
1606 break;
1607 default:
1608 NS_ERROR("not reached");
1609 break;
1612 return flags;
1615 void nsSVGUtils::PaintSVGGlyph(Element* aElement, gfxContext* aContext) {
1616 nsIFrame* frame = aElement->GetPrimaryFrame();
1617 nsSVGDisplayableFrame* svgFrame = do_QueryFrame(frame);
1618 if (!svgFrame) {
1619 return;
1621 gfxMatrix m;
1622 if (frame->GetContent()->IsSVGElement()) {
1623 // PaintSVG() expects the passed transform to be the transform to its own
1624 // SVG user space, so we need to account for any 'transform' attribute:
1625 m = nsSVGUtils::GetTransformMatrixInUserSpace(frame);
1628 // SVG-in-OpenType is not allowed to paint external resources, so we can
1629 // just pass a dummy params into PatintSVG.
1630 imgDrawingParams dummy;
1631 svgFrame->PaintSVG(*aContext, m, dummy);
1634 bool nsSVGUtils::GetSVGGlyphExtents(Element* aElement,
1635 const gfxMatrix& aSVGToAppSpace,
1636 gfxRect* aResult) {
1637 nsIFrame* frame = aElement->GetPrimaryFrame();
1638 nsSVGDisplayableFrame* svgFrame = do_QueryFrame(frame);
1639 if (!svgFrame) {
1640 return false;
1643 gfxMatrix transform(aSVGToAppSpace);
1644 nsIContent* content = frame->GetContent();
1645 if (content->IsSVGElement()) {
1646 transform = static_cast<SVGElement*>(content)->PrependLocalTransformsTo(
1647 aSVGToAppSpace);
1650 *aResult =
1651 svgFrame
1652 ->GetBBoxContribution(gfx::ToMatrix(transform),
1653 nsSVGUtils::eBBoxIncludeFill |
1654 nsSVGUtils::eBBoxIncludeFillGeometry |
1655 nsSVGUtils::eBBoxIncludeStroke |
1656 nsSVGUtils::eBBoxIncludeStrokeGeometry |
1657 nsSVGUtils::eBBoxIncludeMarkers)
1658 .ToThebesRect();
1659 return true;
1662 nsRect nsSVGUtils::ToCanvasBounds(const gfxRect& aUserspaceRect,
1663 const gfxMatrix& aToCanvas,
1664 const nsPresContext* presContext) {
1665 return nsLayoutUtils::RoundGfxRectToAppRect(
1666 aToCanvas.TransformBounds(aUserspaceRect),
1667 presContext->AppUnitsPerDevPixel());
1670 gfxMatrix nsSVGUtils::GetCSSPxToDevPxMatrix(nsIFrame* aNonSVGFrame) {
1671 int32_t appUnitsPerDevPixel =
1672 aNonSVGFrame->PresContext()->AppUnitsPerDevPixel();
1673 float devPxPerCSSPx =
1674 1 / nsPresContext::AppUnitsToFloatCSSPixels(appUnitsPerDevPixel);
1676 return gfxMatrix(devPxPerCSSPx, 0.0, 0.0, devPxPerCSSPx, 0.0, 0.0);
1679 gfxMatrix nsSVGUtils::GetTransformMatrixInUserSpace(const nsIFrame* aFrame) {
1680 // We check element instead of aFrame directly because SVG element
1681 // may have non-SVG frame, <tspan> for example.
1682 MOZ_ASSERT(aFrame->GetContent() && aFrame->GetContent()->IsSVGElement(),
1683 "Only use this wrapper for SVG elements");
1685 if (!aFrame->IsTransformed()) {
1686 return {};
1689 nsDisplayTransform::FrameTransformProperties properties{
1690 aFrame, AppUnitsPerCSSPixel(), nullptr};
1691 nsStyleTransformMatrix::TransformReferenceBox refBox;
1692 refBox.Init(aFrame);
1694 // SVG elements can have x/y offset, their default transform origin
1695 // is the origin of user space, not the top left point of the frame.
1696 Point3D svgTransformOrigin{
1697 properties.mToTransformOrigin.x - CSSPixel::FromAppUnits(refBox.X()),
1698 properties.mToTransformOrigin.y - CSSPixel::FromAppUnits(refBox.Y()),
1699 properties.mToTransformOrigin.z};
1701 Matrix svgTransform;
1702 Matrix4x4 trans;
1703 (void)aFrame->IsSVGTransformed(&svgTransform);
1705 if (properties.HasTransform()) {
1706 trans = nsStyleTransformMatrix::ReadTransforms(
1707 properties.mTranslate, properties.mRotate, properties.mScale,
1708 properties.mMotion, properties.mTransform, refBox,
1709 AppUnitsPerCSSPixel());
1710 } else {
1711 trans = Matrix4x4::From2D(svgTransform);
1714 trans.ChangeBasis(svgTransformOrigin);
1716 Matrix mm;
1717 trans.ProjectTo2D();
1718 (void)trans.CanDraw2D(&mm);
1720 return ThebesMatrix(mm);