Backed out changeset 2960ea3e50ca (bug 1881157) for causing crashtest assertion failu...
[gecko.git] / layout / svg / SVGIntegrationUtils.cpp
blobab32ddee530fa8ec6aa74481f30063271fc27952
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 #include "SVGIntegrationUtils.h"
10 // Keep others in (case-insensitive) order:
11 #include "gfxDrawable.h"
13 #include "nsCSSAnonBoxes.h"
14 #include "nsCSSRendering.h"
15 #include "nsDisplayList.h"
16 #include "nsLayoutUtils.h"
17 #include "gfxContext.h"
18 #include "SVGPaintServerFrame.h"
19 #include "mozilla/gfx/Point.h"
20 #include "mozilla/CSSClipPathInstance.h"
21 #include "mozilla/FilterInstance.h"
22 #include "mozilla/StaticPrefs_layers.h"
23 #include "mozilla/SVGClipPathFrame.h"
24 #include "mozilla/SVGObserverUtils.h"
25 #include "mozilla/SVGMaskFrame.h"
26 #include "mozilla/SVGUtils.h"
27 #include "mozilla/Unused.h"
28 #include "mozilla/dom/SVGElement.h"
30 using namespace mozilla::dom;
31 using namespace mozilla::layers;
32 using namespace mozilla::gfx;
33 using namespace mozilla::image;
35 namespace mozilla {
37 /**
38 * This class is used to get the pre-effects ink overflow rect of a frame,
39 * or, in the case of a frame with continuations, to collect the union of the
40 * pre-effects ink overflow rects of all the continuations. The result is
41 * relative to the origin (top left corner of the border box) of the frame, or,
42 * if the frame has continuations, the origin of the _first_ continuation.
44 class PreEffectsInkOverflowCollector : public nsLayoutUtils::BoxCallback {
45 public:
46 /**
47 * If the pre-effects ink overflow rect of the frame being examined
48 * happens to be known, it can be passed in as aCurrentFrame and its
49 * pre-effects ink overflow rect can be passed in as
50 * aCurrentFrameOverflowArea. This is just an optimization to save a
51 * frame property lookup - these arguments are optional.
53 PreEffectsInkOverflowCollector(nsIFrame* aFirstContinuation,
54 nsIFrame* aCurrentFrame,
55 const nsRect& aCurrentFrameOverflowArea,
56 bool aInReflow)
57 : mFirstContinuation(aFirstContinuation),
58 mCurrentFrame(aCurrentFrame),
59 mCurrentFrameOverflowArea(aCurrentFrameOverflowArea),
60 mInReflow(aInReflow) {
61 NS_ASSERTION(!mFirstContinuation->GetPrevContinuation(),
62 "We want the first continuation here");
65 void AddBox(nsIFrame* aFrame) override {
66 nsRect overflow = (aFrame == mCurrentFrame)
67 ? mCurrentFrameOverflowArea
68 : PreEffectsInkOverflowRect(aFrame, mInReflow);
69 mResult.UnionRect(mResult,
70 overflow + aFrame->GetOffsetTo(mFirstContinuation));
73 nsRect GetResult() const { return mResult; }
75 private:
76 static nsRect PreEffectsInkOverflowRect(nsIFrame* aFrame, bool aInReflow) {
77 nsRect* r = aFrame->GetProperty(nsIFrame::PreEffectsBBoxProperty());
78 if (r) {
79 return *r;
82 #ifdef DEBUG
83 // Having PreTransformOverflowAreasProperty cached means
84 // InkOverflowRect() will return post-effect rect, which is not what
85 // we want. This function intentional reports pre-effect rect. But it does
86 // not matter if there is no SVG effect on this frame, since no effect
87 // means post-effect rect matches pre-effect rect.
89 // This function may be called during reflow or painting. We should only
90 // do this check in painting process since the PreEffectsBBoxProperty of
91 // continuations are not set correctly while reflowing.
92 if (SVGIntegrationUtils::UsingOverflowAffectingEffects(aFrame) &&
93 !aInReflow) {
94 OverflowAreas* preTransformOverflows =
95 aFrame->GetProperty(nsIFrame::PreTransformOverflowAreasProperty());
97 MOZ_ASSERT(!preTransformOverflows,
98 "InkOverflowRect() won't return the pre-effects rect!");
100 #endif
101 return aFrame->InkOverflowRectRelativeToSelf();
104 nsIFrame* mFirstContinuation;
105 nsIFrame* mCurrentFrame;
106 const nsRect& mCurrentFrameOverflowArea;
107 nsRect mResult;
108 bool mInReflow;
112 * Gets the union of the pre-effects ink overflow rects of all of a frame's
113 * continuations, in "user space".
115 static nsRect GetPreEffectsInkOverflowUnion(
116 nsIFrame* aFirstContinuation, nsIFrame* aCurrentFrame,
117 const nsRect& aCurrentFramePreEffectsOverflow,
118 const nsPoint& aFirstContinuationToUserSpace, bool aInReflow) {
119 NS_ASSERTION(!aFirstContinuation->GetPrevContinuation(),
120 "Need first continuation here");
121 PreEffectsInkOverflowCollector collector(aFirstContinuation, aCurrentFrame,
122 aCurrentFramePreEffectsOverflow,
123 aInReflow);
124 // Compute union of all overflow areas relative to aFirstContinuation:
125 nsLayoutUtils::GetAllInFlowBoxes(aFirstContinuation, &collector);
126 // Return the result in user space:
127 return collector.GetResult() + aFirstContinuationToUserSpace;
131 * Gets the pre-effects ink overflow rect of aCurrentFrame in "user space".
133 static nsRect GetPreEffectsInkOverflow(
134 nsIFrame* aFirstContinuation, nsIFrame* aCurrentFrame,
135 const nsPoint& aFirstContinuationToUserSpace) {
136 NS_ASSERTION(!aFirstContinuation->GetPrevContinuation(),
137 "Need first continuation here");
138 PreEffectsInkOverflowCollector collector(aFirstContinuation, nullptr,
139 nsRect(), false);
140 // Compute overflow areas of current frame relative to aFirstContinuation:
141 nsLayoutUtils::AddBoxesForFrame(aCurrentFrame, &collector);
142 // Return the result in user space:
143 return collector.GetResult() + aFirstContinuationToUserSpace;
146 bool SVGIntegrationUtils::UsingOverflowAffectingEffects(
147 const nsIFrame* aFrame) {
148 // Currently overflow don't take account of SVG or other non-absolute
149 // positioned clipping, or masking.
150 return aFrame->StyleEffects()->HasFilters();
153 bool SVGIntegrationUtils::UsingEffectsForFrame(const nsIFrame* aFrame) {
154 // Even when SVG display lists are disabled, returning true for SVG frames
155 // does not adversely affect any of our callers. Therefore we don't bother
156 // checking the SDL prefs here, since we don't know if we're being called for
157 // painting or hit-testing anyway.
158 const nsStyleSVGReset* style = aFrame->StyleSVGReset();
159 const nsStyleEffects* effects = aFrame->StyleEffects();
160 // TODO(cbrewster): remove backdrop-filter from this list once it is supported
161 // in preserve-3d cases.
162 return effects->HasFilters() || effects->HasBackdropFilters() ||
163 style->HasClipPath() || style->HasMask();
166 nsPoint SVGIntegrationUtils::GetOffsetToBoundingBox(nsIFrame* aFrame) {
167 if (aFrame->HasAnyStateBits(NS_FRAME_SVG_LAYOUT)) {
168 // Do NOT call GetAllInFlowRectsUnion for SVG - it will get the
169 // covered region relative to the SVGOuterSVGFrame, which is absolutely
170 // not what we want. SVG frames are always in user space, so they have
171 // no offset adjustment to make.
172 return nsPoint();
175 // The GetAllInFlowRectsUnion() call gets the union of the frame border-box
176 // rects over all continuations, relative to the origin (top-left of the
177 // border box) of its second argument (here, aFrame, the first continuation).
178 return -nsLayoutUtils::GetAllInFlowRectsUnion(aFrame, aFrame).TopLeft();
181 struct EffectOffsets {
182 // The offset between the reference frame and the bounding box of the
183 // target frame in app unit.
184 nsPoint offsetToBoundingBox;
185 // The offset between the reference frame and the bounding box of the
186 // target frame in app unit.
187 nsPoint offsetToUserSpace;
188 // The offset between the reference frame and the bounding box of the
189 // target frame in device unit.
190 gfxPoint offsetToUserSpaceInDevPx;
193 static EffectOffsets ComputeEffectOffset(
194 nsIFrame* aFrame, const SVGIntegrationUtils::PaintFramesParams& aParams) {
195 EffectOffsets result;
197 result.offsetToBoundingBox =
198 aParams.builder->ToReferenceFrame(aFrame) -
199 SVGIntegrationUtils::GetOffsetToBoundingBox(aFrame);
200 if (!aFrame->IsSVGFrame()) {
201 /* Snap the offset if the reference frame is not a SVG frame,
202 * since other frames will be snapped to pixel when rendering. */
203 result.offsetToBoundingBox =
204 nsPoint(aFrame->PresContext()->RoundAppUnitsToNearestDevPixels(
205 result.offsetToBoundingBox.x),
206 aFrame->PresContext()->RoundAppUnitsToNearestDevPixels(
207 result.offsetToBoundingBox.y));
210 // After applying only "aOffsetToBoundingBox", aParams.ctx would have its
211 // origin at the top left corner of frame's bounding box (over all
212 // continuations).
213 // However, SVG painting needs the origin to be located at the origin of the
214 // SVG frame's "user space", i.e. the space in which, for example, the
215 // frame's BBox lives.
216 // SVG geometry frames and foreignObject frames apply their own offsets, so
217 // their position is relative to their user space. So for these frame types,
218 // if we want aParams.ctx to be in user space, we first need to subtract the
219 // frame's position so that SVG painting can later add it again and the
220 // frame is painted in the right place.
221 gfxPoint toUserSpaceGfx =
222 SVGUtils::FrameSpaceInCSSPxToUserSpaceOffset(aFrame);
223 nsPoint toUserSpace =
224 nsPoint(nsPresContext::CSSPixelsToAppUnits(float(toUserSpaceGfx.x)),
225 nsPresContext::CSSPixelsToAppUnits(float(toUserSpaceGfx.y)));
227 result.offsetToUserSpace = result.offsetToBoundingBox - toUserSpace;
229 #ifdef DEBUG
230 bool hasSVGLayout = aFrame->HasAnyStateBits(NS_FRAME_SVG_LAYOUT);
231 NS_ASSERTION(
232 hasSVGLayout || result.offsetToBoundingBox == result.offsetToUserSpace,
233 "For non-SVG frames there shouldn't be any additional offset");
234 #endif
236 result.offsetToUserSpaceInDevPx = nsLayoutUtils::PointToGfxPoint(
237 result.offsetToUserSpace, aFrame->PresContext()->AppUnitsPerDevPixel());
239 return result;
243 * Setup transform matrix of a gfx context by a specific frame. Move the
244 * origin of aParams.ctx to the user space of aFrame.
246 static EffectOffsets MoveContextOriginToUserSpace(
247 nsIFrame* aFrame, const SVGIntegrationUtils::PaintFramesParams& aParams) {
248 EffectOffsets offset = ComputeEffectOffset(aFrame, aParams);
250 aParams.ctx.SetMatrixDouble(aParams.ctx.CurrentMatrixDouble().PreTranslate(
251 offset.offsetToUserSpaceInDevPx));
253 return offset;
256 gfxPoint SVGIntegrationUtils::GetOffsetToUserSpaceInDevPx(
257 nsIFrame* aFrame, const PaintFramesParams& aParams) {
258 nsIFrame* firstFrame =
259 nsLayoutUtils::FirstContinuationOrIBSplitSibling(aFrame);
260 EffectOffsets offset = ComputeEffectOffset(firstFrame, aParams);
261 return offset.offsetToUserSpaceInDevPx;
264 /* static */
265 nsSize SVGIntegrationUtils::GetContinuationUnionSize(nsIFrame* aNonSVGFrame) {
266 NS_ASSERTION(!aNonSVGFrame->IsSVGFrame(), "SVG frames should not get here");
267 nsIFrame* firstFrame =
268 nsLayoutUtils::FirstContinuationOrIBSplitSibling(aNonSVGFrame);
269 return nsLayoutUtils::GetAllInFlowRectsUnion(firstFrame, firstFrame).Size();
272 /* static */ gfx::Size SVGIntegrationUtils::GetSVGCoordContextForNonSVGFrame(
273 nsIFrame* aNonSVGFrame) {
274 NS_ASSERTION(!aNonSVGFrame->IsSVGFrame(), "SVG frames should not get here");
275 nsIFrame* firstFrame =
276 nsLayoutUtils::FirstContinuationOrIBSplitSibling(aNonSVGFrame);
277 nsRect r = nsLayoutUtils::GetAllInFlowRectsUnion(firstFrame, firstFrame);
278 return gfx::Size(nsPresContext::AppUnitsToFloatCSSPixels(r.width),
279 nsPresContext::AppUnitsToFloatCSSPixels(r.height));
282 gfxRect SVGIntegrationUtils::GetSVGBBoxForNonSVGFrame(
283 nsIFrame* aNonSVGFrame, bool aUnionContinuations) {
284 // Except for SVGOuterSVGFrame, we shouldn't be getting here with SVG
285 // frames at all. This function is for elements that are laid out using the
286 // CSS box model rules.
287 NS_ASSERTION(!aNonSVGFrame->HasAnyStateBits(NS_FRAME_SVG_LAYOUT),
288 "Frames with SVG layout should not get here");
290 nsIFrame* firstFrame =
291 nsLayoutUtils::FirstContinuationOrIBSplitSibling(aNonSVGFrame);
292 // 'r' is in "user space":
293 nsRect r = (aUnionContinuations)
294 ? GetPreEffectsInkOverflowUnion(
295 firstFrame, nullptr, nsRect(),
296 GetOffsetToBoundingBox(firstFrame), false)
297 : GetPreEffectsInkOverflow(firstFrame, aNonSVGFrame,
298 GetOffsetToBoundingBox(firstFrame));
300 return nsLayoutUtils::RectToGfxRect(r, AppUnitsPerCSSPixel());
303 // XXX Since we're called during reflow, this method is broken for frames with
304 // continuations. When we're called for a frame with continuations, we're
305 // called for each continuation in turn as it's reflowed. However, it isn't
306 // until the last continuation is reflowed that this method's
307 // GetOffsetToBoundingBox() and GetPreEffectsInkOverflowUnion() calls will
308 // obtain valid border boxes for all the continuations. As a result, we'll
309 // end up returning bogus post-filter ink overflow rects for all the prior
310 // continuations. Unfortunately, by the time the last continuation is
311 // reflowed, it's too late to go back and set and propagate the overflow
312 // rects on the previous continuations.
314 // The reason that we need to pass an override bbox to
315 // GetPreEffectsInkOverflowUnion rather than just letting it call into our
316 // GetSVGBBoxForNonSVGFrame method is because we get called by
317 // ComputeEffectsRect when it has been called with
318 // aStoreRectProperties set to false. In this case the pre-effects visual
319 // overflow rect that it has been passed may be different to that stored on
320 // aFrame, resulting in a different bbox.
322 // XXXjwatt The pre-effects ink overflow rect passed to
323 // ComputeEffectsRect won't include continuation overflows, so
324 // for frames with continuation the following filter analysis will likely end
325 // up being carried out with a bbox created as if the frame didn't have
326 // continuations.
328 // XXXjwatt Using aPreEffectsOverflowRect to create the bbox isn't really right
329 // for SVG frames, since for SVG frames the SVG spec defines the bbox to be
330 // something quite different to the pre-effects ink overflow rect. However,
331 // we're essentially calculating an invalidation area here, and using the
332 // pre-effects overflow rect will actually overestimate that area which, while
333 // being a bit wasteful, isn't otherwise a problem.
335 nsRect SVGIntegrationUtils::ComputePostEffectsInkOverflowRect(
336 nsIFrame* aFrame, const nsRect& aPreEffectsOverflowRect) {
337 MOZ_ASSERT(!aFrame->HasAnyStateBits(NS_FRAME_SVG_LAYOUT),
338 "Don't call this on SVG child frames");
340 MOZ_ASSERT(aFrame->StyleEffects()->HasFilters(),
341 "We should only be called if the frame is filtered, since filters "
342 "are the only effect that affects overflow.");
344 nsIFrame* firstFrame =
345 nsLayoutUtils::FirstContinuationOrIBSplitSibling(aFrame);
346 // Note: we do not return here for eHasNoRefs since we must still handle any
347 // CSS filter functions.
348 // TODO: we should really return an empty rect for eHasRefsSomeInvalid since
349 // in that case we disable painting of the element.
350 nsTArray<SVGFilterFrame*> filterFrames;
351 if (SVGObserverUtils::GetAndObserveFilters(firstFrame, &filterFrames) ==
352 SVGObserverUtils::eHasRefsSomeInvalid) {
353 return aPreEffectsOverflowRect;
356 // Create an override bbox - see comment above:
357 nsPoint firstFrameToBoundingBox = GetOffsetToBoundingBox(firstFrame);
358 // overrideBBox is in "user space", in _CSS_ pixels:
359 // XXX Why are we rounding out to pixel boundaries? We don't do that in
360 // GetSVGBBoxForNonSVGFrame, and it doesn't appear to be necessary.
361 gfxRect overrideBBox = nsLayoutUtils::RectToGfxRect(
362 GetPreEffectsInkOverflowUnion(firstFrame, aFrame, aPreEffectsOverflowRect,
363 firstFrameToBoundingBox, true),
364 AppUnitsPerCSSPixel());
365 overrideBBox.RoundOut();
367 Maybe<nsRect> overflowRect = FilterInstance::GetPostFilterBounds(
368 firstFrame, filterFrames, &overrideBBox);
369 if (!overflowRect) {
370 return aPreEffectsOverflowRect;
373 // Return overflowRect relative to aFrame, rather than "user space":
374 return overflowRect.value() -
375 (aFrame->GetOffsetTo(firstFrame) + firstFrameToBoundingBox);
378 nsRect SVGIntegrationUtils::GetRequiredSourceForInvalidArea(
379 nsIFrame* aFrame, const nsRect& aDirtyRect) {
380 nsIFrame* firstFrame =
381 nsLayoutUtils::FirstContinuationOrIBSplitSibling(aFrame);
383 // If we have any filters to observe then we should have started doing that
384 // during reflow/ComputeFrameEffectsRect, so we use GetFiltersIfObserving
385 // here to avoid needless work (or masking bugs by setting up observers at
386 // the wrong time).
387 nsTArray<SVGFilterFrame*> filterFrames;
388 if (!aFrame->StyleEffects()->HasFilters() ||
389 SVGObserverUtils::GetFiltersIfObserving(firstFrame, &filterFrames) ==
390 SVGObserverUtils::eHasRefsSomeInvalid) {
391 return aDirtyRect;
394 // Convert aDirtyRect into "user space" in app units:
395 nsPoint toUserSpace =
396 aFrame->GetOffsetTo(firstFrame) + GetOffsetToBoundingBox(firstFrame);
397 nsRect postEffectsRect = aDirtyRect + toUserSpace;
399 // Return ther result, relative to aFrame, not in user space:
400 return FilterInstance::GetPreFilterNeededArea(firstFrame, filterFrames,
401 postEffectsRect)
402 .GetBounds() -
403 toUserSpace;
406 bool SVGIntegrationUtils::HitTestFrameForEffects(nsIFrame* aFrame,
407 const nsPoint& aPt) {
408 nsIFrame* firstFrame =
409 nsLayoutUtils::FirstContinuationOrIBSplitSibling(aFrame);
410 // Convert aPt to user space:
411 nsPoint toUserSpace;
412 if (aFrame->HasAnyStateBits(NS_FRAME_SVG_LAYOUT)) {
413 // XXXmstange Isn't this wrong for svg:use and innerSVG frames?
414 toUserSpace = aFrame->GetPosition();
415 } else {
416 toUserSpace =
417 aFrame->GetOffsetTo(firstFrame) + GetOffsetToBoundingBox(firstFrame);
419 nsPoint pt = aPt + toUserSpace;
420 gfxPoint userSpacePt = gfxPoint(pt.x, pt.y) / AppUnitsPerCSSPixel();
421 return SVGUtils::HitTestClip(firstFrame, userSpacePt);
424 using PaintFramesParams = SVGIntegrationUtils::PaintFramesParams;
427 * Paint css-positioned-mask onto a given target(aMaskDT).
428 * Return value indicates if mask is complete.
430 static bool PaintMaskSurface(const PaintFramesParams& aParams,
431 DrawTarget* aMaskDT, float aOpacity,
432 const ComputedStyle* aSC,
433 const nsTArray<SVGMaskFrame*>& aMaskFrames,
434 const nsPoint& aOffsetToUserSpace) {
435 MOZ_ASSERT(aMaskFrames.Length() > 0);
436 MOZ_ASSERT(aMaskDT->GetFormat() == SurfaceFormat::A8);
437 MOZ_ASSERT(aOpacity == 1.0 || aMaskFrames.Length() == 1);
439 const nsStyleSVGReset* svgReset = aSC->StyleSVGReset();
440 gfxMatrix cssPxToDevPxMatrix = SVGUtils::GetCSSPxToDevPxMatrix(aParams.frame);
442 nsPresContext* presContext = aParams.frame->PresContext();
443 gfxPoint devPixelOffsetToUserSpace = nsLayoutUtils::PointToGfxPoint(
444 aOffsetToUserSpace, presContext->AppUnitsPerDevPixel());
446 gfxContext maskContext(aMaskDT, /* aPreserveTransform */ true);
448 bool isMaskComplete = true;
450 // Multiple SVG masks interleave with image mask. Paint each layer onto
451 // aMaskDT one at a time.
452 for (int i = aMaskFrames.Length() - 1; i >= 0; i--) {
453 SVGMaskFrame* maskFrame = aMaskFrames[i];
454 CompositionOp compositionOp =
455 (i == int(aMaskFrames.Length() - 1))
456 ? CompositionOp::OP_OVER
457 : nsCSSRendering::GetGFXCompositeMode(
458 svgReset->mMask.mLayers[i].mComposite);
460 // maskFrame != nullptr means we get a SVG mask.
461 // maskFrame == nullptr means we get an image mask.
462 if (maskFrame) {
463 SVGMaskFrame::MaskParams params(
464 maskContext.GetDrawTarget(), aParams.frame, cssPxToDevPxMatrix,
465 aOpacity, svgReset->mMask.mLayers[i].mMaskMode, aParams.imgParams);
466 RefPtr<SourceSurface> svgMask = maskFrame->GetMaskForMaskedFrame(params);
467 if (svgMask) {
468 Matrix tmp = aMaskDT->GetTransform();
469 aMaskDT->SetTransform(Matrix());
470 aMaskDT->MaskSurface(ColorPattern(DeviceColor(0.0, 0.0, 0.0, 1.0)),
471 svgMask, Point(0, 0),
472 DrawOptions(1.0, compositionOp));
473 aMaskDT->SetTransform(tmp);
475 } else if (svgReset->mMask.mLayers[i].mImage.IsResolved()) {
476 gfxContextMatrixAutoSaveRestore matRestore(&maskContext);
478 maskContext.Multiply(gfxMatrix::Translation(-devPixelOffsetToUserSpace));
479 nsCSSRendering::PaintBGParams params =
480 nsCSSRendering::PaintBGParams::ForSingleLayer(
481 *presContext, aParams.dirtyRect, aParams.borderArea,
482 aParams.frame,
483 aParams.builder->GetBackgroundPaintFlags() |
484 nsCSSRendering::PAINTBG_MASK_IMAGE,
485 i, compositionOp, aOpacity);
487 aParams.imgParams.result &= nsCSSRendering::PaintStyleImageLayerWithSC(
488 params, maskContext, aSC, *aParams.frame->StyleBorder());
489 } else {
490 isMaskComplete = false;
494 return isMaskComplete;
497 struct MaskPaintResult {
498 RefPtr<SourceSurface> maskSurface;
499 Matrix maskTransform;
500 bool transparentBlackMask;
501 bool opacityApplied;
503 MaskPaintResult() : transparentBlackMask(false), opacityApplied(false) {}
506 static MaskPaintResult CreateAndPaintMaskSurface(
507 const PaintFramesParams& aParams, float aOpacity, const ComputedStyle* aSC,
508 const nsTArray<SVGMaskFrame*>& aMaskFrames,
509 const nsPoint& aOffsetToUserSpace) {
510 const nsStyleSVGReset* svgReset = aSC->StyleSVGReset();
511 MOZ_ASSERT(aMaskFrames.Length() > 0);
512 MaskPaintResult paintResult;
514 gfxContext& ctx = aParams.ctx;
516 // Optimization for single SVG mask.
517 if (((aMaskFrames.Length() == 1) && aMaskFrames[0])) {
518 gfxMatrix cssPxToDevPxMatrix =
519 SVGUtils::GetCSSPxToDevPxMatrix(aParams.frame);
520 paintResult.opacityApplied = true;
521 SVGMaskFrame::MaskParams params(
522 ctx.GetDrawTarget(), aParams.frame, cssPxToDevPxMatrix, aOpacity,
523 svgReset->mMask.mLayers[0].mMaskMode, aParams.imgParams);
524 paintResult.maskSurface = aMaskFrames[0]->GetMaskForMaskedFrame(params);
525 paintResult.maskTransform = ctx.CurrentMatrix();
526 paintResult.maskTransform.Invert();
527 if (!paintResult.maskSurface) {
528 paintResult.transparentBlackMask = true;
531 return paintResult;
534 const LayoutDeviceRect& maskSurfaceRect =
535 aParams.maskRect.valueOr(LayoutDeviceRect());
536 if (aParams.maskRect.isSome() && maskSurfaceRect.IsEmpty()) {
537 // XXX: Is this ever true?
538 paintResult.transparentBlackMask = true;
539 return paintResult;
542 RefPtr<DrawTarget> maskDT = ctx.GetDrawTarget()->CreateClippedDrawTarget(
543 maskSurfaceRect.ToUnknownRect(), SurfaceFormat::A8);
544 if (!maskDT || !maskDT->IsValid()) {
545 return paintResult;
548 // We can paint mask along with opacity only if
549 // 1. There is only one mask, or
550 // 2. No overlap among masks.
551 // Collision detect in #2 is not that trivial, we only accept #1 here.
552 paintResult.opacityApplied = (aMaskFrames.Length() == 1);
554 // Set context's matrix on maskContext, offset by the maskSurfaceRect's
555 // position. This makes sure that we combine the masks in device space.
556 Matrix maskSurfaceMatrix = ctx.CurrentMatrix();
558 bool isMaskComplete = PaintMaskSurface(
559 aParams, maskDT, paintResult.opacityApplied ? aOpacity : 1.0, aSC,
560 aMaskFrames, aOffsetToUserSpace);
562 if (!isMaskComplete ||
563 (aParams.imgParams.result != ImgDrawResult::SUCCESS &&
564 aParams.imgParams.result != ImgDrawResult::SUCCESS_NOT_COMPLETE &&
565 aParams.imgParams.result != ImgDrawResult::WRONG_SIZE)) {
566 // Now we know the status of mask resource since we used it while painting.
567 // According to the return value of PaintMaskSurface, we know whether mask
568 // resource is resolvable or not.
570 // For a HTML doc:
571 // According to css-masking spec, always create a mask surface when
572 // we have any item in maskFrame even if all of those items are
573 // non-resolvable <mask-sources> or <images>.
574 // Set paintResult.transparentBlackMask as true, the caller should stop
575 // painting masked content as if this mask is a transparent black one.
576 // For a SVG doc:
577 // SVG 1.1 say that if we fail to resolve a mask, we should draw the
578 // object unmasked.
579 // Left paintResult.maskSurface empty, the caller should paint all
580 // masked content as if this mask is an opaque white one(no mask).
581 paintResult.transparentBlackMask =
582 !aParams.frame->HasAnyStateBits(NS_FRAME_SVG_LAYOUT);
584 MOZ_ASSERT(!paintResult.maskSurface);
585 return paintResult;
588 paintResult.maskTransform = maskSurfaceMatrix;
589 if (!paintResult.maskTransform.Invert()) {
590 return paintResult;
593 paintResult.maskSurface = maskDT->Snapshot();
594 return paintResult;
597 static bool ValidateSVGFrame(nsIFrame* aFrame) {
598 NS_ASSERTION(
599 !aFrame->HasAllStateBits(NS_FRAME_SVG_LAYOUT | NS_FRAME_IS_NONDISPLAY),
600 "Should not use SVGIntegrationUtils on this SVG frame");
602 if (aFrame->HasAnyStateBits(NS_FRAME_SVG_LAYOUT)) {
603 #ifdef DEBUG
604 ISVGDisplayableFrame* svgFrame = do_QueryFrame(aFrame);
605 MOZ_ASSERT(svgFrame && aFrame->GetContent()->IsSVGElement(),
606 "A non-SVG frame carries NS_FRAME_SVG_LAYOUT flag?");
607 #endif
609 const nsIContent* content = aFrame->GetContent();
610 if (!static_cast<const SVGElement*>(content)->HasValidDimensions()) {
611 // The SVG spec says not to draw _anything_
612 return false;
616 return true;
619 bool SVGIntegrationUtils::PaintMask(const PaintFramesParams& aParams,
620 bool& aOutIsMaskComplete) {
621 aOutIsMaskComplete = true;
623 SVGUtils::MaskUsage maskUsage =
624 SVGUtils::DetermineMaskUsage(aParams.frame, aParams.handleOpacity);
625 if (!maskUsage.ShouldDoSomething()) {
626 return false;
629 nsIFrame* frame = aParams.frame;
630 if (!ValidateSVGFrame(frame)) {
631 return false;
634 gfxContext& ctx = aParams.ctx;
635 RefPtr<DrawTarget> maskTarget = ctx.GetDrawTarget();
637 if (maskUsage.ShouldGenerateMaskLayer() && maskUsage.HasSVGClip()) {
638 // We will paint both mask of positioned mask and clip-path into
639 // maskTarget.
641 // Create one extra draw target for drawing positioned mask, so that we do
642 // not have to copy the content of maskTarget before painting
643 // clip-path into it.
644 maskTarget = maskTarget->CreateClippedDrawTarget(Rect(), SurfaceFormat::A8);
647 nsIFrame* firstFrame =
648 nsLayoutUtils::FirstContinuationOrIBSplitSibling(frame);
649 nsTArray<SVGMaskFrame*> maskFrames;
650 // XXX check return value?
651 SVGObserverUtils::GetAndObserveMasks(firstFrame, &maskFrames);
653 gfxGroupForBlendAutoSaveRestore autoPop(&ctx);
654 bool shouldPushOpacity = !maskUsage.IsOpaque() && maskFrames.Length() != 1;
655 if (shouldPushOpacity) {
656 autoPop.PushGroupForBlendBack(gfxContentType::COLOR_ALPHA,
657 maskUsage.Opacity());
660 gfxContextMatrixAutoSaveRestore matSR;
662 // Paint clip-path-basic-shape onto ctx
663 gfxContextAutoSaveRestore basicShapeSR;
664 if (maskUsage.ShouldApplyBasicShapeOrPath()) {
665 matSR.SetContext(&ctx);
667 MoveContextOriginToUserSpace(firstFrame, aParams);
669 basicShapeSR.SetContext(&ctx);
670 gfxMatrix mat = SVGUtils::GetCSSPxToDevPxMatrix(frame);
671 if (!maskUsage.ShouldGenerateMaskLayer()) {
672 // Only have basic-shape clip-path effect. Fill clipped region by
673 // opaque white.
674 ctx.SetDeviceColor(DeviceColor::MaskOpaqueWhite());
675 RefPtr<Path> path = CSSClipPathInstance::CreateClipPathForFrame(
676 ctx.GetDrawTarget(), frame, mat);
677 if (path) {
678 ctx.SetPath(path);
679 ctx.Fill();
682 return true;
684 CSSClipPathInstance::ApplyBasicShapeOrPathClip(ctx, frame, mat);
687 // Paint mask into maskTarget.
688 if (maskUsage.ShouldGenerateMaskLayer()) {
689 matSR.Restore();
690 matSR.SetContext(&ctx);
692 EffectOffsets offsets = ComputeEffectOffset(frame, aParams);
693 maskTarget->SetTransform(maskTarget->GetTransform().PreTranslate(
694 ToPoint(offsets.offsetToUserSpaceInDevPx)));
695 aOutIsMaskComplete = PaintMaskSurface(
696 aParams, maskTarget, shouldPushOpacity ? 1.0f : maskUsage.Opacity(),
697 firstFrame->Style(), maskFrames, offsets.offsetToUserSpace);
700 // Paint clip-path onto ctx.
701 if (maskUsage.HasSVGClip()) {
702 matSR.Restore();
703 matSR.SetContext(&ctx);
705 MoveContextOriginToUserSpace(firstFrame, aParams);
706 Matrix clipMaskTransform;
707 gfxMatrix cssPxToDevPxMatrix = SVGUtils::GetCSSPxToDevPxMatrix(frame);
709 SVGClipPathFrame* clipPathFrame;
710 // XXX check return value?
711 SVGObserverUtils::GetAndObserveClipPath(firstFrame, &clipPathFrame);
712 RefPtr<SourceSurface> maskSurface =
713 maskUsage.ShouldGenerateMaskLayer() ? maskTarget->Snapshot() : nullptr;
714 clipPathFrame->PaintClipMask(ctx, frame, cssPxToDevPxMatrix, maskSurface);
717 return true;
720 template <class T>
721 void PaintMaskAndClipPathInternal(const PaintFramesParams& aParams,
722 const T& aPaintChild) {
723 #ifdef DEBUG
724 const nsStyleSVGReset* style = aParams.frame->StyleSVGReset();
725 MOZ_ASSERT(style->HasClipPath() || style->HasMask(),
726 "Should not use this method when no mask or clipPath effect"
727 "on this frame");
728 #endif
730 /* SVG defines the following rendering model:
732 * 1. Render geometry
733 * 2. Apply filter
734 * 3. Apply clipping, masking, group opacity
736 * We handle #3 here and perform a couple of optimizations:
738 * + Use cairo's clipPath when representable natively (single object
739 * clip region).
741 * + Merge opacity and masking if both used together.
743 nsIFrame* frame = aParams.frame;
744 if (!ValidateSVGFrame(frame)) {
745 return;
748 SVGUtils::MaskUsage maskUsage =
749 SVGUtils::DetermineMaskUsage(aParams.frame, aParams.handleOpacity);
751 if (maskUsage.IsTransparent()) {
752 return;
755 gfxContext& context = aParams.ctx;
756 gfxContextMatrixAutoSaveRestore matrixAutoSaveRestore(&context);
758 nsIFrame* firstFrame =
759 nsLayoutUtils::FirstContinuationOrIBSplitSibling(frame);
761 SVGClipPathFrame* clipPathFrame;
762 // XXX check return value?
763 SVGObserverUtils::GetAndObserveClipPath(firstFrame, &clipPathFrame);
765 nsTArray<SVGMaskFrame*> maskFrames;
766 // XXX check return value?
767 SVGObserverUtils::GetAndObserveMasks(firstFrame, &maskFrames);
769 gfxMatrix cssPxToDevPxMatrix = SVGUtils::GetCSSPxToDevPxMatrix(frame);
771 bool shouldPushMask = false;
773 gfxGroupForBlendAutoSaveRestore autoGroupForBlend(&context);
775 /* Check if we need to do additional operations on this child's
776 * rendering, which necessitates rendering into another surface. */
777 if (maskUsage.ShouldGenerateMask()) {
778 gfxContextMatrixAutoSaveRestore matSR;
780 RefPtr<SourceSurface> maskSurface;
781 bool opacityApplied = false;
783 if (maskUsage.ShouldGenerateMaskLayer()) {
784 matSR.SetContext(&context);
786 // For css-mask, we want to generate a mask for each continuation frame,
787 // so we setup context matrix by the position of the current frame,
788 // instead of the first continuation frame.
789 EffectOffsets offsets = MoveContextOriginToUserSpace(frame, aParams);
790 MaskPaintResult paintResult = CreateAndPaintMaskSurface(
791 aParams, maskUsage.Opacity(), firstFrame->Style(), maskFrames,
792 offsets.offsetToUserSpace);
794 if (paintResult.transparentBlackMask) {
795 return;
798 maskSurface = paintResult.maskSurface;
799 if (maskSurface) {
800 shouldPushMask = true;
802 opacityApplied = paintResult.opacityApplied;
806 if (maskUsage.ShouldGenerateClipMaskLayer()) {
807 matSR.Restore();
808 matSR.SetContext(&context);
810 MoveContextOriginToUserSpace(firstFrame, aParams);
811 RefPtr<SourceSurface> clipMaskSurface = clipPathFrame->GetClipMask(
812 context, frame, cssPxToDevPxMatrix, maskSurface);
814 if (clipMaskSurface) {
815 maskSurface = clipMaskSurface;
816 } else {
817 // Either entire surface is clipped out, or gfx buffer allocation
818 // failure in SVGClipPathFrame::GetClipMask.
819 return;
822 shouldPushMask = true;
825 // opacity != 1.0f.
826 if (!maskUsage.ShouldGenerateLayer()) {
827 MOZ_ASSERT(!maskUsage.IsOpaque());
829 matSR.SetContext(&context);
830 MoveContextOriginToUserSpace(firstFrame, aParams);
831 shouldPushMask = true;
834 if (shouldPushMask) {
835 // We want the mask to be untransformed so use the inverse of the
836 // current transform as the maskTransform to compensate.
837 Matrix maskTransform = context.CurrentMatrix();
838 maskTransform.Invert();
840 autoGroupForBlend.PushGroupForBlendBack(
841 gfxContentType::COLOR_ALPHA,
842 opacityApplied ? 1.0f : maskUsage.Opacity(), maskSurface,
843 maskTransform);
847 /* If this frame has only a trivial clipPath, set up cairo's clipping now so
848 * we can just do normal painting and get it clipped appropriately.
850 if (maskUsage.ShouldApplyClipPath() ||
851 maskUsage.ShouldApplyBasicShapeOrPath()) {
852 gfxContextMatrixAutoSaveRestore matSR(&context);
854 MoveContextOriginToUserSpace(firstFrame, aParams);
856 MOZ_ASSERT(!maskUsage.ShouldApplyClipPath() ||
857 !maskUsage.ShouldApplyBasicShapeOrPath());
858 if (maskUsage.ShouldApplyClipPath()) {
859 clipPathFrame->ApplyClipPath(context, frame, cssPxToDevPxMatrix);
860 } else {
861 CSSClipPathInstance::ApplyBasicShapeOrPathClip(context, frame,
862 cssPxToDevPxMatrix);
866 /* Paint the child */
867 context.SetMatrix(matrixAutoSaveRestore.Matrix());
868 aPaintChild();
870 if (StaticPrefs::layers_draw_mask_debug()) {
871 gfxContextAutoSaveRestore saver(&context);
873 context.NewPath();
874 gfxRect drawingRect = nsLayoutUtils::RectToGfxRect(
875 aParams.borderArea, frame->PresContext()->AppUnitsPerDevPixel());
876 context.SnappedRectangle(drawingRect);
877 sRGBColor overlayColor(0.0f, 0.0f, 0.0f, 0.8f);
878 if (maskUsage.ShouldGenerateMaskLayer()) {
879 overlayColor.r = 1.0f; // red represents css positioned mask.
881 if (maskUsage.HasSVGClip()) {
882 overlayColor.g = 1.0f; // green represents clip-path:<clip-source>.
884 if (maskUsage.ShouldApplyBasicShapeOrPath()) {
885 overlayColor.b = 1.0f; // blue represents
886 // clip-path:<basic-shape>||<geometry-box>.
889 context.SetColor(overlayColor);
890 context.Fill();
893 if (maskUsage.ShouldApplyClipPath() ||
894 maskUsage.ShouldApplyBasicShapeOrPath()) {
895 context.PopClip();
899 void SVGIntegrationUtils::PaintMaskAndClipPath(
900 const PaintFramesParams& aParams,
901 const std::function<void()>& aPaintChild) {
902 PaintMaskAndClipPathInternal(aParams, aPaintChild);
905 void SVGIntegrationUtils::PaintFilter(const PaintFramesParams& aParams,
906 Span<const StyleFilter> aFilters,
907 const SVGFilterPaintCallback& aCallback) {
908 MOZ_ASSERT(!aParams.builder->IsForGenerateGlyphMask(),
909 "Filter effect is discarded while generating glyph mask.");
910 MOZ_ASSERT(!aFilters.IsEmpty(),
911 "Should not use this method when no filter effect on this frame");
913 nsIFrame* frame = aParams.frame;
914 if (!ValidateSVGFrame(frame)) {
915 return;
918 float opacity = SVGUtils::ComputeOpacity(frame, aParams.handleOpacity);
919 if (opacity == 0.0f) {
920 return;
923 // Properties are added lazily and may have been removed by a restyle, so make
924 // sure all applicable ones are set again.
925 nsIFrame* firstFrame =
926 nsLayoutUtils::FirstContinuationOrIBSplitSibling(frame);
927 // Note: we do not return here for eHasNoRefs since we must still handle any
928 // CSS filter functions.
929 // XXX: Do we need to check for eHasRefsSomeInvalid here given that
930 // nsDisplayFilter::BuildLayer returns nullptr for eHasRefsSomeInvalid?
931 // Or can we just assert !eHasRefsSomeInvalid?
932 nsTArray<SVGFilterFrame*> filterFrames;
933 if (SVGObserverUtils::GetAndObserveFilters(firstFrame, &filterFrames) ==
934 SVGObserverUtils::eHasRefsSomeInvalid) {
935 aCallback(aParams.ctx, aParams.imgParams, nullptr, nullptr);
936 return;
939 gfxContext& context = aParams.ctx;
941 gfxContextAutoSaveRestore autoSR(&context);
942 EffectOffsets offsets = MoveContextOriginToUserSpace(firstFrame, aParams);
944 /* Paint the child and apply filters */
945 nsRegion dirtyRegion = aParams.dirtyRect - offsets.offsetToBoundingBox;
947 FilterInstance::PaintFilteredFrame(frame, aFilters, filterFrames, &context,
948 aCallback, &dirtyRegion, aParams.imgParams,
949 opacity);
952 bool SVGIntegrationUtils::CreateWebRenderCSSFilters(
953 Span<const StyleFilter> aFilters, nsIFrame* aFrame,
954 WrFiltersHolder& aWrFilters) {
955 // All CSS filters are supported by WebRender. SVG filters are not fully
956 // supported, those use NS_STYLE_FILTER_URL and are handled separately.
958 // If there are too many filters to render, then just pretend that we
959 // succeeded, and don't render any of them.
960 if (aFilters.Length() >
961 StaticPrefs::gfx_webrender_max_filter_ops_per_chain()) {
962 return true;
964 aWrFilters.filters.SetCapacity(aFilters.Length());
965 auto& wrFilters = aWrFilters.filters;
966 for (const StyleFilter& filter : aFilters) {
967 switch (filter.tag) {
968 case StyleFilter::Tag::Brightness:
969 wrFilters.AppendElement(
970 wr::FilterOp::Brightness(filter.AsBrightness()));
971 break;
972 case StyleFilter::Tag::Contrast:
973 wrFilters.AppendElement(wr::FilterOp::Contrast(filter.AsContrast()));
974 break;
975 case StyleFilter::Tag::Grayscale:
976 wrFilters.AppendElement(wr::FilterOp::Grayscale(filter.AsGrayscale()));
977 break;
978 case StyleFilter::Tag::Invert:
979 wrFilters.AppendElement(wr::FilterOp::Invert(filter.AsInvert()));
980 break;
981 case StyleFilter::Tag::Opacity: {
982 float opacity = filter.AsOpacity();
983 wrFilters.AppendElement(wr::FilterOp::Opacity(
984 wr::PropertyBinding<float>::Value(opacity), opacity));
985 break;
987 case StyleFilter::Tag::Saturate:
988 wrFilters.AppendElement(wr::FilterOp::Saturate(filter.AsSaturate()));
989 break;
990 case StyleFilter::Tag::Sepia:
991 wrFilters.AppendElement(wr::FilterOp::Sepia(filter.AsSepia()));
992 break;
993 case StyleFilter::Tag::HueRotate: {
994 wrFilters.AppendElement(
995 wr::FilterOp::HueRotate(filter.AsHueRotate().ToDegrees()));
996 break;
998 case StyleFilter::Tag::Blur: {
999 // TODO(emilio): we should go directly from css pixels -> device pixels.
1000 float appUnitsPerDevPixel =
1001 aFrame->PresContext()->AppUnitsPerDevPixel();
1002 float radius = NSAppUnitsToFloatPixels(filter.AsBlur().ToAppUnits(),
1003 appUnitsPerDevPixel);
1004 wrFilters.AppendElement(wr::FilterOp::Blur(radius, radius));
1005 break;
1007 case StyleFilter::Tag::DropShadow: {
1008 float appUnitsPerDevPixel =
1009 aFrame->PresContext()->AppUnitsPerDevPixel();
1010 const StyleSimpleShadow& shadow = filter.AsDropShadow();
1011 nscolor color = shadow.color.CalcColor(aFrame);
1013 wr::Shadow wrShadow;
1014 wrShadow.offset = {
1015 NSAppUnitsToFloatPixels(shadow.horizontal.ToAppUnits(),
1016 appUnitsPerDevPixel),
1017 NSAppUnitsToFloatPixels(shadow.vertical.ToAppUnits(),
1018 appUnitsPerDevPixel)};
1019 wrShadow.blur_radius = NSAppUnitsToFloatPixels(shadow.blur.ToAppUnits(),
1020 appUnitsPerDevPixel);
1021 wrShadow.color = {NS_GET_R(color) / 255.0f, NS_GET_G(color) / 255.0f,
1022 NS_GET_B(color) / 255.0f, NS_GET_A(color) / 255.0f};
1023 wrFilters.AppendElement(wr::FilterOp::DropShadow(wrShadow));
1024 break;
1026 default:
1027 return false;
1031 return true;
1034 bool SVGIntegrationUtils::BuildWebRenderFilters(
1035 nsIFrame* aFilteredFrame, Span<const StyleFilter> aFilters,
1036 StyleFilterType aStyleFilterType, WrFiltersHolder& aWrFilters,
1037 bool& aInitialized) {
1038 return FilterInstance::BuildWebRenderFilters(
1039 aFilteredFrame, aFilters, aStyleFilterType, aWrFilters, aInitialized);
1042 bool SVGIntegrationUtils::CanCreateWebRenderFiltersForFrame(nsIFrame* aFrame) {
1043 WrFiltersHolder wrFilters;
1044 auto filterChain = aFrame->StyleEffects()->mFilters.AsSpan();
1045 bool initialized = true;
1046 return CreateWebRenderCSSFilters(filterChain, aFrame, wrFilters) ||
1047 BuildWebRenderFilters(aFrame, filterChain, StyleFilterType::Filter,
1048 wrFilters, initialized);
1051 bool SVGIntegrationUtils::UsesSVGEffectsNotSupportedInCompositor(
1052 nsIFrame* aFrame) {
1053 // WebRender supports masks / clip-paths and some filters in the compositor.
1054 if (aFrame->StyleEffects()->HasFilters()) {
1055 return !SVGIntegrationUtils::CanCreateWebRenderFiltersForFrame(aFrame);
1057 return false;
1060 class PaintFrameCallback : public gfxDrawingCallback {
1061 public:
1062 PaintFrameCallback(nsIFrame* aFrame, const nsSize aPaintServerSize,
1063 const IntSize aRenderSize, uint32_t aFlags)
1064 : mFrame(aFrame),
1065 mPaintServerSize(aPaintServerSize),
1066 mRenderSize(aRenderSize),
1067 mFlags(aFlags) {}
1068 virtual bool operator()(gfxContext* aContext, const gfxRect& aFillRect,
1069 const SamplingFilter aSamplingFilter,
1070 const gfxMatrix& aTransform) override;
1072 private:
1073 nsIFrame* mFrame;
1074 nsSize mPaintServerSize;
1075 IntSize mRenderSize;
1076 uint32_t mFlags;
1079 bool PaintFrameCallback::operator()(gfxContext* aContext,
1080 const gfxRect& aFillRect,
1081 const SamplingFilter aSamplingFilter,
1082 const gfxMatrix& aTransform) {
1083 if (mFrame->HasAnyStateBits(NS_FRAME_DRAWING_AS_PAINTSERVER)) {
1084 return false;
1087 AutoSetRestorePaintServerState paintServer(mFrame);
1089 aContext->Save();
1091 // Clip to aFillRect so that we don't paint outside.
1092 aContext->Clip(aFillRect);
1094 gfxMatrix invmatrix = aTransform;
1095 if (!invmatrix.Invert()) {
1096 return false;
1098 aContext->Multiply(invmatrix);
1100 // nsLayoutUtils::PaintFrame will anchor its painting at mFrame. But we want
1101 // to have it anchored at the top left corner of the bounding box of all of
1102 // mFrame's continuations. So we add a translation transform.
1103 int32_t appUnitsPerDevPixel = mFrame->PresContext()->AppUnitsPerDevPixel();
1104 nsPoint offset = SVGIntegrationUtils::GetOffsetToBoundingBox(mFrame);
1105 gfxPoint devPxOffset = gfxPoint(offset.x, offset.y) / appUnitsPerDevPixel;
1106 aContext->Multiply(gfxMatrix::Translation(devPxOffset));
1108 gfxSize paintServerSize =
1109 gfxSize(mPaintServerSize.width, mPaintServerSize.height) /
1110 mFrame->PresContext()->AppUnitsPerDevPixel();
1112 // nsLayoutUtils::PaintFrame wants to render with paintServerSize, but we
1113 // want it to render with mRenderSize, so we need to set up a scale transform.
1114 gfxFloat scaleX = mRenderSize.width / paintServerSize.width;
1115 gfxFloat scaleY = mRenderSize.height / paintServerSize.height;
1116 aContext->Multiply(gfxMatrix::Scaling(scaleX, scaleY));
1118 // Draw.
1119 nsRect dirty(-offset.x, -offset.y, mPaintServerSize.width,
1120 mPaintServerSize.height);
1122 using PaintFrameFlags = nsLayoutUtils::PaintFrameFlags;
1123 PaintFrameFlags flags = PaintFrameFlags::InTransform;
1124 if (mFlags & SVGIntegrationUtils::FLAG_SYNC_DECODE_IMAGES) {
1125 flags |= PaintFrameFlags::SyncDecodeImages;
1127 nsLayoutUtils::PaintFrame(aContext, mFrame, dirty, NS_RGBA(0, 0, 0, 0),
1128 nsDisplayListBuilderMode::Painting, flags);
1130 nsIFrame* currentFrame = mFrame;
1131 while ((currentFrame = currentFrame->GetNextContinuation()) != nullptr) {
1132 offset = currentFrame->GetOffsetToCrossDoc(mFrame);
1133 devPxOffset = gfxPoint(offset.x, offset.y) / appUnitsPerDevPixel;
1135 aContext->Save();
1136 aContext->Multiply(gfxMatrix::Scaling(1 / scaleX, 1 / scaleY));
1137 aContext->Multiply(gfxMatrix::Translation(devPxOffset));
1138 aContext->Multiply(gfxMatrix::Scaling(scaleX, scaleY));
1140 nsLayoutUtils::PaintFrame(aContext, currentFrame, dirty - offset,
1141 NS_RGBA(0, 0, 0, 0),
1142 nsDisplayListBuilderMode::Painting, flags);
1144 aContext->Restore();
1147 aContext->Restore();
1149 return true;
1152 /* static */
1153 already_AddRefed<gfxDrawable> SVGIntegrationUtils::DrawableFromPaintServer(
1154 nsIFrame* aFrame, nsIFrame* aTarget, const nsSize& aPaintServerSize,
1155 const IntSize& aRenderSize, const DrawTarget* aDrawTarget,
1156 const gfxMatrix& aContextMatrix, uint32_t aFlags) {
1157 // aPaintServerSize is the size that would be filled when using
1158 // background-repeat:no-repeat and background-size:auto. For normal background
1159 // images, this would be the intrinsic size of the image; for gradients and
1160 // patterns this would be the whole target frame fill area.
1161 // aRenderSize is what we will be actually filling after accounting for
1162 // background-size.
1163 if (SVGPaintServerFrame* server = do_QueryFrame(aFrame)) {
1164 // aFrame is either a pattern or a gradient. These fill the whole target
1165 // frame by default, so aPaintServerSize is the whole target background fill
1166 // area.
1167 gfxRect overrideBounds(0, 0, aPaintServerSize.width,
1168 aPaintServerSize.height);
1169 overrideBounds.Scale(1.0 / aFrame->PresContext()->AppUnitsPerDevPixel());
1170 uint32_t imgFlags = imgIContainer::FLAG_ASYNC_NOTIFY;
1171 if (aFlags & SVGIntegrationUtils::FLAG_SYNC_DECODE_IMAGES) {
1172 imgFlags |= imgIContainer::FLAG_SYNC_DECODE;
1174 imgDrawingParams imgParams(imgFlags);
1175 RefPtr<gfxPattern> pattern = server->GetPaintServerPattern(
1176 aTarget, aDrawTarget, aContextMatrix, &nsStyleSVG::mFill, 1.0,
1177 imgParams, &overrideBounds);
1179 if (!pattern) {
1180 return nullptr;
1183 // pattern is now set up to fill aPaintServerSize. But we want it to
1184 // fill aRenderSize, so we need to add a scaling transform.
1185 // We couldn't just have set overrideBounds to aRenderSize - it would have
1186 // worked for gradients, but for patterns it would result in a different
1187 // pattern size.
1188 gfxFloat scaleX = overrideBounds.Width() / aRenderSize.width;
1189 gfxFloat scaleY = overrideBounds.Height() / aRenderSize.height;
1190 gfxMatrix scaleMatrix = gfxMatrix::Scaling(scaleX, scaleY);
1191 pattern->SetMatrix(scaleMatrix * pattern->GetMatrix());
1192 return do_AddRef(new gfxPatternDrawable(pattern, aRenderSize));
1195 if (aFrame->IsSVGFrame() &&
1196 !static_cast<ISVGDisplayableFrame*>(do_QueryFrame(aFrame))) {
1197 MOZ_ASSERT_UNREACHABLE(
1198 "We should prevent painting of unpaintable SVG "
1199 "before we get here");
1200 return nullptr;
1203 // We don't want to paint into a surface as long as we don't need to, so we
1204 // set up a drawing callback.
1205 RefPtr<gfxDrawingCallback> cb =
1206 new PaintFrameCallback(aFrame, aPaintServerSize, aRenderSize, aFlags);
1207 return do_AddRef(new gfxCallbackDrawable(cb, aRenderSize));
1210 } // namespace mozilla