|
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 "nsDisplayList.h" |
|
13 #include "nsFilterInstance.h" |
|
14 #include "nsLayoutUtils.h" |
|
15 #include "nsRenderingContext.h" |
|
16 #include "nsSVGClipPathFrame.h" |
|
17 #include "nsSVGEffects.h" |
|
18 #include "nsSVGElement.h" |
|
19 #include "nsSVGFilterPaintCallback.h" |
|
20 #include "nsSVGMaskFrame.h" |
|
21 #include "nsSVGPaintServerFrame.h" |
|
22 #include "nsSVGUtils.h" |
|
23 #include "FrameLayerBuilder.h" |
|
24 #include "BasicLayers.h" |
|
25 #include "mozilla/gfx/Point.h" |
|
26 |
|
27 using namespace mozilla; |
|
28 using namespace mozilla::layers; |
|
29 |
|
30 // ---------------------------------------------------------------------- |
|
31 |
|
32 /** |
|
33 * This class is used to get the pre-effects visual overflow rect of a frame, |
|
34 * or, in the case of a frame with continuations, to collect the union of the |
|
35 * pre-effects visual overflow rects of all the continuations. The result is |
|
36 * relative to the origin (top left corner of the border box) of the frame, or, |
|
37 * if the frame has continuations, the origin of the _first_ continuation. |
|
38 */ |
|
39 class PreEffectsVisualOverflowCollector : public nsLayoutUtils::BoxCallback |
|
40 { |
|
41 public: |
|
42 /** |
|
43 * If the pre-effects visual overflow rect of the frame being examined |
|
44 * happens to be known, it can be passed in as aCurrentFrame and its |
|
45 * pre-effects visual overflow rect can be passed in as |
|
46 * aCurrentFrameOverflowArea. This is just an optimization to save a |
|
47 * frame property lookup - these arguments are optional. |
|
48 */ |
|
49 PreEffectsVisualOverflowCollector(nsIFrame* aFirstContinuation, |
|
50 nsIFrame* aCurrentFrame, |
|
51 const nsRect& aCurrentFrameOverflowArea) |
|
52 : mFirstContinuation(aFirstContinuation) |
|
53 , mCurrentFrame(aCurrentFrame) |
|
54 , mCurrentFrameOverflowArea(aCurrentFrameOverflowArea) |
|
55 { |
|
56 NS_ASSERTION(!mFirstContinuation->GetPrevContinuation(), |
|
57 "We want the first continuation here"); |
|
58 } |
|
59 |
|
60 virtual void AddBox(nsIFrame* aFrame) MOZ_OVERRIDE { |
|
61 nsRect overflow = (aFrame == mCurrentFrame) ? |
|
62 mCurrentFrameOverflowArea : GetPreEffectsVisualOverflowRect(aFrame); |
|
63 mResult.UnionRect(mResult, overflow + aFrame->GetOffsetTo(mFirstContinuation)); |
|
64 } |
|
65 |
|
66 nsRect GetResult() const { |
|
67 return mResult; |
|
68 } |
|
69 |
|
70 private: |
|
71 |
|
72 static nsRect GetPreEffectsVisualOverflowRect(nsIFrame* aFrame) { |
|
73 nsRect* r = static_cast<nsRect*> |
|
74 (aFrame->Properties().Get(nsIFrame::PreEffectsBBoxProperty())); |
|
75 if (r) { |
|
76 return *r; |
|
77 } |
|
78 // Despite the fact that we're invoked for frames with SVG effects applied, |
|
79 // we can actually get here. All continuations and IB split siblings of a |
|
80 // frame with SVG effects applied will have the PreEffectsBBoxProperty |
|
81 // property set on them. Therefore, the frames that are passed to us will |
|
82 // always have that property set...well, with one exception. If the frames |
|
83 // for an element with SVG effects applied have been subject to an "IB |
|
84 // split", then the block frame(s) that caused the split will have been |
|
85 // wrapped in anonymous, inline-block, nsBlockFrames of pseudo-type |
|
86 // nsCSSAnonBoxes::mozAnonymousBlock. These "IB split sibling" anonymous |
|
87 // blocks will have the PreEffectsBBoxProperty property set on them, but |
|
88 // they will never be passed to us. Instead, we'll be passed the block |
|
89 // children that they wrap, which don't have the PreEffectsBBoxProperty |
|
90 // property set on them. This is actually okay. What we care about is |
|
91 // collecting the _pre_ effects visual overflow rects of the frames to |
|
92 // which the SVG effects have been applied. Since the IB split results in |
|
93 // any overflow rect adjustments for transforms, effects, etc. taking |
|
94 // place on the anonymous block wrappers, the wrapped children are left |
|
95 // with their overflow rects unaffected. In other words, calling |
|
96 // GetVisualOverflowRect() on the children will return their pre-effects |
|
97 // visual overflow rects, just as we need. |
|
98 // |
|
99 // A couple of tests that demonstrate the IB split and cause us to get here |
|
100 // are: |
|
101 // |
|
102 // * reftests/svg/svg-integration/clipPath-html-06.xhtml |
|
103 // * reftests/svg/svg-integration/clipPath-html-06-extref.xhtml |
|
104 // |
|
105 // If we ever got passed a frame with the PreTransformOverflowAreasProperty |
|
106 // property set, that would be bad, since then our GetVisualOverflowRect() |
|
107 // call would give us the post-effects, and post-transform, overflow rect. |
|
108 // |
|
109 NS_ASSERTION(aFrame->GetParent()->StyleContext()->GetPseudo() == |
|
110 nsCSSAnonBoxes::mozAnonymousBlock, |
|
111 "How did we getting here, then?"); |
|
112 NS_ASSERTION(!aFrame->Properties().Get( |
|
113 aFrame->PreTransformOverflowAreasProperty()), |
|
114 "GetVisualOverflowRect() won't return the pre-effects rect!"); |
|
115 return aFrame->GetVisualOverflowRect(); |
|
116 } |
|
117 |
|
118 nsIFrame* mFirstContinuation; |
|
119 nsIFrame* mCurrentFrame; |
|
120 const nsRect& mCurrentFrameOverflowArea; |
|
121 nsRect mResult; |
|
122 }; |
|
123 |
|
124 /** |
|
125 * Gets the union of the pre-effects visual overflow rects of all of a frame's |
|
126 * continuations, in "user space". |
|
127 */ |
|
128 static nsRect |
|
129 GetPreEffectsVisualOverflowUnion(nsIFrame* aFirstContinuation, |
|
130 nsIFrame* aCurrentFrame, |
|
131 const nsRect& aCurrentFramePreEffectsOverflow, |
|
132 const nsPoint& aFirstContinuationToUserSpace) |
|
133 { |
|
134 NS_ASSERTION(!aFirstContinuation->GetPrevContinuation(), |
|
135 "Need first continuation here"); |
|
136 PreEffectsVisualOverflowCollector collector(aFirstContinuation, |
|
137 aCurrentFrame, |
|
138 aCurrentFramePreEffectsOverflow); |
|
139 // Compute union of all overflow areas relative to aFirstContinuation: |
|
140 nsLayoutUtils::GetAllInFlowBoxes(aFirstContinuation, &collector); |
|
141 // Return the result in user space: |
|
142 return collector.GetResult() + aFirstContinuationToUserSpace; |
|
143 } |
|
144 |
|
145 |
|
146 bool |
|
147 nsSVGIntegrationUtils::UsingEffectsForFrame(const nsIFrame* aFrame) |
|
148 { |
|
149 // Even when SVG display lists are disabled, returning true for SVG frames |
|
150 // does not adversely affect any of our callers. Therefore we don't bother |
|
151 // checking the SDL prefs here, since we don't know if we're being called for |
|
152 // painting or hit-testing anyway. |
|
153 const nsStyleSVGReset *style = aFrame->StyleSVGReset(); |
|
154 return (style->HasFilters() || style->mClipPath || style->mMask); |
|
155 } |
|
156 |
|
157 // For non-SVG frames, this gives the offset to the frame's "user space". |
|
158 // For SVG frames, this returns a zero offset. |
|
159 static nsPoint |
|
160 GetOffsetToBoundingBox(nsIFrame* aFrame) |
|
161 { |
|
162 if ((aFrame->GetStateBits() & NS_FRAME_SVG_LAYOUT)) { |
|
163 // Do NOT call GetAllInFlowRectsUnion for SVG - it will get the |
|
164 // covered region relative to the nsSVGOuterSVGFrame, which is absolutely |
|
165 // not what we want. SVG frames are always in user space, so they have |
|
166 // no offset adjustment to make. |
|
167 return nsPoint(); |
|
168 } |
|
169 // We could allow aFrame to be any continuation, but since that would require |
|
170 // a GetPrevContinuation() virtual call and conditional returns, and since |
|
171 // all our current consumers always pass in the first continuation, we don't |
|
172 // currently bother. |
|
173 NS_ASSERTION(!aFrame->GetPrevContinuation(), "Not first continuation"); |
|
174 |
|
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(); |
|
179 } |
|
180 |
|
181 /* static */ nsSize |
|
182 nsSVGIntegrationUtils::GetContinuationUnionSize(nsIFrame* aNonSVGFrame) |
|
183 { |
|
184 NS_ASSERTION(!aNonSVGFrame->IsFrameOfType(nsIFrame::eSVG), |
|
185 "SVG frames should not get here"); |
|
186 nsIFrame* firstFrame = |
|
187 nsLayoutUtils::FirstContinuationOrIBSplitSibling(aNonSVGFrame); |
|
188 return nsLayoutUtils::GetAllInFlowRectsUnion(firstFrame, firstFrame).Size(); |
|
189 } |
|
190 |
|
191 /* static */ gfx::Size |
|
192 nsSVGIntegrationUtils::GetSVGCoordContextForNonSVGFrame(nsIFrame* aNonSVGFrame) |
|
193 { |
|
194 NS_ASSERTION(!aNonSVGFrame->IsFrameOfType(nsIFrame::eSVG), |
|
195 "SVG frames should not get here"); |
|
196 nsIFrame* firstFrame = |
|
197 nsLayoutUtils::FirstContinuationOrIBSplitSibling(aNonSVGFrame); |
|
198 nsRect r = nsLayoutUtils::GetAllInFlowRectsUnion(firstFrame, firstFrame); |
|
199 nsPresContext* presContext = firstFrame->PresContext(); |
|
200 return gfx::Size(presContext->AppUnitsToFloatCSSPixels(r.width), |
|
201 presContext->AppUnitsToFloatCSSPixels(r.height)); |
|
202 } |
|
203 |
|
204 gfxRect |
|
205 nsSVGIntegrationUtils::GetSVGBBoxForNonSVGFrame(nsIFrame* aNonSVGFrame) |
|
206 { |
|
207 NS_ASSERTION(!aNonSVGFrame->IsFrameOfType(nsIFrame::eSVG), |
|
208 "SVG frames should not get here"); |
|
209 nsIFrame* firstFrame = |
|
210 nsLayoutUtils::FirstContinuationOrIBSplitSibling(aNonSVGFrame); |
|
211 // 'r' is in "user space": |
|
212 nsRect r = GetPreEffectsVisualOverflowUnion(firstFrame, nullptr, nsRect(), |
|
213 GetOffsetToBoundingBox(firstFrame)); |
|
214 return nsLayoutUtils::RectToGfxRect(r, |
|
215 aNonSVGFrame->PresContext()->AppUnitsPerCSSPixel()); |
|
216 } |
|
217 |
|
218 // XXX Since we're called during reflow, this method is broken for frames with |
|
219 // continuations. When we're called for a frame with continuations, we're |
|
220 // called for each continuation in turn as it's reflowed. However, it isn't |
|
221 // until the last continuation is reflowed that this method's |
|
222 // GetOffsetToBoundingBox() and GetPreEffectsVisualOverflowUnion() calls will |
|
223 // obtain valid border boxes for all the continuations. As a result, we'll |
|
224 // end up returning bogus post-filter visual overflow rects for all the prior |
|
225 // continuations. Unfortunately, by the time the last continuation is |
|
226 // reflowed, it's too late to go back and set and propagate the overflow |
|
227 // rects on the previous continuations. |
|
228 // |
|
229 // The reason that we need to pass an override bbox to |
|
230 // GetPreEffectsVisualOverflowUnion rather than just letting it call into our |
|
231 // GetSVGBBoxForNonSVGFrame method is because we get called by |
|
232 // ComputeEffectsRect when it has been called with |
|
233 // aStoreRectProperties set to false. In this case the pre-effects visual |
|
234 // overflow rect that it has been passed may be different to that stored on |
|
235 // aFrame, resulting in a different bbox. |
|
236 // |
|
237 // XXXjwatt The pre-effects visual overflow rect passed to |
|
238 // ComputeEffectsRect won't include continuation overflows, so |
|
239 // for frames with continuation the following filter analysis will likely end |
|
240 // up being carried out with a bbox created as if the frame didn't have |
|
241 // continuations. |
|
242 // |
|
243 // XXXjwatt Using aPreEffectsOverflowRect to create the bbox isn't really right |
|
244 // for SVG frames, since for SVG frames the SVG spec defines the bbox to be |
|
245 // something quite different to the pre-effects visual overflow rect. However, |
|
246 // we're essentially calculating an invalidation area here, and using the |
|
247 // pre-effects overflow rect will actually overestimate that area which, while |
|
248 // being a bit wasteful, isn't otherwise a problem. |
|
249 // |
|
250 nsRect |
|
251 nsSVGIntegrationUtils:: |
|
252 ComputePostEffectsVisualOverflowRect(nsIFrame* aFrame, |
|
253 const nsRect& aPreEffectsOverflowRect) |
|
254 { |
|
255 NS_ASSERTION(!(aFrame->GetStateBits() & NS_FRAME_SVG_LAYOUT), |
|
256 "Don't call this on SVG child frames"); |
|
257 |
|
258 nsIFrame* firstFrame = |
|
259 nsLayoutUtils::FirstContinuationOrIBSplitSibling(aFrame); |
|
260 nsSVGEffects::EffectProperties effectProperties = |
|
261 nsSVGEffects::GetEffectProperties(firstFrame); |
|
262 if (!effectProperties.HasValidFilter()) { |
|
263 return aPreEffectsOverflowRect; |
|
264 } |
|
265 |
|
266 // Create an override bbox - see comment above: |
|
267 nsPoint firstFrameToBoundingBox = GetOffsetToBoundingBox(firstFrame); |
|
268 // overrideBBox is in "user space", in _CSS_ pixels: |
|
269 // XXX Why are we rounding out to pixel boundaries? We don't do that in |
|
270 // GetSVGBBoxForNonSVGFrame, and it doesn't appear to be necessary. |
|
271 gfxRect overrideBBox = |
|
272 nsLayoutUtils::RectToGfxRect( |
|
273 GetPreEffectsVisualOverflowUnion(firstFrame, aFrame, |
|
274 aPreEffectsOverflowRect, |
|
275 firstFrameToBoundingBox), |
|
276 aFrame->PresContext()->AppUnitsPerCSSPixel()); |
|
277 overrideBBox.RoundOut(); |
|
278 |
|
279 nsRect overflowRect = |
|
280 nsFilterInstance::GetPostFilterBounds(firstFrame, &overrideBBox); |
|
281 |
|
282 // Return overflowRect relative to aFrame, rather than "user space": |
|
283 return overflowRect - (aFrame->GetOffsetTo(firstFrame) + firstFrameToBoundingBox); |
|
284 } |
|
285 |
|
286 nsIntRegion |
|
287 nsSVGIntegrationUtils::AdjustInvalidAreaForSVGEffects(nsIFrame* aFrame, |
|
288 const nsPoint& aToReferenceFrame, |
|
289 const nsIntRegion& aInvalidRegion) |
|
290 { |
|
291 if (aInvalidRegion.IsEmpty()) { |
|
292 return nsIntRect(); |
|
293 } |
|
294 |
|
295 // Don't bother calling GetEffectProperties; the filter property should |
|
296 // already have been set up during reflow/ComputeFrameEffectsRect |
|
297 nsIFrame* firstFrame = |
|
298 nsLayoutUtils::FirstContinuationOrIBSplitSibling(aFrame); |
|
299 nsSVGFilterProperty *prop = nsSVGEffects::GetFilterProperty(firstFrame); |
|
300 if (!prop || !prop->IsInObserverLists()) { |
|
301 return aInvalidRegion; |
|
302 } |
|
303 |
|
304 int32_t appUnitsPerDevPixel = aFrame->PresContext()->AppUnitsPerDevPixel(); |
|
305 |
|
306 if (!prop || !prop->ReferencesValidResources()) { |
|
307 // The frame is either not there or not currently available, |
|
308 // perhaps because we're in the middle of tearing stuff down. |
|
309 // Be conservative, return our visual overflow rect relative |
|
310 // to the reference frame. |
|
311 nsRect overflow = aFrame->GetVisualOverflowRect() + aToReferenceFrame; |
|
312 return overflow.ToOutsidePixels(appUnitsPerDevPixel); |
|
313 } |
|
314 |
|
315 // Convert aInvalidRegion into bounding box frame space in app units: |
|
316 nsPoint toBoundingBox = |
|
317 aFrame->GetOffsetTo(firstFrame) + GetOffsetToBoundingBox(firstFrame); |
|
318 // The initial rect was relative to the reference frame, so we need to |
|
319 // remove that offset to get a rect relative to the current frame. |
|
320 toBoundingBox -= aToReferenceFrame; |
|
321 nsRegion preEffectsRegion = aInvalidRegion.ToAppUnits(appUnitsPerDevPixel).MovedBy(toBoundingBox); |
|
322 |
|
323 // Adjust the dirty area for effects, and shift it back to being relative to |
|
324 // the reference frame. |
|
325 nsRegion result = nsFilterInstance::GetPostFilterDirtyArea(firstFrame, |
|
326 preEffectsRegion).MovedBy(-toBoundingBox); |
|
327 // Return the result, in pixels relative to the reference frame. |
|
328 return result.ToOutsidePixels(appUnitsPerDevPixel); |
|
329 } |
|
330 |
|
331 nsRect |
|
332 nsSVGIntegrationUtils::GetRequiredSourceForInvalidArea(nsIFrame* aFrame, |
|
333 const nsRect& aDirtyRect) |
|
334 { |
|
335 // Don't bother calling GetEffectProperties; the filter property should |
|
336 // already have been set up during reflow/ComputeFrameEffectsRect |
|
337 nsIFrame* firstFrame = |
|
338 nsLayoutUtils::FirstContinuationOrIBSplitSibling(aFrame); |
|
339 nsSVGFilterProperty *prop = nsSVGEffects::GetFilterProperty(firstFrame); |
|
340 if (!prop || !prop->ReferencesValidResources()) { |
|
341 return aDirtyRect; |
|
342 } |
|
343 |
|
344 // Convert aDirtyRect into "user space" in app units: |
|
345 nsPoint toUserSpace = |
|
346 aFrame->GetOffsetTo(firstFrame) + GetOffsetToBoundingBox(firstFrame); |
|
347 nsRect postEffectsRect = aDirtyRect + toUserSpace; |
|
348 |
|
349 // Return ther result, relative to aFrame, not in user space: |
|
350 return nsFilterInstance::GetPreFilterNeededArea(firstFrame, postEffectsRect).GetBounds() |
|
351 - toUserSpace; |
|
352 } |
|
353 |
|
354 bool |
|
355 nsSVGIntegrationUtils::HitTestFrameForEffects(nsIFrame* aFrame, const nsPoint& aPt) |
|
356 { |
|
357 nsIFrame* firstFrame = |
|
358 nsLayoutUtils::FirstContinuationOrIBSplitSibling(aFrame); |
|
359 // Convert aPt to user space: |
|
360 nsPoint toUserSpace; |
|
361 if (aFrame->GetStateBits() & NS_FRAME_SVG_LAYOUT) { |
|
362 // XXXmstange Isn't this wrong for svg:use and innerSVG frames? |
|
363 toUserSpace = aFrame->GetPosition(); |
|
364 } else { |
|
365 toUserSpace = |
|
366 aFrame->GetOffsetTo(firstFrame) + GetOffsetToBoundingBox(firstFrame); |
|
367 } |
|
368 nsPoint pt = aPt + toUserSpace; |
|
369 return nsSVGUtils::HitTestClip(firstFrame, pt); |
|
370 } |
|
371 |
|
372 class RegularFramePaintCallback : public nsSVGFilterPaintCallback |
|
373 { |
|
374 public: |
|
375 RegularFramePaintCallback(nsDisplayListBuilder* aBuilder, |
|
376 LayerManager* aManager, |
|
377 const nsPoint& aOffset) |
|
378 : mBuilder(aBuilder), mLayerManager(aManager), |
|
379 mOffset(aOffset) {} |
|
380 |
|
381 virtual void Paint(nsRenderingContext *aContext, nsIFrame *aTarget, |
|
382 const nsIntRect* aDirtyRect, |
|
383 nsIFrame* aTransformRoot) MOZ_OVERRIDE |
|
384 { |
|
385 BasicLayerManager* basic = static_cast<BasicLayerManager*>(mLayerManager); |
|
386 basic->SetTarget(aContext->ThebesContext()); |
|
387 nsRenderingContext::AutoPushTranslation push(aContext, -mOffset); |
|
388 mLayerManager->EndTransaction(FrameLayerBuilder::DrawThebesLayer, mBuilder); |
|
389 } |
|
390 |
|
391 private: |
|
392 nsDisplayListBuilder* mBuilder; |
|
393 LayerManager* mLayerManager; |
|
394 nsPoint mOffset; |
|
395 }; |
|
396 |
|
397 void |
|
398 nsSVGIntegrationUtils::PaintFramesWithEffects(nsRenderingContext* aCtx, |
|
399 nsIFrame* aFrame, |
|
400 const nsRect& aDirtyRect, |
|
401 nsDisplayListBuilder* aBuilder, |
|
402 LayerManager *aLayerManager) |
|
403 { |
|
404 #ifdef DEBUG |
|
405 NS_ASSERTION(!(aFrame->GetStateBits() & NS_FRAME_SVG_LAYOUT) || |
|
406 (NS_SVGDisplayListPaintingEnabled() && |
|
407 !(aFrame->GetStateBits() & NS_FRAME_IS_NONDISPLAY)), |
|
408 "Should not use nsSVGIntegrationUtils on this SVG frame"); |
|
409 #endif |
|
410 |
|
411 /* SVG defines the following rendering model: |
|
412 * |
|
413 * 1. Render geometry |
|
414 * 2. Apply filter |
|
415 * 3. Apply clipping, masking, group opacity |
|
416 * |
|
417 * We follow this, but perform a couple of optimizations: |
|
418 * |
|
419 * + Use cairo's clipPath when representable natively (single object |
|
420 * clip region). |
|
421 * |
|
422 * + Merge opacity and masking if both used together. |
|
423 */ |
|
424 |
|
425 const nsIContent* content = aFrame->GetContent(); |
|
426 bool hasSVGLayout = (aFrame->GetStateBits() & NS_FRAME_SVG_LAYOUT); |
|
427 if (hasSVGLayout) { |
|
428 nsISVGChildFrame *svgChildFrame = do_QueryFrame(aFrame); |
|
429 if (!svgChildFrame || !aFrame->GetContent()->IsSVG()) { |
|
430 NS_ASSERTION(false, "why?"); |
|
431 return; |
|
432 } |
|
433 if (!static_cast<const nsSVGElement*>(content)->HasValidDimensions()) { |
|
434 return; // The SVG spec says not to draw _anything_ |
|
435 } |
|
436 } |
|
437 |
|
438 float opacity = aFrame->StyleDisplay()->mOpacity; |
|
439 if (opacity == 0.0f) { |
|
440 return; |
|
441 } |
|
442 if (opacity != 1.0f && |
|
443 hasSVGLayout && nsSVGUtils::CanOptimizeOpacity(aFrame)) { |
|
444 opacity = 1.0f; |
|
445 } |
|
446 |
|
447 /* Properties are added lazily and may have been removed by a restyle, |
|
448 so make sure all applicable ones are set again. */ |
|
449 nsIFrame* firstFrame = |
|
450 nsLayoutUtils::FirstContinuationOrIBSplitSibling(aFrame); |
|
451 nsSVGEffects::EffectProperties effectProperties = |
|
452 nsSVGEffects::GetEffectProperties(firstFrame); |
|
453 |
|
454 bool isOK = effectProperties.HasNoFilterOrHasValidFilter(); |
|
455 nsSVGClipPathFrame *clipPathFrame = effectProperties.GetClipPathFrame(&isOK); |
|
456 nsSVGMaskFrame *maskFrame = effectProperties.GetMaskFrame(&isOK); |
|
457 if (!isOK) { |
|
458 return; // Some resource is missing. We shouldn't paint anything. |
|
459 } |
|
460 |
|
461 bool isTrivialClip = clipPathFrame ? clipPathFrame->IsTrivial() : true; |
|
462 |
|
463 gfxContext* gfx = aCtx->ThebesContext(); |
|
464 gfxContextMatrixAutoSaveRestore matrixAutoSaveRestore(gfx); |
|
465 |
|
466 nsPoint firstFrameOffset = GetOffsetToBoundingBox(firstFrame); |
|
467 nsPoint offsetToBoundingBox = aBuilder->ToReferenceFrame(firstFrame) - firstFrameOffset; |
|
468 if (!firstFrame->IsFrameOfType(nsIFrame::eSVG)) { |
|
469 /* Snap the offset if the reference frame is not a SVG frame, |
|
470 * since other frames will be snapped to pixel when rendering. */ |
|
471 offsetToBoundingBox = nsPoint( |
|
472 aFrame->PresContext()->RoundAppUnitsToNearestDevPixels(offsetToBoundingBox.x), |
|
473 aFrame->PresContext()->RoundAppUnitsToNearestDevPixels(offsetToBoundingBox.y)); |
|
474 } |
|
475 |
|
476 // After applying only "offsetToBoundingBox", aCtx would have its origin at |
|
477 // the top left corner of aFrame's bounding box (over all continuations). |
|
478 // However, SVG painting needs the origin to be located at the origin of the |
|
479 // SVG frame's "user space", i.e. the space in which, for example, the |
|
480 // frame's BBox lives. |
|
481 // SVG geometry frames and foreignObject frames apply their own offsets, so |
|
482 // their position is relative to their user space. So for these frame types, |
|
483 // if we want aCtx to be in user space, we first need to subtract the |
|
484 // frame's position so that SVG painting can later add it again and the |
|
485 // frame is painted in the right place. |
|
486 |
|
487 gfxPoint toUserSpaceGfx = nsSVGUtils::FrameSpaceInCSSPxToUserSpaceOffset(aFrame); |
|
488 nsPoint toUserSpace(nsPresContext::CSSPixelsToAppUnits(float(toUserSpaceGfx.x)), |
|
489 nsPresContext::CSSPixelsToAppUnits(float(toUserSpaceGfx.y))); |
|
490 nsPoint offsetToUserSpace = offsetToBoundingBox - toUserSpace; |
|
491 |
|
492 NS_ASSERTION(hasSVGLayout || offsetToBoundingBox == offsetToUserSpace, |
|
493 "For non-SVG frames there shouldn't be any additional offset"); |
|
494 |
|
495 aCtx->Translate(offsetToUserSpace); |
|
496 |
|
497 gfxMatrix cssPxToDevPxMatrix = GetCSSPxToDevPxMatrix(aFrame); |
|
498 |
|
499 bool complexEffects = false; |
|
500 /* Check if we need to do additional operations on this child's |
|
501 * rendering, which necessitates rendering into another surface. */ |
|
502 if (opacity != 1.0f || maskFrame || (clipPathFrame && !isTrivialClip) |
|
503 || aFrame->StyleDisplay()->mMixBlendMode != NS_STYLE_BLEND_NORMAL) { |
|
504 complexEffects = true; |
|
505 gfx->Save(); |
|
506 aCtx->IntersectClip(aFrame->GetVisualOverflowRectRelativeToSelf() + |
|
507 toUserSpace); |
|
508 gfx->PushGroup(gfxContentType::COLOR_ALPHA); |
|
509 } |
|
510 |
|
511 /* If this frame has only a trivial clipPath, set up cairo's clipping now so |
|
512 * we can just do normal painting and get it clipped appropriately. |
|
513 */ |
|
514 if (clipPathFrame && isTrivialClip) { |
|
515 gfx->Save(); |
|
516 clipPathFrame->ClipPaint(aCtx, aFrame, cssPxToDevPxMatrix); |
|
517 } |
|
518 |
|
519 /* Paint the child */ |
|
520 if (effectProperties.HasValidFilter()) { |
|
521 RegularFramePaintCallback callback(aBuilder, aLayerManager, |
|
522 offsetToUserSpace); |
|
523 |
|
524 nsRegion dirtyRegion = aDirtyRect - offsetToBoundingBox; |
|
525 nsFilterInstance::PaintFilteredFrame(aCtx, aFrame, &callback, &dirtyRegion); |
|
526 } else { |
|
527 gfx->SetMatrix(matrixAutoSaveRestore.Matrix()); |
|
528 aLayerManager->EndTransaction(FrameLayerBuilder::DrawThebesLayer, aBuilder); |
|
529 aCtx->Translate(offsetToUserSpace); |
|
530 } |
|
531 |
|
532 if (clipPathFrame && isTrivialClip) { |
|
533 gfx->Restore(); |
|
534 } |
|
535 |
|
536 /* No more effects, we're done. */ |
|
537 if (!complexEffects) { |
|
538 return; |
|
539 } |
|
540 |
|
541 gfx->PopGroupToSource(); |
|
542 |
|
543 nsRefPtr<gfxPattern> maskSurface = |
|
544 maskFrame ? maskFrame->ComputeMaskAlpha(aCtx, aFrame, |
|
545 cssPxToDevPxMatrix, opacity) : nullptr; |
|
546 |
|
547 nsRefPtr<gfxPattern> clipMaskSurface; |
|
548 if (clipPathFrame && !isTrivialClip) { |
|
549 gfx->PushGroup(gfxContentType::COLOR_ALPHA); |
|
550 |
|
551 nsresult rv = clipPathFrame->ClipPaint(aCtx, aFrame, cssPxToDevPxMatrix); |
|
552 clipMaskSurface = gfx->PopGroup(); |
|
553 |
|
554 if (NS_SUCCEEDED(rv) && clipMaskSurface) { |
|
555 // Still more set after clipping, so clip to another surface |
|
556 if (maskSurface || opacity != 1.0f) { |
|
557 gfx->PushGroup(gfxContentType::COLOR_ALPHA); |
|
558 gfx->Mask(clipMaskSurface); |
|
559 gfx->PopGroupToSource(); |
|
560 } else { |
|
561 gfx->Mask(clipMaskSurface); |
|
562 } |
|
563 } |
|
564 } |
|
565 |
|
566 if (maskSurface) { |
|
567 gfx->Mask(maskSurface); |
|
568 } else if (opacity != 1.0f) { |
|
569 gfx->Paint(opacity); |
|
570 } |
|
571 |
|
572 gfx->Restore(); |
|
573 } |
|
574 |
|
575 gfxMatrix |
|
576 nsSVGIntegrationUtils::GetCSSPxToDevPxMatrix(nsIFrame* aNonSVGFrame) |
|
577 { |
|
578 int32_t appUnitsPerDevPixel = aNonSVGFrame->PresContext()->AppUnitsPerDevPixel(); |
|
579 float devPxPerCSSPx = |
|
580 1 / nsPresContext::AppUnitsToFloatCSSPixels(appUnitsPerDevPixel); |
|
581 |
|
582 return gfxMatrix(devPxPerCSSPx, 0.0, |
|
583 0.0, devPxPerCSSPx, |
|
584 0.0, 0.0); |
|
585 } |
|
586 |
|
587 class PaintFrameCallback : public gfxDrawingCallback { |
|
588 public: |
|
589 PaintFrameCallback(nsIFrame* aFrame, |
|
590 const nsSize aPaintServerSize, |
|
591 const gfxIntSize aRenderSize, |
|
592 uint32_t aFlags) |
|
593 : mFrame(aFrame) |
|
594 , mPaintServerSize(aPaintServerSize) |
|
595 , mRenderSize(aRenderSize) |
|
596 , mFlags (aFlags) |
|
597 {} |
|
598 virtual bool operator()(gfxContext* aContext, |
|
599 const gfxRect& aFillRect, |
|
600 const GraphicsFilter& aFilter, |
|
601 const gfxMatrix& aTransform) MOZ_OVERRIDE; |
|
602 private: |
|
603 nsIFrame* mFrame; |
|
604 nsSize mPaintServerSize; |
|
605 gfxIntSize mRenderSize; |
|
606 uint32_t mFlags; |
|
607 }; |
|
608 |
|
609 bool |
|
610 PaintFrameCallback::operator()(gfxContext* aContext, |
|
611 const gfxRect& aFillRect, |
|
612 const GraphicsFilter& aFilter, |
|
613 const gfxMatrix& aTransform) |
|
614 { |
|
615 if (mFrame->GetStateBits() & NS_FRAME_DRAWING_AS_PAINTSERVER) |
|
616 return false; |
|
617 |
|
618 mFrame->AddStateBits(NS_FRAME_DRAWING_AS_PAINTSERVER); |
|
619 |
|
620 nsRefPtr<nsRenderingContext> context(new nsRenderingContext()); |
|
621 context->Init(mFrame->PresContext()->DeviceContext(), aContext); |
|
622 aContext->Save(); |
|
623 |
|
624 // Clip to aFillRect so that we don't paint outside. |
|
625 aContext->NewPath(); |
|
626 aContext->Rectangle(aFillRect); |
|
627 aContext->Clip(); |
|
628 |
|
629 aContext->Multiply(gfxMatrix(aTransform).Invert()); |
|
630 |
|
631 // nsLayoutUtils::PaintFrame will anchor its painting at mFrame. But we want |
|
632 // to have it anchored at the top left corner of the bounding box of all of |
|
633 // mFrame's continuations. So we add a translation transform. |
|
634 int32_t appUnitsPerDevPixel = mFrame->PresContext()->AppUnitsPerDevPixel(); |
|
635 nsPoint offset = GetOffsetToBoundingBox(mFrame); |
|
636 gfxPoint devPxOffset = gfxPoint(offset.x, offset.y) / appUnitsPerDevPixel; |
|
637 aContext->Multiply(gfxMatrix().Translate(devPxOffset)); |
|
638 |
|
639 gfxSize paintServerSize = |
|
640 gfxSize(mPaintServerSize.width, mPaintServerSize.height) / |
|
641 mFrame->PresContext()->AppUnitsPerDevPixel(); |
|
642 |
|
643 // nsLayoutUtils::PaintFrame wants to render with paintServerSize, but we |
|
644 // want it to render with mRenderSize, so we need to set up a scale transform. |
|
645 gfxFloat scaleX = mRenderSize.width / paintServerSize.width; |
|
646 gfxFloat scaleY = mRenderSize.height / paintServerSize.height; |
|
647 gfxMatrix scaleMatrix = gfxMatrix().Scale(scaleX, scaleY); |
|
648 aContext->Multiply(scaleMatrix); |
|
649 |
|
650 // Draw. |
|
651 nsRect dirty(-offset.x, -offset.y, |
|
652 mPaintServerSize.width, mPaintServerSize.height); |
|
653 |
|
654 uint32_t flags = nsLayoutUtils::PAINT_IN_TRANSFORM | |
|
655 nsLayoutUtils::PAINT_ALL_CONTINUATIONS; |
|
656 if (mFlags & nsSVGIntegrationUtils::FLAG_SYNC_DECODE_IMAGES) { |
|
657 flags |= nsLayoutUtils::PAINT_SYNC_DECODE_IMAGES; |
|
658 } |
|
659 nsLayoutUtils::PaintFrame(context, mFrame, |
|
660 dirty, NS_RGBA(0, 0, 0, 0), |
|
661 flags); |
|
662 |
|
663 aContext->Restore(); |
|
664 |
|
665 mFrame->RemoveStateBits(NS_FRAME_DRAWING_AS_PAINTSERVER); |
|
666 |
|
667 return true; |
|
668 } |
|
669 |
|
670 /* static */ already_AddRefed<gfxDrawable> |
|
671 nsSVGIntegrationUtils::DrawableFromPaintServer(nsIFrame* aFrame, |
|
672 nsIFrame* aTarget, |
|
673 const nsSize& aPaintServerSize, |
|
674 const gfxIntSize& aRenderSize, |
|
675 const gfxMatrix& aContextMatrix, |
|
676 uint32_t aFlags) |
|
677 { |
|
678 // aPaintServerSize is the size that would be filled when using |
|
679 // background-repeat:no-repeat and background-size:auto. For normal background |
|
680 // images, this would be the intrinsic size of the image; for gradients and |
|
681 // patterns this would be the whole target frame fill area. |
|
682 // aRenderSize is what we will be actually filling after accounting for |
|
683 // background-size. |
|
684 if (aFrame->IsFrameOfType(nsIFrame::eSVGPaintServer)) { |
|
685 // aFrame is either a pattern or a gradient. These fill the whole target |
|
686 // frame by default, so aPaintServerSize is the whole target background fill |
|
687 // area. |
|
688 nsSVGPaintServerFrame* server = |
|
689 static_cast<nsSVGPaintServerFrame*>(aFrame); |
|
690 |
|
691 gfxRect overrideBounds(0, 0, |
|
692 aPaintServerSize.width, aPaintServerSize.height); |
|
693 overrideBounds.ScaleInverse(aFrame->PresContext()->AppUnitsPerDevPixel()); |
|
694 nsRefPtr<gfxPattern> pattern = |
|
695 server->GetPaintServerPattern(aTarget, aContextMatrix, |
|
696 &nsStyleSVG::mFill, 1.0, &overrideBounds); |
|
697 |
|
698 if (!pattern) |
|
699 return nullptr; |
|
700 |
|
701 // pattern is now set up to fill aPaintServerSize. But we want it to |
|
702 // fill aRenderSize, so we need to add a scaling transform. |
|
703 // We couldn't just have set overrideBounds to aRenderSize - it would have |
|
704 // worked for gradients, but for patterns it would result in a different |
|
705 // pattern size. |
|
706 gfxFloat scaleX = overrideBounds.Width() / aRenderSize.width; |
|
707 gfxFloat scaleY = overrideBounds.Height() / aRenderSize.height; |
|
708 gfxMatrix scaleMatrix = gfxMatrix().Scale(scaleX, scaleY); |
|
709 pattern->SetMatrix(scaleMatrix.Multiply(pattern->GetMatrix())); |
|
710 nsRefPtr<gfxDrawable> drawable = |
|
711 new gfxPatternDrawable(pattern, aRenderSize); |
|
712 return drawable.forget(); |
|
713 } |
|
714 |
|
715 // We don't want to paint into a surface as long as we don't need to, so we |
|
716 // set up a drawing callback. |
|
717 nsRefPtr<gfxDrawingCallback> cb = |
|
718 new PaintFrameCallback(aFrame, aPaintServerSize, aRenderSize, aFlags); |
|
719 nsRefPtr<gfxDrawable> drawable = new gfxCallbackDrawable(cb, aRenderSize); |
|
720 return drawable.forget(); |
|
721 } |