1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5
6 // Main header first:
7 #include "nsSVGIntegrationUtils.h"
8
9 // Keep others in (case-insensitive) order:
10 #include "gfxDrawable.h"
11 #include "nsCSSAnonBoxes.h"
12 #include "nsCSSClipPathInstance.h"
13 #include "nsDisplayList.h"
14 #include "nsFilterInstance.h"
15 #include "nsLayoutUtils.h"
16 #include "nsRenderingContext.h"
17 #include "nsSVGClipPathFrame.h"
18 #include "nsSVGEffects.h"
19 #include "nsSVGElement.h"
20 #include "nsSVGFilterPaintCallback.h"
21 #include "nsSVGMaskFrame.h"
22 #include "nsSVGPaintServerFrame.h"
23 #include "nsSVGUtils.h"
24 #include "FrameLayerBuilder.h"
25 #include "BasicLayers.h"
26 #include "mozilla/gfx/Point.h"
27 #include "nsCSSRendering.h"
28 #include "mozilla/Unused.h"
29
30 using namespace mozilla;
31 using namespace mozilla::layers;
32 using namespace mozilla::gfx;
33 using namespace mozilla::image;
34
35 // ----------------------------------------------------------------------
36
37 /**
38 * This class is used to get the pre-effects visual overflow rect of a frame,
39 * or, in the case of a frame with continuations, to collect the union of the
40 * pre-effects visual 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.
43 */
44 class PreEffectsVisualOverflowCollector : public nsLayoutUtils::BoxCallback
45 {
46 public:
47 /**
48 * If the pre-effects visual overflow rect of the frame being examined
49 * happens to be known, it can be passed in as aCurrentFrame and its
50 * pre-effects visual overflow rect can be passed in as
51 * aCurrentFrameOverflowArea. This is just an optimization to save a
52 * frame property lookup - these arguments are optional.
53 */
PreEffectsVisualOverflowCollector(nsIFrame * aFirstContinuation,nsIFrame * aCurrentFrame,const nsRect & aCurrentFrameOverflowArea)54 PreEffectsVisualOverflowCollector(nsIFrame* aFirstContinuation,
55 nsIFrame* aCurrentFrame,
56 const nsRect& aCurrentFrameOverflowArea)
57 : mFirstContinuation(aFirstContinuation)
58 , mCurrentFrame(aCurrentFrame)
59 , mCurrentFrameOverflowArea(aCurrentFrameOverflowArea)
60 {
61 NS_ASSERTION(!mFirstContinuation->GetPrevContinuation(),
62 "We want the first continuation here");
63 }
64
AddBox(nsIFrame * aFrame)65 virtual void AddBox(nsIFrame* aFrame) override {
66 nsRect overflow = (aFrame == mCurrentFrame) ?
67 mCurrentFrameOverflowArea : GetPreEffectsVisualOverflowRect(aFrame);
68 mResult.UnionRect(mResult, overflow + aFrame->GetOffsetTo(mFirstContinuation));
69 }
70
GetResult() const71 nsRect GetResult() const {
72 return mResult;
73 }
74
75 private:
76
GetPreEffectsVisualOverflowRect(nsIFrame * aFrame)77 static nsRect GetPreEffectsVisualOverflowRect(nsIFrame* aFrame) {
78 nsRect* r = aFrame->Properties().Get(nsIFrame::PreEffectsBBoxProperty());
79 if (r) {
80 return *r;
81 }
82 // Despite the fact that we're invoked for frames with SVG effects applied,
83 // we can actually get here. All continuations and IB split siblings of a
84 // frame with SVG effects applied will have the PreEffectsBBoxProperty
85 // property set on them. Therefore, the frames that are passed to us will
86 // always have that property set...well, with one exception. If the frames
87 // for an element with SVG effects applied have been subject to an "IB
88 // split", then the block frame(s) that caused the split will have been
89 // wrapped in anonymous, inline-block, nsBlockFrames of pseudo-type
90 // nsCSSAnonBoxes::mozAnonymousBlock. These "IB split sibling" anonymous
91 // blocks will have the PreEffectsBBoxProperty property set on them, but
92 // they will never be passed to us. Instead, we'll be passed the block
93 // children that they wrap, which don't have the PreEffectsBBoxProperty
94 // property set on them. This is actually okay. What we care about is
95 // collecting the _pre_ effects visual overflow rects of the frames to
96 // which the SVG effects have been applied. Since the IB split results in
97 // any overflow rect adjustments for transforms, effects, etc. taking
98 // place on the anonymous block wrappers, the wrapped children are left
99 // with their overflow rects unaffected. In other words, calling
100 // GetVisualOverflowRect() on the children will return their pre-effects
101 // visual overflow rects, just as we need.
102 //
103 // A couple of tests that demonstrate the IB split and cause us to get here
104 // are:
105 //
106 // * reftests/svg/svg-integration/clipPath-html-06.xhtml
107 // * reftests/svg/svg-integration/clipPath-html-06-extref.xhtml
108 //
109 // If we ever got passed a frame with the PreTransformOverflowAreasProperty
110 // property set, that would be bad, since then our GetVisualOverflowRect()
111 // call would give us the post-effects, and post-transform, overflow rect.
112 //
113 NS_ASSERTION(aFrame->GetParent()->StyleContext()->GetPseudo() ==
114 nsCSSAnonBoxes::mozAnonymousBlock,
115 "How did we getting here, then?");
116 NS_ASSERTION(!aFrame->Properties().Get(
117 aFrame->PreTransformOverflowAreasProperty()),
118 "GetVisualOverflowRect() won't return the pre-effects rect!");
119 return aFrame->GetVisualOverflowRect();
120 }
121
122 nsIFrame* mFirstContinuation;
123 nsIFrame* mCurrentFrame;
124 const nsRect& mCurrentFrameOverflowArea;
125 nsRect mResult;
126 };
127
128 /**
129 * Gets the union of the pre-effects visual overflow rects of all of a frame's
130 * continuations, in "user space".
131 */
132 static nsRect
GetPreEffectsVisualOverflowUnion(nsIFrame * aFirstContinuation,nsIFrame * aCurrentFrame,const nsRect & aCurrentFramePreEffectsOverflow,const nsPoint & aFirstContinuationToUserSpace)133 GetPreEffectsVisualOverflowUnion(nsIFrame* aFirstContinuation,
134 nsIFrame* aCurrentFrame,
135 const nsRect& aCurrentFramePreEffectsOverflow,
136 const nsPoint& aFirstContinuationToUserSpace)
137 {
138 NS_ASSERTION(!aFirstContinuation->GetPrevContinuation(),
139 "Need first continuation here");
140 PreEffectsVisualOverflowCollector collector(aFirstContinuation,
141 aCurrentFrame,
142 aCurrentFramePreEffectsOverflow);
143 // Compute union of all overflow areas relative to aFirstContinuation:
144 nsLayoutUtils::GetAllInFlowBoxes(aFirstContinuation, &collector);
145 // Return the result in user space:
146 return collector.GetResult() + aFirstContinuationToUserSpace;
147 }
148
149
150 bool
UsingEffectsForFrame(const nsIFrame * aFrame)151 nsSVGIntegrationUtils::UsingEffectsForFrame(const nsIFrame* aFrame)
152 {
153 // Even when SVG display lists are disabled, returning true for SVG frames
154 // does not adversely affect any of our callers. Therefore we don't bother
155 // checking the SDL prefs here, since we don't know if we're being called for
156 // painting or hit-testing anyway.
157 const nsStyleSVGReset *style = aFrame->StyleSVGReset();
158 return aFrame->StyleEffects()->HasFilters() ||
159 style->HasClipPath() ||
160 style->mMask.HasLayerWithImage();
161 }
162
163 bool
UsingMaskOrClipPathForFrame(const nsIFrame * aFrame)164 nsSVGIntegrationUtils::UsingMaskOrClipPathForFrame(const nsIFrame* aFrame)
165 {
166 const nsStyleSVGReset *style = aFrame->StyleSVGReset();
167 return style->HasClipPath() ||
168 style->mMask.HasLayerWithImage();
169 }
170
171 nsPoint
GetOffsetToBoundingBox(nsIFrame * aFrame)172 nsSVGIntegrationUtils::GetOffsetToBoundingBox(nsIFrame* aFrame)
173 {
174 if ((aFrame->GetStateBits() & NS_FRAME_SVG_LAYOUT)) {
175 // Do NOT call GetAllInFlowRectsUnion for SVG - it will get the
176 // covered region relative to the nsSVGOuterSVGFrame, which is absolutely
177 // not what we want. SVG frames are always in user space, so they have
178 // no offset adjustment to make.
179 return nsPoint();
180 }
181
182 // The GetAllInFlowRectsUnion() call gets the union of the frame border-box
183 // rects over all continuations, relative to the origin (top-left of the
184 // border box) of its second argument (here, aFrame, the first continuation).
185 return -nsLayoutUtils::GetAllInFlowRectsUnion(aFrame, aFrame).TopLeft();
186 }
187
188 /* static */ nsSize
GetContinuationUnionSize(nsIFrame * aNonSVGFrame)189 nsSVGIntegrationUtils::GetContinuationUnionSize(nsIFrame* aNonSVGFrame)
190 {
191 NS_ASSERTION(!aNonSVGFrame->IsFrameOfType(nsIFrame::eSVG),
192 "SVG frames should not get here");
193 nsIFrame* firstFrame =
194 nsLayoutUtils::FirstContinuationOrIBSplitSibling(aNonSVGFrame);
195 return nsLayoutUtils::GetAllInFlowRectsUnion(firstFrame, firstFrame).Size();
196 }
197
198 /* static */ gfx::Size
GetSVGCoordContextForNonSVGFrame(nsIFrame * aNonSVGFrame)199 nsSVGIntegrationUtils::GetSVGCoordContextForNonSVGFrame(nsIFrame* aNonSVGFrame)
200 {
201 NS_ASSERTION(!aNonSVGFrame->IsFrameOfType(nsIFrame::eSVG),
202 "SVG frames should not get here");
203 nsIFrame* firstFrame =
204 nsLayoutUtils::FirstContinuationOrIBSplitSibling(aNonSVGFrame);
205 nsRect r = nsLayoutUtils::GetAllInFlowRectsUnion(firstFrame, firstFrame);
206 nsPresContext* presContext = firstFrame->PresContext();
207 return gfx::Size(presContext->AppUnitsToFloatCSSPixels(r.width),
208 presContext->AppUnitsToFloatCSSPixels(r.height));
209 }
210
211 gfxRect
GetSVGBBoxForNonSVGFrame(nsIFrame * aNonSVGFrame)212 nsSVGIntegrationUtils::GetSVGBBoxForNonSVGFrame(nsIFrame* aNonSVGFrame)
213 {
214 NS_ASSERTION(!aNonSVGFrame->IsFrameOfType(nsIFrame::eSVG),
215 "SVG frames should not get here");
216 nsIFrame* firstFrame =
217 nsLayoutUtils::FirstContinuationOrIBSplitSibling(aNonSVGFrame);
218 // 'r' is in "user space":
219 nsRect r = GetPreEffectsVisualOverflowUnion(firstFrame, nullptr, nsRect(),
220 GetOffsetToBoundingBox(firstFrame));
221 return nsLayoutUtils::RectToGfxRect(r,
222 aNonSVGFrame->PresContext()->AppUnitsPerCSSPixel());
223 }
224
225 // XXX Since we're called during reflow, this method is broken for frames with
226 // continuations. When we're called for a frame with continuations, we're
227 // called for each continuation in turn as it's reflowed. However, it isn't
228 // until the last continuation is reflowed that this method's
229 // GetOffsetToBoundingBox() and GetPreEffectsVisualOverflowUnion() calls will
230 // obtain valid border boxes for all the continuations. As a result, we'll
231 // end up returning bogus post-filter visual overflow rects for all the prior
232 // continuations. Unfortunately, by the time the last continuation is
233 // reflowed, it's too late to go back and set and propagate the overflow
234 // rects on the previous continuations.
235 //
236 // The reason that we need to pass an override bbox to
237 // GetPreEffectsVisualOverflowUnion rather than just letting it call into our
238 // GetSVGBBoxForNonSVGFrame method is because we get called by
239 // ComputeEffectsRect when it has been called with
240 // aStoreRectProperties set to false. In this case the pre-effects visual
241 // overflow rect that it has been passed may be different to that stored on
242 // aFrame, resulting in a different bbox.
243 //
244 // XXXjwatt The pre-effects visual overflow rect passed to
245 // ComputeEffectsRect won't include continuation overflows, so
246 // for frames with continuation the following filter analysis will likely end
247 // up being carried out with a bbox created as if the frame didn't have
248 // continuations.
249 //
250 // XXXjwatt Using aPreEffectsOverflowRect to create the bbox isn't really right
251 // for SVG frames, since for SVG frames the SVG spec defines the bbox to be
252 // something quite different to the pre-effects visual overflow rect. However,
253 // we're essentially calculating an invalidation area here, and using the
254 // pre-effects overflow rect will actually overestimate that area which, while
255 // being a bit wasteful, isn't otherwise a problem.
256 //
257 nsRect
258 nsSVGIntegrationUtils::
ComputePostEffectsVisualOverflowRect(nsIFrame * aFrame,const nsRect & aPreEffectsOverflowRect)259 ComputePostEffectsVisualOverflowRect(nsIFrame* aFrame,
260 const nsRect& aPreEffectsOverflowRect)
261 {
262 NS_ASSERTION(!(aFrame->GetStateBits() & NS_FRAME_SVG_LAYOUT),
263 "Don't call this on SVG child frames");
264
265 nsIFrame* firstFrame =
266 nsLayoutUtils::FirstContinuationOrIBSplitSibling(aFrame);
267 nsSVGEffects::EffectProperties effectProperties =
268 nsSVGEffects::GetEffectProperties(firstFrame);
269 if (!effectProperties.HasValidFilter()) {
270 return aPreEffectsOverflowRect;
271 }
272
273 // Create an override bbox - see comment above:
274 nsPoint firstFrameToBoundingBox = GetOffsetToBoundingBox(firstFrame);
275 // overrideBBox is in "user space", in _CSS_ pixels:
276 // XXX Why are we rounding out to pixel boundaries? We don't do that in
277 // GetSVGBBoxForNonSVGFrame, and it doesn't appear to be necessary.
278 gfxRect overrideBBox =
279 nsLayoutUtils::RectToGfxRect(
280 GetPreEffectsVisualOverflowUnion(firstFrame, aFrame,
281 aPreEffectsOverflowRect,
282 firstFrameToBoundingBox),
283 aFrame->PresContext()->AppUnitsPerCSSPixel());
284 overrideBBox.RoundOut();
285
286 nsRect overflowRect =
287 nsFilterInstance::GetPostFilterBounds(firstFrame, &overrideBBox);
288
289 // Return overflowRect relative to aFrame, rather than "user space":
290 return overflowRect - (aFrame->GetOffsetTo(firstFrame) + firstFrameToBoundingBox);
291 }
292
293 nsIntRegion
AdjustInvalidAreaForSVGEffects(nsIFrame * aFrame,const nsPoint & aToReferenceFrame,const nsIntRegion & aInvalidRegion)294 nsSVGIntegrationUtils::AdjustInvalidAreaForSVGEffects(nsIFrame* aFrame,
295 const nsPoint& aToReferenceFrame,
296 const nsIntRegion& aInvalidRegion)
297 {
298 if (aInvalidRegion.IsEmpty()) {
299 return nsIntRect();
300 }
301
302 // Don't bother calling GetEffectProperties; the filter property should
303 // already have been set up during reflow/ComputeFrameEffectsRect
304 nsIFrame* firstFrame =
305 nsLayoutUtils::FirstContinuationOrIBSplitSibling(aFrame);
306 nsSVGFilterProperty *prop = nsSVGEffects::GetFilterProperty(firstFrame);
307 if (!prop || !prop->IsInObserverLists()) {
308 return aInvalidRegion;
309 }
310
311 int32_t appUnitsPerDevPixel = aFrame->PresContext()->AppUnitsPerDevPixel();
312
313 if (!prop || !prop->ReferencesValidResources()) {
314 // The frame is either not there or not currently available,
315 // perhaps because we're in the middle of tearing stuff down.
316 // Be conservative, return our visual overflow rect relative
317 // to the reference frame.
318 nsRect overflow = aFrame->GetVisualOverflowRect() + aToReferenceFrame;
319 return overflow.ToOutsidePixels(appUnitsPerDevPixel);
320 }
321
322 // Convert aInvalidRegion into bounding box frame space in app units:
323 nsPoint toBoundingBox =
324 aFrame->GetOffsetTo(firstFrame) + GetOffsetToBoundingBox(firstFrame);
325 // The initial rect was relative to the reference frame, so we need to
326 // remove that offset to get a rect relative to the current frame.
327 toBoundingBox -= aToReferenceFrame;
328 nsRegion preEffectsRegion = aInvalidRegion.ToAppUnits(appUnitsPerDevPixel).MovedBy(toBoundingBox);
329
330 // Adjust the dirty area for effects, and shift it back to being relative to
331 // the reference frame.
332 nsRegion result = nsFilterInstance::GetPostFilterDirtyArea(firstFrame,
333 preEffectsRegion).MovedBy(-toBoundingBox);
334 // Return the result, in pixels relative to the reference frame.
335 return result.ToOutsidePixels(appUnitsPerDevPixel);
336 }
337
338 nsRect
GetRequiredSourceForInvalidArea(nsIFrame * aFrame,const nsRect & aDirtyRect)339 nsSVGIntegrationUtils::GetRequiredSourceForInvalidArea(nsIFrame* aFrame,
340 const nsRect& aDirtyRect)
341 {
342 // Don't bother calling GetEffectProperties; the filter property should
343 // already have been set up during reflow/ComputeFrameEffectsRect
344 nsIFrame* firstFrame =
345 nsLayoutUtils::FirstContinuationOrIBSplitSibling(aFrame);
346 nsSVGFilterProperty *prop = nsSVGEffects::GetFilterProperty(firstFrame);
347 if (!prop || !prop->ReferencesValidResources()) {
348 return aDirtyRect;
349 }
350
351 // Convert aDirtyRect into "user space" in app units:
352 nsPoint toUserSpace =
353 aFrame->GetOffsetTo(firstFrame) + GetOffsetToBoundingBox(firstFrame);
354 nsRect postEffectsRect = aDirtyRect + toUserSpace;
355
356 // Return ther result, relative to aFrame, not in user space:
357 return nsFilterInstance::GetPreFilterNeededArea(firstFrame, postEffectsRect).GetBounds()
358 - toUserSpace;
359 }
360
361 bool
HitTestFrameForEffects(nsIFrame * aFrame,const nsPoint & aPt)362 nsSVGIntegrationUtils::HitTestFrameForEffects(nsIFrame* aFrame, const nsPoint& aPt)
363 {
364 nsIFrame* firstFrame =
365 nsLayoutUtils::FirstContinuationOrIBSplitSibling(aFrame);
366 // Convert aPt to user space:
367 nsPoint toUserSpace;
368 if (aFrame->GetStateBits() & NS_FRAME_SVG_LAYOUT) {
369 // XXXmstange Isn't this wrong for svg:use and innerSVG frames?
370 toUserSpace = aFrame->GetPosition();
371 } else {
372 toUserSpace =
373 aFrame->GetOffsetTo(firstFrame) + GetOffsetToBoundingBox(firstFrame);
374 }
375 nsPoint pt = aPt + toUserSpace;
376 gfxPoint userSpacePt =
377 gfxPoint(pt.x, pt.y) / aFrame->PresContext()->AppUnitsPerCSSPixel();
378 return nsSVGUtils::HitTestClip(firstFrame, userSpacePt);
379 }
380
381 class RegularFramePaintCallback : public nsSVGFilterPaintCallback
382 {
383 public:
RegularFramePaintCallback(nsDisplayListBuilder * aBuilder,LayerManager * aManager,const nsPoint & aOffset)384 RegularFramePaintCallback(nsDisplayListBuilder* aBuilder,
385 LayerManager* aManager,
386 const nsPoint& aOffset)
387 : mBuilder(aBuilder), mLayerManager(aManager),
388 mOffset(aOffset) {}
389
Paint(gfxContext & aContext,nsIFrame * aTarget,const gfxMatrix & aTransform,const nsIntRect * aDirtyRect)390 virtual DrawResult Paint(gfxContext& aContext, nsIFrame *aTarget,
391 const gfxMatrix& aTransform,
392 const nsIntRect* aDirtyRect) override
393 {
394 BasicLayerManager* basic = mLayerManager->AsBasicLayerManager();
395 basic->SetTarget(&aContext);
396
397 gfxPoint devPixelOffset =
398 nsLayoutUtils::PointToGfxPoint(-mOffset,
399 aTarget->PresContext()->AppUnitsPerDevPixel());
400
401 gfxContextMatrixAutoSaveRestore autoSR(&aContext);
402 aContext.SetMatrix(aContext.CurrentMatrix().Translate(devPixelOffset));
403
404 mLayerManager->EndTransaction(FrameLayerBuilder::DrawPaintedLayer, mBuilder);
405 return DrawResult::SUCCESS;
406 }
407
408 private:
409 nsDisplayListBuilder* mBuilder;
410 LayerManager* mLayerManager;
411 nsPoint mOffset;
412 };
413
414 /**
415 * Returns true if any of the masks is an image mask (and not an SVG mask).
416 */
417 static bool
HasNonSVGMask(const nsTArray<nsSVGMaskFrame * > & aMaskFrames)418 HasNonSVGMask(const nsTArray<nsSVGMaskFrame*>& aMaskFrames)
419 {
420 for (size_t i = 0; i < aMaskFrames.Length() ; i++) {
421 nsSVGMaskFrame *maskFrame = aMaskFrames[i];
422 if (!maskFrame) {
423 return true;
424 }
425 }
426
427 return false;
428 }
429
430 typedef nsSVGIntegrationUtils::PaintFramesParams PaintFramesParams;
431
432 /**
433 * Paint css-positioned-mask onto a given target(aMaskDT).
434 */
435 static DrawResult
PaintMaskSurface(const PaintFramesParams & aParams,DrawTarget * aMaskDT,float aOpacity,nsStyleContext * aSC,const nsTArray<nsSVGMaskFrame * > & aMaskFrames,const gfxMatrix & aMaskSurfaceMatrix,const nsPoint & aOffsetToUserSpace)436 PaintMaskSurface(const PaintFramesParams& aParams,
437 DrawTarget* aMaskDT, float aOpacity, nsStyleContext* aSC,
438 const nsTArray<nsSVGMaskFrame*>& aMaskFrames,
439 const gfxMatrix& aMaskSurfaceMatrix,
440 const nsPoint& aOffsetToUserSpace)
441 {
442 MOZ_ASSERT(aMaskFrames.Length() > 0);
443 MOZ_ASSERT(aMaskDT->GetFormat() == SurfaceFormat::A8);
444
445 const nsStyleSVGReset *svgReset = aSC->StyleSVGReset();
446 gfxMatrix cssPxToDevPxMatrix =
447 nsSVGIntegrationUtils::GetCSSPxToDevPxMatrix(aParams.frame);
448
449 nsPresContext* presContext = aParams.frame->PresContext();
450 gfxPoint devPixelOffsetToUserSpace =
451 nsLayoutUtils::PointToGfxPoint(aOffsetToUserSpace,
452 presContext->AppUnitsPerDevPixel());
453
454 RefPtr<gfxContext> maskContext = gfxContext::CreateOrNull(aMaskDT);
455 MOZ_ASSERT(maskContext);
456 maskContext->SetMatrix(aMaskSurfaceMatrix);
457
458 // Multiple SVG masks interleave with image mask. Paint each layer onto
459 // aMaskDT one at a time.
460 for (int i = aMaskFrames.Length() - 1; i >= 0 ; i--) {
461 nsSVGMaskFrame *maskFrame = aMaskFrames[i];
462
463 CompositionOp compositionOp = (i == int(aMaskFrames.Length() - 1))
464 ? CompositionOp::OP_OVER
465 : nsCSSRendering::GetGFXCompositeMode(svgReset->mMask.mLayers[i].mComposite);
466
467 // maskFrame != nullptr means we get a SVG mask.
468 // maskFrame == nullptr means we get an image mask.
469 if (maskFrame) {
470 Matrix svgMaskMatrix;
471 RefPtr<SourceSurface> svgMask =
472 maskFrame->GetMaskForMaskedFrame(maskContext, aParams.frame,
473 cssPxToDevPxMatrix,
474 aOpacity,
475 &svgMaskMatrix,
476 svgReset->mMask.mLayers[i].mMaskMode);
477 if (svgMask) {
478 gfxContextMatrixAutoSaveRestore matRestore(maskContext);
479
480 maskContext->Multiply(ThebesMatrix(svgMaskMatrix));
481 Rect drawRect = IntRectToRect(IntRect(IntPoint(0, 0), svgMask->GetSize()));
482 aMaskDT->MaskSurface(ColorPattern(Color(0.0, 0.0, 0.0, 1.0)), svgMask,
483 drawRect.TopLeft(),
484 DrawOptions(1.0, compositionOp));
485 }
486 } else {
487 gfxContextMatrixAutoSaveRestore matRestore(maskContext);
488
489 maskContext->Multiply(gfxMatrix::Translation(-devPixelOffsetToUserSpace));
490 nsRenderingContext rc(maskContext);
491 nsCSSRendering::PaintBGParams params =
492 nsCSSRendering::PaintBGParams::ForSingleLayer(*presContext,
493 rc, aParams.dirtyRect,
494 aParams.borderArea,
495 aParams.frame,
496 aParams.builder->GetBackgroundPaintFlags() |
497 nsCSSRendering::PAINTBG_MASK_IMAGE,
498 i, compositionOp);
499
500 DrawResult result =
501 nsCSSRendering::PaintBackgroundWithSC(params, aSC,
502 *aParams.frame->StyleBorder());
503 if (result != DrawResult::SUCCESS) {
504 return result;
505 }
506 }
507 }
508
509 return DrawResult::SUCCESS;
510 }
511
512 static DrawResult
CreateAndPaintMaskSurface(const PaintFramesParams & aParams,float aOpacity,nsStyleContext * aSC,const nsTArray<nsSVGMaskFrame * > & aMaskFrames,const nsPoint & aOffsetToUserSpace,Matrix & aOutMaskTransform,RefPtr<SourceSurface> & aOutMaskSurface,bool & aOpacityApplied)513 CreateAndPaintMaskSurface(const PaintFramesParams& aParams,
514 float aOpacity, nsStyleContext* aSC,
515 const nsTArray<nsSVGMaskFrame*>& aMaskFrames,
516 const nsPoint& aOffsetToUserSpace,
517 Matrix& aOutMaskTransform,
518 RefPtr<SourceSurface>& aOutMaskSurface,
519 bool& aOpacityApplied)
520 {
521 const nsStyleSVGReset *svgReset = aSC->StyleSVGReset();
522 MOZ_ASSERT(aMaskFrames.Length() > 0);
523
524 gfxContext& ctx = aParams.ctx;
525
526 // There is only one SVG mask.
527 if (((aMaskFrames.Length() == 1) && aMaskFrames[0])) {
528 gfxMatrix cssPxToDevPxMatrix =
529 nsSVGIntegrationUtils::GetCSSPxToDevPxMatrix(aParams.frame);
530
531 aOpacityApplied = true;
532 aOutMaskSurface =
533 aMaskFrames[0]->GetMaskForMaskedFrame(&ctx, aParams.frame,
534 cssPxToDevPxMatrix, aOpacity,
535 &aOutMaskTransform,
536 svgReset->mMask.mLayers[0].mMaskMode);
537 return DrawResult::SUCCESS;
538 }
539
540 const IntRect& maskSurfaceRect = aParams.maskRect;
541 if (maskSurfaceRect.IsEmpty()) {
542 return DrawResult::SUCCESS;
543 }
544
545 RefPtr<DrawTarget> maskDT =
546 ctx.GetDrawTarget()->CreateSimilarDrawTarget(maskSurfaceRect.Size(),
547 SurfaceFormat::A8);
548 if (!maskDT || !maskDT->IsValid()) {
549 return DrawResult::TEMPORARY_ERROR;
550 }
551
552 // Set aAppliedOpacity as true only if all mask layers are svg mask.
553 // In this case, we will apply opacity into the final mask surface, so the
554 // caller does not need to apply it again.
555 aOpacityApplied = !HasNonSVGMask(aMaskFrames);
556
557 // Set context's matrix on maskContext, offset by the maskSurfaceRect's
558 // position. This makes sure that we combine the masks in device space.
559 gfxMatrix maskSurfaceMatrix =
560 ctx.CurrentMatrix() * gfxMatrix::Translation(-aParams.maskRect.TopLeft());
561
562 DrawResult result = PaintMaskSurface(aParams, maskDT,
563 aOpacityApplied ? aOpacity : 1.0,
564 aSC, aMaskFrames, maskSurfaceMatrix,
565 aOffsetToUserSpace);
566 if (result != DrawResult::SUCCESS) {
567 return result;
568 }
569
570 aOutMaskTransform = ToMatrix(maskSurfaceMatrix);
571 if (!aOutMaskTransform.Invert()) {
572 return DrawResult::SUCCESS;
573 }
574
575 aOutMaskSurface = maskDT->Snapshot();
576 return DrawResult::SUCCESS;
577 }
578
579 static bool
ValidateSVGFrame(nsIFrame * aFrame)580 ValidateSVGFrame(nsIFrame* aFrame)
581 {
582 #ifdef DEBUG
583 NS_ASSERTION(!(aFrame->GetStateBits() & NS_FRAME_SVG_LAYOUT) ||
584 (NS_SVGDisplayListPaintingEnabled() &&
585 !(aFrame->GetStateBits() & NS_FRAME_IS_NONDISPLAY)),
586 "Should not use nsSVGIntegrationUtils on this SVG frame");
587 #endif
588
589 bool hasSVGLayout = (aFrame->GetStateBits() & NS_FRAME_SVG_LAYOUT);
590 if (hasSVGLayout) {
591 #ifdef DEBUG
592 nsISVGChildFrame *svgChildFrame = do_QueryFrame(aFrame);
593 MOZ_ASSERT(svgChildFrame && aFrame->GetContent()->IsSVGElement(),
594 "A non-SVG frame carries NS_FRAME_SVG_LAYOUT flag?");
595 #endif
596
597 const nsIContent* content = aFrame->GetContent();
598 if (!static_cast<const nsSVGElement*>(content)->HasValidDimensions()) {
599 // The SVG spec says not to draw _anything_
600 return false;
601 }
602 }
603
604 return true;
605 }
606
607 /**
608 * Setup transform matrix of a gfx context by a specific frame. Depend on
609 * aClipCtx, this function may clip that context by the visual overflow area
610 * of aFrame.
611 *
612 * @param aFrame is the target frame.
613 * @param aOffsetToBoundingBox returns the offset between the reference frame
614 * and the bounding box of aFrame.
615 * @oaram aOffsetToUserSpace returns the offset between the reference frame and
616 * the user space coordinate of aFrame.
617 */
618 static void
SetupContextMatrix(nsIFrame * aFrame,const PaintFramesParams & aParams,nsPoint & aOffsetToBoundingBox,nsPoint & aOffsetToUserSpace)619 SetupContextMatrix(nsIFrame* aFrame, const PaintFramesParams& aParams,
620 nsPoint& aOffsetToBoundingBox, nsPoint& aOffsetToUserSpace)
621 {
622 aOffsetToBoundingBox = aParams.builder->ToReferenceFrame(aFrame) -
623 nsSVGIntegrationUtils::GetOffsetToBoundingBox(aFrame);
624 if (!aFrame->IsFrameOfType(nsIFrame::eSVG)) {
625 /* Snap the offset if the reference frame is not a SVG frame,
626 * since other frames will be snapped to pixel when rendering. */
627 aOffsetToBoundingBox = nsPoint(
628 aFrame->PresContext()->RoundAppUnitsToNearestDevPixels(aOffsetToBoundingBox.x),
629 aFrame->PresContext()->RoundAppUnitsToNearestDevPixels(aOffsetToBoundingBox.y));
630 }
631
632 // After applying only "aOffsetToBoundingBox", aParams.ctx would have its
633 // origin at the top left corner of frame's bounding box (over all
634 // continuations).
635 // However, SVG painting needs the origin to be located at the origin of the
636 // SVG frame's "user space", i.e. the space in which, for example, the
637 // frame's BBox lives.
638 // SVG geometry frames and foreignObject frames apply their own offsets, so
639 // their position is relative to their user space. So for these frame types,
640 // if we want aCtx to be in user space, we first need to subtract the
641 // frame's position so that SVG painting can later add it again and the
642 // frame is painted in the right place.
643
644 gfxPoint toUserSpaceGfx = nsSVGUtils::FrameSpaceInCSSPxToUserSpaceOffset(aFrame);
645 nsPoint toUserSpace =
646 nsPoint(nsPresContext::CSSPixelsToAppUnits(float(toUserSpaceGfx.x)),
647 nsPresContext::CSSPixelsToAppUnits(float(toUserSpaceGfx.y)));
648
649 aOffsetToUserSpace = aOffsetToBoundingBox - toUserSpace;
650
651 #ifdef DEBUG
652 bool hasSVGLayout = (aFrame->GetStateBits() & NS_FRAME_SVG_LAYOUT);
653 NS_ASSERTION(hasSVGLayout || aOffsetToBoundingBox == aOffsetToUserSpace,
654 "For non-SVG frames there shouldn't be any additional offset");
655 #endif
656
657 gfxPoint devPixelOffsetToUserSpace =
658 nsLayoutUtils::PointToGfxPoint(aOffsetToUserSpace,
659 aFrame->PresContext()->AppUnitsPerDevPixel());
660 gfxContext& context = aParams.ctx;
661 context.SetMatrix(context.CurrentMatrix().Translate(devPixelOffsetToUserSpace));
662 }
663
664 bool
IsMaskResourceReady(nsIFrame * aFrame)665 nsSVGIntegrationUtils::IsMaskResourceReady(nsIFrame* aFrame)
666 {
667 nsIFrame* firstFrame =
668 nsLayoutUtils::FirstContinuationOrIBSplitSibling(aFrame);
669 nsSVGEffects::EffectProperties effectProperties =
670 nsSVGEffects::GetEffectProperties(firstFrame);
671 nsTArray<nsSVGMaskFrame*> maskFrames = effectProperties.GetMaskFrames();
672 const nsStyleSVGReset* svgReset = firstFrame->StyleSVGReset();
673
674 for (uint32_t i = 0; i < maskFrames.Length(); i++) {
675 // Refers to a valid SVG mask.
676 if (maskFrames[i]) {
677 continue;
678 }
679
680 // Refers to an external resource, which is not ready yet.
681 if (!svgReset->mMask.mLayers[i].mImage.IsComplete()) {
682 return false;
683 }
684 }
685
686 // Either all mask resources are ready, or no mask resource is needed.
687 return true;
688 }
689
690 DrawResult
PaintMask(const PaintFramesParams & aParams)691 nsSVGIntegrationUtils::PaintMask(const PaintFramesParams& aParams)
692 {
693 nsSVGUtils::MaskUsage maskUsage;
694 nsSVGUtils::DetermineMaskUsage(aParams.frame, aParams.handleOpacity,
695 maskUsage);
696 MOZ_ASSERT(maskUsage.shouldGenerateMaskLayer);
697
698 nsIFrame* frame = aParams.frame;
699 if (!ValidateSVGFrame(frame)) {
700 return DrawResult::SUCCESS;
701 }
702
703 if (maskUsage.opacity == 0.0f) {
704 return DrawResult::SUCCESS;
705 }
706
707 gfxContext& ctx = aParams.ctx;
708
709 gfxContextMatrixAutoSaveRestore matSR(&ctx);
710
711 nsIFrame* firstFrame =
712 nsLayoutUtils::FirstContinuationOrIBSplitSibling(frame);
713 nsSVGEffects::EffectProperties effectProperties =
714 nsSVGEffects::GetEffectProperties(firstFrame);
715 nsTArray<nsSVGMaskFrame *> maskFrames = effectProperties.GetMaskFrames();
716 bool opacityApplied = !HasNonSVGMask(maskFrames);
717
718 nsPoint offsetToBoundingBox;
719 nsPoint offsetToUserSpace;
720 SetupContextMatrix(frame, aParams, offsetToBoundingBox,
721 offsetToUserSpace);
722
723 return PaintMaskSurface(aParams, ctx.GetDrawTarget(),
724 opacityApplied ? maskUsage.opacity : 1.0,
725 firstFrame->StyleContext(), maskFrames,
726 ctx.CurrentMatrix(), offsetToUserSpace);
727 }
728
729 DrawResult
PaintMaskAndClipPath(const PaintFramesParams & aParams)730 nsSVGIntegrationUtils::PaintMaskAndClipPath(const PaintFramesParams& aParams)
731 {
732 MOZ_ASSERT(UsingMaskOrClipPathForFrame(aParams.frame),
733 "Should not use this method when no mask or clipPath effect"
734 "on this frame");
735
736 /* SVG defines the following rendering model:
737 *
738 * 1. Render geometry
739 * 2. Apply filter
740 * 3. Apply clipping, masking, group opacity
741 *
742 * We handle #3 here and perform a couple of optimizations:
743 *
744 * + Use cairo's clipPath when representable natively (single object
745 * clip region).
746 *
747 * + Merge opacity and masking if both used together.
748 */
749 nsIFrame* frame = aParams.frame;
750 DrawResult result = DrawResult::SUCCESS;
751 if (!ValidateSVGFrame(frame)) {
752 return result;
753 }
754
755 nsSVGUtils::MaskUsage maskUsage;
756 nsSVGUtils::DetermineMaskUsage(aParams.frame, aParams.handleOpacity,
757 maskUsage);
758
759 if (maskUsage.opacity == 0.0f) {
760 return DrawResult::SUCCESS;
761 }
762
763 gfxContext& context = aParams.ctx;
764 gfxContextMatrixAutoSaveRestore matrixAutoSaveRestore(&context);
765
766 /* Properties are added lazily and may have been removed by a restyle,
767 so make sure all applicable ones are set again. */
768 nsIFrame* firstFrame =
769 nsLayoutUtils::FirstContinuationOrIBSplitSibling(frame);
770 nsSVGEffects::EffectProperties effectProperties =
771 nsSVGEffects::GetEffectProperties(firstFrame);
772
773 bool isOK = effectProperties.HasNoFilterOrHasValidFilter();
774 nsSVGClipPathFrame *clipPathFrame = effectProperties.GetClipPathFrame(&isOK);
775
776 gfxMatrix cssPxToDevPxMatrix = GetCSSPxToDevPxMatrix(frame);
777 nsTArray<nsSVGMaskFrame*> maskFrames = effectProperties.GetMaskFrames();
778
779 nsPoint offsetToBoundingBox;
780 nsPoint offsetToUserSpace;
781
782 bool shouldGenerateMask = (maskUsage.opacity != 1.0f ||
783 maskUsage.shouldGenerateClipMaskLayer ||
784 maskUsage.shouldGenerateMaskLayer);
785
786 /* Check if we need to do additional operations on this child's
787 * rendering, which necessitates rendering into another surface. */
788 if (shouldGenerateMask) {
789 gfxContextMatrixAutoSaveRestore matSR;
790
791 Matrix maskTransform;
792 RefPtr<SourceSurface> maskSurface;
793 bool opacityApplied = false;
794
795 if (maskUsage.shouldGenerateMaskLayer) {
796 matSR.SetContext(&context);
797
798 // For css-mask, we want to generate a mask for each continuation frame,
799 // so we setup context matrix by the position of the current frame,
800 // instead of the first continuation frame.
801 SetupContextMatrix(frame, aParams, offsetToBoundingBox,
802 offsetToUserSpace);
803 result = CreateAndPaintMaskSurface(aParams, maskUsage.opacity,
804 firstFrame->StyleContext(),
805 maskFrames, offsetToUserSpace,
806 maskTransform, maskSurface,
807 opacityApplied);
808 if (!maskSurface) {
809 // Entire surface is clipped out.
810 return result;
811 }
812 }
813
814 if (maskUsage.shouldGenerateClipMaskLayer) {
815 matSR.Restore();
816 matSR.SetContext(&context);
817
818 SetupContextMatrix(firstFrame, aParams, offsetToBoundingBox,
819 offsetToUserSpace);
820 Matrix clippedMaskTransform;
821 RefPtr<SourceSurface> clipMaskSurface =
822 clipPathFrame->GetClipMask(context, frame, cssPxToDevPxMatrix,
823 &clippedMaskTransform, maskSurface,
824 maskTransform, &result);
825
826 if (clipMaskSurface) {
827 maskSurface = clipMaskSurface;
828 maskTransform = clippedMaskTransform;
829 } else {
830 // Either entire surface is clipped out, or gfx buffer allocation
831 // failure in nsSVGClipPathFrame::GetClipMask.
832 return result;
833 }
834 }
835
836 // opacity != 1.0f.
837 if (!maskUsage.shouldGenerateClipMaskLayer &&
838 !maskUsage.shouldGenerateMaskLayer) {
839 MOZ_ASSERT(maskUsage.opacity != 1.0f);
840
841 matSR.SetContext(&context);
842 SetupContextMatrix(firstFrame, aParams, offsetToBoundingBox,
843 offsetToUserSpace);
844 }
845
846 if (aParams.layerManager->GetRoot()->GetContentFlags() & Layer::CONTENT_COMPONENT_ALPHA) {
847 context.PushGroupAndCopyBackground(gfxContentType::COLOR_ALPHA,
848 opacityApplied
849 ? 1.0
850 : maskUsage.opacity,
851 maskSurface, maskTransform);
852 } else {
853 context.PushGroupForBlendBack(gfxContentType::COLOR_ALPHA,
854 opacityApplied ? 1.0 : maskUsage.opacity,
855 maskSurface, maskTransform);
856 }
857 }
858
859 /* If this frame has only a trivial clipPath, set up cairo's clipping now so
860 * we can just do normal painting and get it clipped appropriately.
861 */
862 if (maskUsage.shouldApplyClipPath || maskUsage.shouldApplyBasicShape) {
863 gfxContextMatrixAutoSaveRestore matSR(&context);
864
865 SetupContextMatrix(firstFrame, aParams, offsetToBoundingBox,
866 offsetToUserSpace);
867
868 MOZ_ASSERT(!maskUsage.shouldApplyClipPath ||
869 !maskUsage.shouldApplyBasicShape);
870 if (maskUsage.shouldApplyClipPath) {
871 clipPathFrame->ApplyClipPath(context, frame, cssPxToDevPxMatrix);
872 } else {
873 nsCSSClipPathInstance::ApplyBasicShapeClip(context, frame);
874 }
875 }
876
877 /* Paint the child */
878 context.SetMatrix(matrixAutoSaveRestore.Matrix());
879 BasicLayerManager* basic = aParams.layerManager->AsBasicLayerManager();
880 RefPtr<gfxContext> oldCtx = basic->GetTarget();
881 basic->SetTarget(&context);
882 aParams.layerManager->EndTransaction(FrameLayerBuilder::DrawPaintedLayer,
883 aParams.builder);
884 basic->SetTarget(oldCtx);
885
886 if (gfxPrefs::DrawMaskLayer()) {
887 gfxContextAutoSaveRestore saver(&context);
888
889 context.NewPath();
890 gfxRect drawingRect =
891 nsLayoutUtils::RectToGfxRect(aParams.borderArea,
892 frame->PresContext()->AppUnitsPerDevPixel());
893 context.Rectangle(drawingRect, true);
894 context.SetColor(Color(0.0, 1.0, 0.0, 1.0));
895 context.Fill();
896 }
897
898 if (maskUsage.shouldApplyClipPath || maskUsage.shouldApplyBasicShape) {
899 context.PopClip();
900 }
901
902 if (shouldGenerateMask) {
903 context.PopGroupAndBlend();
904 }
905
906 return result;
907 }
908
909 DrawResult
PaintFilter(const PaintFramesParams & aParams)910 nsSVGIntegrationUtils::PaintFilter(const PaintFramesParams& aParams)
911 {
912 MOZ_ASSERT(!aParams.builder->IsForGenerateGlyphMask(),
913 "Filter effect is discarded while generating glyph mask.");
914 MOZ_ASSERT(aParams.frame->StyleEffects()->HasFilters(),
915 "Should not use this method when no filter effect on this frame");
916
917 nsIFrame* frame = aParams.frame;
918 if (!ValidateSVGFrame(frame)) {
919 return DrawResult::SUCCESS;
920 }
921
922 float opacity = nsSVGUtils::ComputeOpacity(frame, aParams.handleOpacity);
923 if (opacity == 0.0f) {
924 return DrawResult::SUCCESS;
925 }
926
927 /* Properties are added lazily and may have been removed by a restyle,
928 so make sure all applicable ones are set again. */
929 nsIFrame* firstFrame =
930 nsLayoutUtils::FirstContinuationOrIBSplitSibling(frame);
931 nsSVGEffects::EffectProperties effectProperties =
932 nsSVGEffects::GetEffectProperties(firstFrame);
933
934 if (!effectProperties.HasValidFilter()) {
935 return DrawResult::NOT_READY;
936 }
937
938 gfxContext& context = aParams.ctx;
939 nsPoint offsetToBoundingBox;
940 nsPoint offsetToUserSpace;
941
942 gfxContextAutoSaveRestore autoSR(&context);
943 SetupContextMatrix(firstFrame, aParams, offsetToBoundingBox,
944 offsetToUserSpace);
945
946 if (opacity != 1.0f) {
947 context.PushGroupForBlendBack(gfxContentType::COLOR_ALPHA, opacity,
948 nullptr, Matrix());
949 }
950
951 /* Paint the child and apply filters */
952 RegularFramePaintCallback callback(aParams.builder, aParams.layerManager,
953 offsetToUserSpace);
954 nsRegion dirtyRegion = aParams.dirtyRect - offsetToBoundingBox;
955 gfxMatrix tm = nsSVGIntegrationUtils::GetCSSPxToDevPxMatrix(frame);
956 nsFilterInstance::PaintFilteredFrame(frame, context.GetDrawTarget(),
957 tm, &callback, &dirtyRegion);
958
959 if (opacity != 1.0f) {
960 context.PopGroupAndBlend();
961 }
962
963 return DrawResult::SUCCESS;
964 }
965
966 gfxMatrix
GetCSSPxToDevPxMatrix(nsIFrame * aNonSVGFrame)967 nsSVGIntegrationUtils::GetCSSPxToDevPxMatrix(nsIFrame* aNonSVGFrame)
968 {
969 int32_t appUnitsPerDevPixel = aNonSVGFrame->PresContext()->AppUnitsPerDevPixel();
970 float devPxPerCSSPx =
971 1 / nsPresContext::AppUnitsToFloatCSSPixels(appUnitsPerDevPixel);
972
973 return gfxMatrix(devPxPerCSSPx, 0.0,
974 0.0, devPxPerCSSPx,
975 0.0, 0.0);
976 }
977
978 class PaintFrameCallback : public gfxDrawingCallback {
979 public:
PaintFrameCallback(nsIFrame * aFrame,const nsSize aPaintServerSize,const IntSize aRenderSize,uint32_t aFlags)980 PaintFrameCallback(nsIFrame* aFrame,
981 const nsSize aPaintServerSize,
982 const IntSize aRenderSize,
983 uint32_t aFlags)
984 : mFrame(aFrame)
985 , mPaintServerSize(aPaintServerSize)
986 , mRenderSize(aRenderSize)
987 , mFlags (aFlags)
988 {}
989 virtual bool operator()(gfxContext* aContext,
990 const gfxRect& aFillRect,
991 const SamplingFilter aSamplingFilter,
992 const gfxMatrix& aTransform) override;
993 private:
994 nsIFrame* mFrame;
995 nsSize mPaintServerSize;
996 IntSize mRenderSize;
997 uint32_t mFlags;
998 };
999
1000 bool
operator ()(gfxContext * aContext,const gfxRect & aFillRect,const SamplingFilter aSamplingFilter,const gfxMatrix & aTransform)1001 PaintFrameCallback::operator()(gfxContext* aContext,
1002 const gfxRect& aFillRect,
1003 const SamplingFilter aSamplingFilter,
1004 const gfxMatrix& aTransform)
1005 {
1006 if (mFrame->GetStateBits() & NS_FRAME_DRAWING_AS_PAINTSERVER)
1007 return false;
1008
1009 mFrame->AddStateBits(NS_FRAME_DRAWING_AS_PAINTSERVER);
1010
1011 aContext->Save();
1012
1013 // Clip to aFillRect so that we don't paint outside.
1014 aContext->NewPath();
1015 aContext->Rectangle(aFillRect);
1016 aContext->Clip();
1017
1018 gfxMatrix invmatrix = aTransform;
1019 if (!invmatrix.Invert()) {
1020 return false;
1021 }
1022 aContext->Multiply(invmatrix);
1023
1024 // nsLayoutUtils::PaintFrame will anchor its painting at mFrame. But we want
1025 // to have it anchored at the top left corner of the bounding box of all of
1026 // mFrame's continuations. So we add a translation transform.
1027 int32_t appUnitsPerDevPixel = mFrame->PresContext()->AppUnitsPerDevPixel();
1028 nsPoint offset = nsSVGIntegrationUtils::GetOffsetToBoundingBox(mFrame);
1029 gfxPoint devPxOffset = gfxPoint(offset.x, offset.y) / appUnitsPerDevPixel;
1030 aContext->Multiply(gfxMatrix::Translation(devPxOffset));
1031
1032 gfxSize paintServerSize =
1033 gfxSize(mPaintServerSize.width, mPaintServerSize.height) /
1034 mFrame->PresContext()->AppUnitsPerDevPixel();
1035
1036 // nsLayoutUtils::PaintFrame wants to render with paintServerSize, but we
1037 // want it to render with mRenderSize, so we need to set up a scale transform.
1038 gfxFloat scaleX = mRenderSize.width / paintServerSize.width;
1039 gfxFloat scaleY = mRenderSize.height / paintServerSize.height;
1040 aContext->Multiply(gfxMatrix::Scaling(scaleX, scaleY));
1041
1042 // Draw.
1043 nsRect dirty(-offset.x, -offset.y,
1044 mPaintServerSize.width, mPaintServerSize.height);
1045
1046 using PaintFrameFlags = nsLayoutUtils::PaintFrameFlags;
1047 PaintFrameFlags flags = PaintFrameFlags::PAINT_IN_TRANSFORM;
1048 if (mFlags & nsSVGIntegrationUtils::FLAG_SYNC_DECODE_IMAGES) {
1049 flags |= PaintFrameFlags::PAINT_SYNC_DECODE_IMAGES;
1050 }
1051 nsRenderingContext context(aContext);
1052 nsLayoutUtils::PaintFrame(&context, mFrame,
1053 dirty, NS_RGBA(0, 0, 0, 0),
1054 nsDisplayListBuilderMode::PAINTING,
1055 flags);
1056
1057 nsIFrame* currentFrame = mFrame;
1058 while ((currentFrame = currentFrame->GetNextContinuation()) != nullptr) {
1059 offset = currentFrame->GetOffsetToCrossDoc(mFrame);
1060 devPxOffset = gfxPoint(offset.x, offset.y) / appUnitsPerDevPixel;
1061
1062 aContext->Save();
1063 aContext->Multiply(gfxMatrix::Scaling(1/scaleX, 1/scaleY));
1064 aContext->Multiply(gfxMatrix::Translation(devPxOffset));
1065 aContext->Multiply(gfxMatrix::Scaling(scaleX, scaleY));
1066
1067 nsLayoutUtils::PaintFrame(&context, currentFrame,
1068 dirty - offset, NS_RGBA(0, 0, 0, 0),
1069 nsDisplayListBuilderMode::PAINTING,
1070 flags);
1071
1072 aContext->Restore();
1073 }
1074
1075 aContext->Restore();
1076
1077 mFrame->RemoveStateBits(NS_FRAME_DRAWING_AS_PAINTSERVER);
1078
1079 return true;
1080 }
1081
1082 /* static */ already_AddRefed<gfxDrawable>
DrawableFromPaintServer(nsIFrame * aFrame,nsIFrame * aTarget,const nsSize & aPaintServerSize,const IntSize & aRenderSize,const DrawTarget * aDrawTarget,const gfxMatrix & aContextMatrix,uint32_t aFlags)1083 nsSVGIntegrationUtils::DrawableFromPaintServer(nsIFrame* aFrame,
1084 nsIFrame* aTarget,
1085 const nsSize& aPaintServerSize,
1086 const IntSize& aRenderSize,
1087 const DrawTarget* aDrawTarget,
1088 const gfxMatrix& aContextMatrix,
1089 uint32_t aFlags)
1090 {
1091 // aPaintServerSize is the size that would be filled when using
1092 // background-repeat:no-repeat and background-size:auto. For normal background
1093 // images, this would be the intrinsic size of the image; for gradients and
1094 // patterns this would be the whole target frame fill area.
1095 // aRenderSize is what we will be actually filling after accounting for
1096 // background-size.
1097 if (aFrame->IsFrameOfType(nsIFrame::eSVGPaintServer)) {
1098 // aFrame is either a pattern or a gradient. These fill the whole target
1099 // frame by default, so aPaintServerSize is the whole target background fill
1100 // area.
1101 nsSVGPaintServerFrame* server =
1102 static_cast<nsSVGPaintServerFrame*>(aFrame);
1103
1104 gfxRect overrideBounds(0, 0,
1105 aPaintServerSize.width, aPaintServerSize.height);
1106 overrideBounds.ScaleInverse(aFrame->PresContext()->AppUnitsPerDevPixel());
1107 RefPtr<gfxPattern> pattern =
1108 server->GetPaintServerPattern(aTarget, aDrawTarget,
1109 aContextMatrix, &nsStyleSVG::mFill, 1.0,
1110 &overrideBounds);
1111
1112 if (!pattern)
1113 return nullptr;
1114
1115 // pattern is now set up to fill aPaintServerSize. But we want it to
1116 // fill aRenderSize, so we need to add a scaling transform.
1117 // We couldn't just have set overrideBounds to aRenderSize - it would have
1118 // worked for gradients, but for patterns it would result in a different
1119 // pattern size.
1120 gfxFloat scaleX = overrideBounds.Width() / aRenderSize.width;
1121 gfxFloat scaleY = overrideBounds.Height() / aRenderSize.height;
1122 gfxMatrix scaleMatrix = gfxMatrix::Scaling(scaleX, scaleY);
1123 pattern->SetMatrix(scaleMatrix * pattern->GetMatrix());
1124 RefPtr<gfxDrawable> drawable =
1125 new gfxPatternDrawable(pattern, aRenderSize);
1126 return drawable.forget();
1127 }
1128
1129 if (aFrame->IsFrameOfType(nsIFrame::eSVG) &&
1130 !static_cast<nsISVGChildFrame*>(do_QueryFrame(aFrame))) {
1131 MOZ_ASSERT_UNREACHABLE("We should prevent painting of unpaintable SVG "
1132 "before we get here");
1133 return nullptr;
1134 }
1135
1136 // We don't want to paint into a surface as long as we don't need to, so we
1137 // set up a drawing callback.
1138 RefPtr<gfxDrawingCallback> cb =
1139 new PaintFrameCallback(aFrame, aPaintServerSize, aRenderSize, aFlags);
1140 RefPtr<gfxDrawable> drawable = new gfxCallbackDrawable(cb, aRenderSize);
1141 return drawable.forget();
1142 }
1143