/src/mozilla-central/layout/svg/nsSVGUtils.cpp
Line | Count | Source (jump to first uncovered line) |
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/. */ |
6 | | |
7 | | // Main header first: |
8 | | // This is also necessary to ensure our definition of M_SQRT1_2 is picked up |
9 | | #include "nsSVGUtils.h" |
10 | | #include <algorithm> |
11 | | |
12 | | // Keep others in (case-insensitive) order: |
13 | | #include "gfx2DGlue.h" |
14 | | #include "gfxContext.h" |
15 | | #include "gfxMatrix.h" |
16 | | #include "gfxPlatform.h" |
17 | | #include "gfxRect.h" |
18 | | #include "gfxUtils.h" |
19 | | #include "mozilla/gfx/2D.h" |
20 | | #include "mozilla/gfx/PatternHelpers.h" |
21 | | #include "mozilla/Preferences.h" |
22 | | #include "mozilla/SVGContextPaint.h" |
23 | | #include "nsCSSClipPathInstance.h" |
24 | | #include "nsCSSFrameConstructor.h" |
25 | | #include "nsDisplayList.h" |
26 | | #include "nsFilterInstance.h" |
27 | | #include "nsFrameList.h" |
28 | | #include "nsGkAtoms.h" |
29 | | #include "nsIContent.h" |
30 | | #include "nsIDocument.h" |
31 | | #include "nsIFrame.h" |
32 | | #include "nsIPresShell.h" |
33 | | #include "nsSVGDisplayableFrame.h" |
34 | | #include "nsLayoutUtils.h" |
35 | | #include "nsPresContext.h" |
36 | | #include "nsStyleCoord.h" |
37 | | #include "nsStyleStruct.h" |
38 | | #include "nsSVGClipPathFrame.h" |
39 | | #include "nsSVGContainerFrame.h" |
40 | | #include "SVGObserverUtils.h" |
41 | | #include "nsSVGFilterPaintCallback.h" |
42 | | #include "nsSVGForeignObjectFrame.h" |
43 | | #include "nsSVGInnerSVGFrame.h" |
44 | | #include "nsSVGIntegrationUtils.h" |
45 | | #include "nsSVGLength2.h" |
46 | | #include "nsSVGMaskFrame.h" |
47 | | #include "nsSVGOuterSVGFrame.h" |
48 | | #include "mozilla/dom/SVGClipPathElement.h" |
49 | | #include "mozilla/dom/SVGPathElement.h" |
50 | | #include "mozilla/dom/SVGUnitTypesBinding.h" |
51 | | #include "SVGGeometryElement.h" |
52 | | #include "SVGGeometryFrame.h" |
53 | | #include "nsSVGPaintServerFrame.h" |
54 | | #include "mozilla/dom/SVGViewportElement.h" |
55 | | #include "nsTextFrame.h" |
56 | | #include "SVGContentUtils.h" |
57 | | #include "SVGTextFrame.h" |
58 | | #include "mozilla/Unused.h" |
59 | | |
60 | | using namespace mozilla; |
61 | | using namespace mozilla::dom; |
62 | | using namespace mozilla::dom::SVGUnitTypes_Binding; |
63 | | using namespace mozilla::gfx; |
64 | | using namespace mozilla::image; |
65 | | |
66 | | static bool sSVGDisplayListHitTestingEnabled; |
67 | | static bool sSVGDisplayListPaintingEnabled; |
68 | | static bool sSVGNewGetBBoxEnabled; |
69 | | |
70 | | bool |
71 | | NS_SVGDisplayListHitTestingEnabled() |
72 | 0 | { |
73 | 0 | return sSVGDisplayListHitTestingEnabled; |
74 | 0 | } |
75 | | |
76 | | bool |
77 | | NS_SVGDisplayListPaintingEnabled() |
78 | 0 | { |
79 | 0 | return sSVGDisplayListPaintingEnabled; |
80 | 0 | } |
81 | | |
82 | | bool |
83 | | NS_SVGNewGetBBoxEnabled() |
84 | 0 | { |
85 | 0 | return sSVGNewGetBBoxEnabled; |
86 | 0 | } |
87 | | |
88 | | |
89 | | // we only take the address of this: |
90 | | static mozilla::gfx::UserDataKey sSVGAutoRenderStateKey; |
91 | | |
92 | | SVGAutoRenderState::SVGAutoRenderState(DrawTarget* aDrawTarget |
93 | | MOZ_GUARD_OBJECT_NOTIFIER_PARAM_IN_IMPL) |
94 | | : mDrawTarget(aDrawTarget) |
95 | | , mOriginalRenderState(nullptr) |
96 | | , mPaintingToWindow(false) |
97 | 0 | { |
98 | 0 | MOZ_GUARD_OBJECT_NOTIFIER_INIT; |
99 | 0 | mOriginalRenderState = |
100 | 0 | aDrawTarget->RemoveUserData(&sSVGAutoRenderStateKey); |
101 | 0 | // We always remove ourselves from aContext before it dies, so |
102 | 0 | // passing nullptr as the destroy function is okay. |
103 | 0 | aDrawTarget->AddUserData(&sSVGAutoRenderStateKey, this, nullptr); |
104 | 0 | } |
105 | | |
106 | | SVGAutoRenderState::~SVGAutoRenderState() |
107 | 0 | { |
108 | 0 | mDrawTarget->RemoveUserData(&sSVGAutoRenderStateKey); |
109 | 0 | if (mOriginalRenderState) { |
110 | 0 | mDrawTarget->AddUserData(&sSVGAutoRenderStateKey, |
111 | 0 | mOriginalRenderState, nullptr); |
112 | 0 | } |
113 | 0 | } |
114 | | |
115 | | void |
116 | | SVGAutoRenderState::SetPaintingToWindow(bool aPaintingToWindow) |
117 | 0 | { |
118 | 0 | mPaintingToWindow = aPaintingToWindow; |
119 | 0 | } |
120 | | |
121 | | /* static */ bool |
122 | | SVGAutoRenderState::IsPaintingToWindow(DrawTarget* aDrawTarget) |
123 | 0 | { |
124 | 0 | void *state = aDrawTarget->GetUserData(&sSVGAutoRenderStateKey); |
125 | 0 | if (state) { |
126 | 0 | return static_cast<SVGAutoRenderState*>(state)->mPaintingToWindow; |
127 | 0 | } |
128 | 0 | return false; |
129 | 0 | } |
130 | | |
131 | | void |
132 | | nsSVGUtils::Init() |
133 | 3 | { |
134 | 3 | Preferences::AddBoolVarCache(&sSVGDisplayListHitTestingEnabled, |
135 | 3 | "svg.display-lists.hit-testing.enabled"); |
136 | 3 | |
137 | 3 | Preferences::AddBoolVarCache(&sSVGDisplayListPaintingEnabled, |
138 | 3 | "svg.display-lists.painting.enabled"); |
139 | 3 | |
140 | 3 | Preferences::AddBoolVarCache(&sSVGNewGetBBoxEnabled, |
141 | 3 | "svg.new-getBBox.enabled"); |
142 | 3 | } |
143 | | |
144 | | nsRect |
145 | | nsSVGUtils::GetPostFilterVisualOverflowRect(nsIFrame *aFrame, |
146 | | const nsRect &aPreFilterRect) |
147 | 0 | { |
148 | 0 | MOZ_ASSERT(aFrame->GetStateBits() & NS_FRAME_SVG_LAYOUT, |
149 | 0 | "Called on invalid frame type"); |
150 | 0 |
|
151 | 0 | SVGFilterObserverListForCSSProp* observers = |
152 | 0 | SVGObserverUtils::GetFilterObserverList(aFrame); |
153 | 0 | if (!observers || !observers->ReferencesValidResources()) { |
154 | 0 | return aPreFilterRect; |
155 | 0 | } |
156 | 0 | |
157 | 0 | return nsFilterInstance::GetPostFilterBounds(aFrame, nullptr, &aPreFilterRect); |
158 | 0 | } |
159 | | |
160 | | bool |
161 | | nsSVGUtils::OuterSVGIsCallingReflowSVG(nsIFrame *aFrame) |
162 | 0 | { |
163 | 0 | return GetOuterSVGFrame(aFrame)->IsCallingReflowSVG(); |
164 | 0 | } |
165 | | |
166 | | bool |
167 | | nsSVGUtils::AnyOuterSVGIsCallingReflowSVG(nsIFrame* aFrame) |
168 | 0 | { |
169 | 0 | nsSVGOuterSVGFrame* outer = GetOuterSVGFrame(aFrame); |
170 | 0 | do { |
171 | 0 | if (outer->IsCallingReflowSVG()) { |
172 | 0 | return true; |
173 | 0 | } |
174 | 0 | outer = GetOuterSVGFrame(outer->GetParent()); |
175 | 0 | } while (outer); |
176 | 0 | return false; |
177 | 0 | } |
178 | | |
179 | | void |
180 | | nsSVGUtils::ScheduleReflowSVG(nsIFrame *aFrame) |
181 | 0 | { |
182 | 0 | MOZ_ASSERT(aFrame->IsFrameOfType(nsIFrame::eSVG), |
183 | 0 | "Passed bad frame!"); |
184 | 0 |
|
185 | 0 | // If this is triggered, the callers should be fixed to call us before |
186 | 0 | // ReflowSVG is called. If we try to mark dirty bits on frames while we're |
187 | 0 | // in the process of removing them, things will get messed up. |
188 | 0 | NS_ASSERTION(!OuterSVGIsCallingReflowSVG(aFrame), |
189 | 0 | "Do not call under nsSVGDisplayableFrame::ReflowSVG!"); |
190 | 0 |
|
191 | 0 | // We don't call SVGObserverUtils::InvalidateRenderingObservers here because |
192 | 0 | // we should only be called under InvalidateAndScheduleReflowSVG (which |
193 | 0 | // calls InvalidateBounds) or nsSVGDisplayContainerFrame::InsertFrames |
194 | 0 | // (at which point the frame has no observers). |
195 | 0 |
|
196 | 0 | if (aFrame->GetStateBits() & NS_FRAME_IS_NONDISPLAY) { |
197 | 0 | return; |
198 | 0 | } |
199 | 0 | |
200 | 0 | if (aFrame->GetStateBits() & |
201 | 0 | (NS_FRAME_IS_DIRTY | NS_FRAME_FIRST_REFLOW)) { |
202 | 0 | // Nothing to do if we're already dirty, or if the outer-<svg> |
203 | 0 | // hasn't yet had its initial reflow. |
204 | 0 | return; |
205 | 0 | } |
206 | 0 | |
207 | 0 | nsSVGOuterSVGFrame *outerSVGFrame = nullptr; |
208 | 0 |
|
209 | 0 | // We must not add dirty bits to the nsSVGOuterSVGFrame or else |
210 | 0 | // PresShell::FrameNeedsReflow won't work when we pass it in below. |
211 | 0 | if (aFrame->GetStateBits() & NS_STATE_IS_OUTER_SVG) { |
212 | 0 | outerSVGFrame = static_cast<nsSVGOuterSVGFrame*>(aFrame); |
213 | 0 | } else { |
214 | 0 | aFrame->AddStateBits(NS_FRAME_IS_DIRTY); |
215 | 0 |
|
216 | 0 | nsIFrame *f = aFrame->GetParent(); |
217 | 0 | while (f && !(f->GetStateBits() & NS_STATE_IS_OUTER_SVG)) { |
218 | 0 | if (f->GetStateBits() & |
219 | 0 | (NS_FRAME_IS_DIRTY | NS_FRAME_HAS_DIRTY_CHILDREN)) { |
220 | 0 | return; |
221 | 0 | } |
222 | 0 | f->AddStateBits(NS_FRAME_HAS_DIRTY_CHILDREN); |
223 | 0 | f = f->GetParent(); |
224 | 0 | MOZ_ASSERT(f->IsFrameOfType(nsIFrame::eSVG), |
225 | 0 | "NS_STATE_IS_OUTER_SVG check above not valid!"); |
226 | 0 | } |
227 | 0 |
|
228 | 0 | outerSVGFrame = static_cast<nsSVGOuterSVGFrame*>(f); |
229 | 0 |
|
230 | 0 | MOZ_ASSERT(outerSVGFrame && outerSVGFrame->IsSVGOuterSVGFrame(), |
231 | 0 | "Did not find nsSVGOuterSVGFrame!"); |
232 | 0 | } |
233 | 0 |
|
234 | 0 | if (outerSVGFrame->GetStateBits() & NS_FRAME_IN_REFLOW) { |
235 | 0 | // We're currently under an nsSVGOuterSVGFrame::Reflow call so there is no |
236 | 0 | // need to call PresShell::FrameNeedsReflow, since we have an |
237 | 0 | // nsSVGOuterSVGFrame::DidReflow call pending. |
238 | 0 | return; |
239 | 0 | } |
240 | 0 | |
241 | 0 | nsFrameState dirtyBit = |
242 | 0 | (outerSVGFrame == aFrame ? NS_FRAME_IS_DIRTY : NS_FRAME_HAS_DIRTY_CHILDREN); |
243 | 0 |
|
244 | 0 | aFrame->PresShell()->FrameNeedsReflow( |
245 | 0 | outerSVGFrame, nsIPresShell::eResize, dirtyBit); |
246 | 0 | } |
247 | | |
248 | | bool |
249 | | nsSVGUtils::NeedsReflowSVG(nsIFrame *aFrame) |
250 | 0 | { |
251 | 0 | MOZ_ASSERT(aFrame->IsFrameOfType(nsIFrame::eSVG), |
252 | 0 | "SVG uses bits differently!"); |
253 | 0 |
|
254 | 0 | // The flags we test here may change, hence why we have this separate |
255 | 0 | // function. |
256 | 0 | return NS_SUBTREE_DIRTY(aFrame); |
257 | 0 | } |
258 | | |
259 | | void |
260 | | nsSVGUtils::NotifyAncestorsOfFilterRegionChange(nsIFrame *aFrame) |
261 | 0 | { |
262 | 0 | MOZ_ASSERT(!(aFrame->GetStateBits() & NS_STATE_IS_OUTER_SVG), |
263 | 0 | "Not expecting to be called on the outer SVG Frame"); |
264 | 0 |
|
265 | 0 | aFrame = aFrame->GetParent(); |
266 | 0 |
|
267 | 0 | while (aFrame) { |
268 | 0 | if (aFrame->GetStateBits() & NS_STATE_IS_OUTER_SVG) |
269 | 0 | return; |
270 | 0 | |
271 | 0 | SVGFilterObserverListForCSSProp* observers = |
272 | 0 | SVGObserverUtils::GetFilterObserverList(aFrame); |
273 | 0 | if (observers) { |
274 | 0 | observers->Invalidate(); |
275 | 0 | } |
276 | 0 | aFrame = aFrame->GetParent(); |
277 | 0 | } |
278 | 0 | } |
279 | | |
280 | | Size |
281 | | nsSVGUtils::GetContextSize(const nsIFrame* aFrame) |
282 | 0 | { |
283 | 0 | Size size; |
284 | 0 |
|
285 | 0 | MOZ_ASSERT(aFrame->GetContent()->IsSVGElement(), "bad cast"); |
286 | 0 | const nsSVGElement* element = static_cast<nsSVGElement*>(aFrame->GetContent()); |
287 | 0 |
|
288 | 0 | SVGViewportElement* ctx = element->GetCtx(); |
289 | 0 | if (ctx) { |
290 | 0 | size.width = ctx->GetLength(SVGContentUtils::X); |
291 | 0 | size.height = ctx->GetLength(SVGContentUtils::Y); |
292 | 0 | } |
293 | 0 | return size; |
294 | 0 | } |
295 | | |
296 | | float |
297 | | nsSVGUtils::ObjectSpace(const gfxRect &aRect, const nsSVGLength2 *aLength) |
298 | 0 | { |
299 | 0 | float axis; |
300 | 0 |
|
301 | 0 | switch (aLength->GetCtxType()) { |
302 | 0 | case SVGContentUtils::X: |
303 | 0 | axis = aRect.Width(); |
304 | 0 | break; |
305 | 0 | case SVGContentUtils::Y: |
306 | 0 | axis = aRect.Height(); |
307 | 0 | break; |
308 | 0 | case SVGContentUtils::XY: |
309 | 0 | axis = float(SVGContentUtils::ComputeNormalizedHypotenuse( |
310 | 0 | aRect.Width(), aRect.Height())); |
311 | 0 | break; |
312 | 0 | default: |
313 | 0 | MOZ_ASSERT_UNREACHABLE("unexpected ctx type"); |
314 | 0 | axis = 0.0f; |
315 | 0 | break; |
316 | 0 | } |
317 | 0 | if (aLength->IsPercentage()) { |
318 | 0 | // Multiply first to avoid precision errors: |
319 | 0 | return axis * aLength->GetAnimValInSpecifiedUnits() / 100; |
320 | 0 | } |
321 | 0 | return aLength->GetAnimValue(static_cast<SVGViewportElement*>(nullptr)) * axis; |
322 | 0 | } |
323 | | |
324 | | float |
325 | | nsSVGUtils::UserSpace(nsSVGElement *aSVGElement, const nsSVGLength2 *aLength) |
326 | 0 | { |
327 | 0 | return aLength->GetAnimValue(aSVGElement); |
328 | 0 | } |
329 | | |
330 | | float |
331 | | nsSVGUtils::UserSpace(nsIFrame *aNonSVGContext, const nsSVGLength2 *aLength) |
332 | 0 | { |
333 | 0 | return aLength->GetAnimValue(aNonSVGContext); |
334 | 0 | } |
335 | | |
336 | | float |
337 | | nsSVGUtils::UserSpace(const UserSpaceMetrics& aMetrics, const nsSVGLength2 *aLength) |
338 | 0 | { |
339 | 0 | return aLength->GetAnimValue(aMetrics); |
340 | 0 | } |
341 | | |
342 | | nsSVGOuterSVGFrame * |
343 | | nsSVGUtils::GetOuterSVGFrame(nsIFrame *aFrame) |
344 | 0 | { |
345 | 0 | while (aFrame) { |
346 | 0 | if (aFrame->GetStateBits() & NS_STATE_IS_OUTER_SVG) { |
347 | 0 | return static_cast<nsSVGOuterSVGFrame*>(aFrame); |
348 | 0 | } |
349 | 0 | aFrame = aFrame->GetParent(); |
350 | 0 | } |
351 | 0 |
|
352 | 0 | return nullptr; |
353 | 0 | } |
354 | | |
355 | | nsIFrame* |
356 | | nsSVGUtils::GetOuterSVGFrameAndCoveredRegion(nsIFrame* aFrame, nsRect* aRect) |
357 | 0 | { |
358 | 0 | nsSVGDisplayableFrame* svg = do_QueryFrame(aFrame); |
359 | 0 | if (!svg) |
360 | 0 | return nullptr; |
361 | 0 | nsSVGOuterSVGFrame* outer = GetOuterSVGFrame(aFrame); |
362 | 0 | if (outer == svg) { |
363 | 0 | return nullptr; |
364 | 0 | } |
365 | 0 | |
366 | 0 | if (aFrame->GetStateBits() & NS_FRAME_IS_NONDISPLAY) { |
367 | 0 | *aRect = nsRect(0, 0, 0, 0); |
368 | 0 | } else { |
369 | 0 | uint32_t flags = nsSVGUtils::eForGetClientRects | |
370 | 0 | nsSVGUtils::eBBoxIncludeFill | |
371 | 0 | nsSVGUtils::eBBoxIncludeStroke | |
372 | 0 | nsSVGUtils::eBBoxIncludeMarkers; |
373 | 0 | gfxMatrix m = nsSVGUtils::GetUserToCanvasTM(aFrame); |
374 | 0 | SVGBBox bbox = nsSVGUtils::GetBBox(aFrame, flags, &m); |
375 | 0 | nsRect bounds = |
376 | 0 | nsLayoutUtils::RoundGfxRectToAppRect(bbox, |
377 | 0 | aFrame->PresContext()->AppUnitsPerDevPixel()); |
378 | 0 | nsMargin bp = outer->GetUsedBorderAndPadding(); |
379 | 0 | *aRect = bounds + nsPoint(bp.left, bp.top); |
380 | 0 | } |
381 | 0 |
|
382 | 0 | return outer; |
383 | 0 | } |
384 | | |
385 | | gfxMatrix |
386 | | nsSVGUtils::GetCanvasTM(nsIFrame *aFrame) |
387 | 0 | { |
388 | 0 | // XXX yuck, we really need a common interface for GetCanvasTM |
389 | 0 |
|
390 | 0 | if (!aFrame->IsFrameOfType(nsIFrame::eSVG)) { |
391 | 0 | return GetCSSPxToDevPxMatrix(aFrame); |
392 | 0 | } |
393 | 0 | |
394 | 0 | LayoutFrameType type = aFrame->Type(); |
395 | 0 | if (type == LayoutFrameType::SVGForeignObject) { |
396 | 0 | return static_cast<nsSVGForeignObjectFrame*>(aFrame)->GetCanvasTM(); |
397 | 0 | } |
398 | 0 | if (type == LayoutFrameType::SVGOuterSVG) { |
399 | 0 | return GetCSSPxToDevPxMatrix(aFrame); |
400 | 0 | } |
401 | 0 | |
402 | 0 | nsSVGContainerFrame *containerFrame = do_QueryFrame(aFrame); |
403 | 0 | if (containerFrame) { |
404 | 0 | return containerFrame->GetCanvasTM(); |
405 | 0 | } |
406 | 0 | |
407 | 0 | return static_cast<SVGGeometryFrame*>(aFrame)->GetCanvasTM(); |
408 | 0 | } |
409 | | |
410 | | gfxMatrix |
411 | | nsSVGUtils::GetUserToCanvasTM(nsIFrame *aFrame) |
412 | 0 | { |
413 | 0 | nsSVGDisplayableFrame* svgFrame = do_QueryFrame(aFrame); |
414 | 0 | NS_ASSERTION(svgFrame, "bad frame"); |
415 | 0 |
|
416 | 0 | gfxMatrix tm; |
417 | 0 | if (svgFrame) { |
418 | 0 | nsSVGElement *content = static_cast<nsSVGElement*>(aFrame->GetContent()); |
419 | 0 | tm = content->PrependLocalTransformsTo( |
420 | 0 | GetCanvasTM(aFrame->GetParent()), |
421 | 0 | eUserSpaceToParent); |
422 | 0 | } |
423 | 0 | return tm; |
424 | 0 | } |
425 | | |
426 | | void |
427 | | nsSVGUtils::NotifyChildrenOfSVGChange(nsIFrame *aFrame, uint32_t aFlags) |
428 | 0 | { |
429 | 0 | for (nsIFrame* kid : aFrame->PrincipalChildList()) { |
430 | 0 | nsSVGDisplayableFrame* SVGFrame = do_QueryFrame(kid); |
431 | 0 | if (SVGFrame) { |
432 | 0 | SVGFrame->NotifySVGChanged(aFlags); |
433 | 0 | } else { |
434 | 0 | NS_ASSERTION(kid->IsFrameOfType(nsIFrame::eSVG) || |
435 | 0 | nsSVGUtils::IsInSVGTextSubtree(kid), |
436 | 0 | "SVG frame expected"); |
437 | 0 | // recurse into the children of container frames e.g. <clipPath>, <mask> |
438 | 0 | // in case they have child frames with transformation matrices |
439 | 0 | if (kid->IsFrameOfType(nsIFrame::eSVG)) { |
440 | 0 | NotifyChildrenOfSVGChange(kid, aFlags); |
441 | 0 | } |
442 | 0 | } |
443 | 0 | } |
444 | 0 | } |
445 | | |
446 | | // ************************************************************ |
447 | | |
448 | | class SVGPaintCallback : public nsSVGFilterPaintCallback |
449 | | { |
450 | | public: |
451 | | virtual void Paint(gfxContext& aContext, nsIFrame *aTarget, |
452 | | const gfxMatrix& aTransform, |
453 | | const nsIntRect* aDirtyRect, |
454 | | imgDrawingParams& aImgParams) override |
455 | 0 | { |
456 | 0 | nsSVGDisplayableFrame* svgFrame = do_QueryFrame(aTarget); |
457 | 0 | NS_ASSERTION(svgFrame, "Expected SVG frame here"); |
458 | 0 |
|
459 | 0 | nsIntRect* dirtyRect = nullptr; |
460 | 0 | nsIntRect tmpDirtyRect; |
461 | 0 |
|
462 | 0 | // aDirtyRect is in user-space pixels, we need to convert to |
463 | 0 | // outer-SVG-frame-relative device pixels. |
464 | 0 | if (aDirtyRect) { |
465 | 0 | gfxMatrix userToDeviceSpace = aTransform; |
466 | 0 | if (userToDeviceSpace.IsSingular()) { |
467 | 0 | return; |
468 | 0 | } |
469 | 0 | gfxRect dirtyBounds = userToDeviceSpace.TransformBounds( |
470 | 0 | gfxRect(aDirtyRect->x, aDirtyRect->y, aDirtyRect->width, aDirtyRect->height)); |
471 | 0 | dirtyBounds.RoundOut(); |
472 | 0 | if (gfxUtils::GfxRectToIntRect(dirtyBounds, &tmpDirtyRect)) { |
473 | 0 | dirtyRect = &tmpDirtyRect; |
474 | 0 | } |
475 | 0 | } |
476 | 0 |
|
477 | 0 | svgFrame->PaintSVG(aContext, nsSVGUtils::GetCSSPxToDevPxMatrix(aTarget), |
478 | 0 | aImgParams, dirtyRect); |
479 | 0 | } |
480 | | }; |
481 | | |
482 | | float |
483 | | nsSVGUtils::ComputeOpacity(nsIFrame* aFrame, bool aHandleOpacity) |
484 | 0 | { |
485 | 0 | float opacity = aFrame->StyleEffects()->mOpacity; |
486 | 0 |
|
487 | 0 | if (opacity != 1.0f && |
488 | 0 | (nsSVGUtils::CanOptimizeOpacity(aFrame) || !aHandleOpacity)) { |
489 | 0 | return 1.0f; |
490 | 0 | } |
491 | 0 | |
492 | 0 | return opacity; |
493 | 0 | } |
494 | | |
495 | | void |
496 | | nsSVGUtils::DetermineMaskUsage(nsIFrame* aFrame, bool aHandleOpacity, |
497 | | MaskUsage& aUsage) |
498 | 0 | { |
499 | 0 | aUsage.opacity = ComputeOpacity(aFrame, aHandleOpacity); |
500 | 0 |
|
501 | 0 | nsIFrame* firstFrame = |
502 | 0 | nsLayoutUtils::FirstContinuationOrIBSplitSibling(aFrame); |
503 | 0 |
|
504 | 0 | SVGObserverUtils::EffectProperties effectProperties = |
505 | 0 | SVGObserverUtils::GetEffectProperties(firstFrame); |
506 | 0 | const nsStyleSVGReset *svgReset = firstFrame->StyleSVGReset(); |
507 | 0 |
|
508 | 0 | nsTArray<nsSVGMaskFrame*> maskFrames = effectProperties.GetMaskFrames(); |
509 | 0 |
|
510 | 0 | aUsage.shouldGenerateMaskLayer = (maskFrames.Length() > 0); |
511 | 0 |
|
512 | 0 | nsSVGClipPathFrame *clipPathFrame = effectProperties.GetClipPathFrame(); |
513 | 0 | MOZ_ASSERT(!clipPathFrame || |
514 | 0 | svgReset->mClipPath.GetType() == StyleShapeSourceType::URL); |
515 | 0 |
|
516 | 0 | switch (svgReset->mClipPath.GetType()) { |
517 | 0 | case StyleShapeSourceType::URL: |
518 | 0 | if (clipPathFrame) { |
519 | 0 | if (clipPathFrame->IsTrivial()) { |
520 | 0 | aUsage.shouldApplyClipPath = true; |
521 | 0 | } else { |
522 | 0 | aUsage.shouldGenerateClipMaskLayer = true; |
523 | 0 | } |
524 | 0 | } |
525 | 0 | break; |
526 | 0 | case StyleShapeSourceType::Shape: |
527 | 0 | case StyleShapeSourceType::Box: |
528 | 0 | case StyleShapeSourceType::Path: |
529 | 0 | aUsage.shouldApplyBasicShapeOrPath = true; |
530 | 0 | break; |
531 | 0 | case StyleShapeSourceType::None: |
532 | 0 | MOZ_ASSERT(!aUsage.shouldGenerateClipMaskLayer && |
533 | 0 | !aUsage.shouldApplyClipPath && |
534 | 0 | !aUsage.shouldApplyBasicShapeOrPath); |
535 | 0 | break; |
536 | 0 | default: |
537 | 0 | MOZ_ASSERT_UNREACHABLE("Unsupported clip-path type."); |
538 | 0 | break; |
539 | 0 | } |
540 | 0 | } |
541 | | |
542 | | class MixModeBlender { |
543 | | public: |
544 | | typedef mozilla::gfx::Factory Factory; |
545 | | |
546 | | MixModeBlender(nsIFrame *aFrame, gfxContext* aContext) |
547 | | : mFrame(aFrame), mSourceCtx(aContext) |
548 | 0 | { |
549 | 0 | MOZ_ASSERT(mFrame && mSourceCtx); |
550 | 0 | } |
551 | | |
552 | | bool ShouldCreateDrawTargetForBlend() const |
553 | 0 | { |
554 | 0 | return mFrame->StyleEffects()->mMixBlendMode != NS_STYLE_BLEND_NORMAL; |
555 | 0 | } |
556 | | |
557 | | gfxContext* CreateBlendTarget(const gfxMatrix& aTransform) |
558 | 0 | { |
559 | 0 | MOZ_ASSERT(ShouldCreateDrawTargetForBlend()); |
560 | 0 |
|
561 | 0 | // Create a temporary context to draw to so we can blend it back with |
562 | 0 | // another operator. |
563 | 0 | IntRect drawRect = ComputeClipExtsInDeviceSpace(aTransform); |
564 | 0 |
|
565 | 0 | RefPtr<DrawTarget> targetDT = |
566 | 0 | mSourceCtx->GetDrawTarget()->CreateSimilarDrawTarget(drawRect.Size(), |
567 | 0 | SurfaceFormat::B8G8R8A8); |
568 | 0 | if (!targetDT || !targetDT->IsValid()) { |
569 | 0 | return nullptr; |
570 | 0 | } |
571 | 0 | |
572 | 0 | MOZ_ASSERT(!mTargetCtx, |
573 | 0 | "CreateBlendTarget is designed to be used once only."); |
574 | 0 |
|
575 | 0 | mTargetCtx = gfxContext::CreateOrNull(targetDT); |
576 | 0 | MOZ_ASSERT(mTargetCtx); // already checked the draw target above |
577 | 0 | mTargetCtx->SetMatrix(mSourceCtx->CurrentMatrix() * |
578 | 0 | Matrix::Translation(-drawRect.TopLeft())); |
579 | 0 |
|
580 | 0 | mTargetOffset = drawRect.TopLeft(); |
581 | 0 |
|
582 | 0 | return mTargetCtx; |
583 | 0 | } |
584 | | |
585 | | void BlendToTarget() |
586 | 0 | { |
587 | 0 | MOZ_ASSERT(ShouldCreateDrawTargetForBlend()); |
588 | 0 | MOZ_ASSERT(mTargetCtx, |
589 | 0 | "BlendToTarget should be used after CreateBlendTarget."); |
590 | 0 |
|
591 | 0 | RefPtr<SourceSurface> targetSurf = mTargetCtx->GetDrawTarget()->Snapshot(); |
592 | 0 |
|
593 | 0 | gfxContextAutoSaveRestore save(mSourceCtx); |
594 | 0 | mSourceCtx->SetMatrix(Matrix()); // This will be restored right after. |
595 | 0 | RefPtr<gfxPattern> pattern = |
596 | 0 | new gfxPattern(targetSurf, |
597 | 0 | Matrix::Translation(mTargetOffset.x, mTargetOffset.y)); |
598 | 0 | mSourceCtx->SetPattern(pattern); |
599 | 0 | mSourceCtx->Paint(); |
600 | 0 | } |
601 | | |
602 | | private: |
603 | | MixModeBlender() = delete; |
604 | | |
605 | | IntRect ComputeClipExtsInDeviceSpace(const gfxMatrix& aTransform) |
606 | 0 | { |
607 | 0 | // These are used if we require a temporary surface for a custom blend |
608 | 0 | // mode. Clip the source context first, so that we can generate a smaller |
609 | 0 | // temporary surface. (Since we will clip this context in |
610 | 0 | // SetupContextMatrix, a pair of save/restore is needed.) |
611 | 0 | gfxContextAutoSaveRestore saver(mSourceCtx); |
612 | 0 |
|
613 | 0 | if (!(mFrame->GetStateBits() & NS_FRAME_IS_NONDISPLAY)) { |
614 | 0 | // aFrame has a valid visual overflow rect, so clip to it before calling |
615 | 0 | // PushGroup() to minimize the size of the surfaces we'll composite: |
616 | 0 | gfxContextMatrixAutoSaveRestore matrixAutoSaveRestore(mSourceCtx); |
617 | 0 | mSourceCtx->Multiply(aTransform); |
618 | 0 | nsRect overflowRect = mFrame->GetVisualOverflowRectRelativeToSelf(); |
619 | 0 | if (mFrame->IsFrameOfType(nsIFrame::eSVGGeometry) || |
620 | 0 | nsSVGUtils::IsInSVGTextSubtree(mFrame)) { |
621 | 0 | // Unlike containers, leaf frames do not include GetPosition() in |
622 | 0 | // GetCanvasTM(). |
623 | 0 | overflowRect = overflowRect + mFrame->GetPosition(); |
624 | 0 | } |
625 | 0 | mSourceCtx->Clip(NSRectToSnappedRect(overflowRect, |
626 | 0 | mFrame->PresContext()->AppUnitsPerDevPixel(), |
627 | 0 | *mSourceCtx->GetDrawTarget())); |
628 | 0 | } |
629 | 0 |
|
630 | 0 | // Get the clip extents in device space. |
631 | 0 | gfxRect clippedFrameSurfaceRect = |
632 | 0 | mSourceCtx->GetClipExtents(gfxContext::eDeviceSpace); |
633 | 0 | clippedFrameSurfaceRect.RoundOut(); |
634 | 0 |
|
635 | 0 | IntRect result; |
636 | 0 | ToRect(clippedFrameSurfaceRect).ToIntRect(&result); |
637 | 0 |
|
638 | 0 | return Factory::CheckSurfaceSize(result.Size()) ? result : IntRect(); |
639 | 0 | } |
640 | | |
641 | | nsIFrame* mFrame; |
642 | | gfxContext* mSourceCtx; |
643 | | RefPtr<gfxContext> mTargetCtx; |
644 | | IntPoint mTargetOffset; |
645 | | }; |
646 | | |
647 | | void |
648 | | nsSVGUtils::PaintFrameWithEffects(nsIFrame *aFrame, |
649 | | gfxContext& aContext, |
650 | | const gfxMatrix& aTransform, |
651 | | imgDrawingParams& aImgParams, |
652 | | const nsIntRect *aDirtyRect) |
653 | 0 | { |
654 | 0 | NS_ASSERTION(!NS_SVGDisplayListPaintingEnabled() || |
655 | 0 | (aFrame->GetStateBits() & NS_FRAME_IS_NONDISPLAY) || |
656 | 0 | aFrame->PresContext()->Document()->IsSVGGlyphsDocument(), |
657 | 0 | "If display lists are enabled, only painting of non-display " |
658 | 0 | "SVG should take this code path"); |
659 | 0 |
|
660 | 0 | nsSVGDisplayableFrame* svgFrame = do_QueryFrame(aFrame); |
661 | 0 | if (!svgFrame) |
662 | 0 | return; |
663 | 0 | |
664 | 0 | MaskUsage maskUsage; |
665 | 0 | DetermineMaskUsage(aFrame, true, maskUsage); |
666 | 0 | if (maskUsage.opacity == 0.0f) { |
667 | 0 | return; |
668 | 0 | } |
669 | 0 | |
670 | 0 | const nsIContent* content = aFrame->GetContent(); |
671 | 0 | if (content->IsSVGElement() && |
672 | 0 | !static_cast<const nsSVGElement*>(content)->HasValidDimensions()) { |
673 | 0 | return; |
674 | 0 | } |
675 | 0 | |
676 | 0 | if (aDirtyRect && |
677 | 0 | !(aFrame->GetStateBits() & NS_FRAME_IS_NONDISPLAY)) { |
678 | 0 | // Here we convert aFrame's paint bounds to outer-<svg> device space, |
679 | 0 | // compare it to aDirtyRect, and return early if they don't intersect. |
680 | 0 | // We don't do this optimization for nondisplay SVG since nondisplay |
681 | 0 | // SVG doesn't maintain bounds/overflow rects. |
682 | 0 | nsRect overflowRect = aFrame->GetVisualOverflowRectRelativeToSelf(); |
683 | 0 | if (aFrame->IsFrameOfType(nsIFrame::eSVGGeometry) || |
684 | 0 | nsSVGUtils::IsInSVGTextSubtree(aFrame)) { |
685 | 0 | // Unlike containers, leaf frames do not include GetPosition() in |
686 | 0 | // GetCanvasTM(). |
687 | 0 | overflowRect = overflowRect + aFrame->GetPosition(); |
688 | 0 | } |
689 | 0 | int32_t appUnitsPerDevPx = aFrame->PresContext()->AppUnitsPerDevPixel(); |
690 | 0 | gfxMatrix tm = aTransform; |
691 | 0 | if (aFrame->IsFrameOfType(nsIFrame::eSVG | nsIFrame::eSVGContainer)) { |
692 | 0 | gfx::Matrix childrenOnlyTM; |
693 | 0 | if (static_cast<nsSVGContainerFrame*>(aFrame)-> |
694 | 0 | HasChildrenOnlyTransform(&childrenOnlyTM)) { |
695 | 0 | // Undo the children-only transform: |
696 | 0 | if (!childrenOnlyTM.Invert()) { |
697 | 0 | return; |
698 | 0 | } |
699 | 0 | tm = ThebesMatrix(childrenOnlyTM) * tm; |
700 | 0 | } |
701 | 0 | } |
702 | 0 | nsIntRect bounds = TransformFrameRectToOuterSVG(overflowRect, |
703 | 0 | tm, aFrame->PresContext()). |
704 | 0 | ToOutsidePixels(appUnitsPerDevPx); |
705 | 0 | if (!aDirtyRect->Intersects(bounds)) { |
706 | 0 | return; |
707 | 0 | } |
708 | 0 | } |
709 | 0 | |
710 | 0 | /* SVG defines the following rendering model: |
711 | 0 | * |
712 | 0 | * 1. Render fill |
713 | 0 | * 2. Render stroke |
714 | 0 | * 3. Render markers |
715 | 0 | * 4. Apply filter |
716 | 0 | * 5. Apply clipping, masking, group opacity |
717 | 0 | * |
718 | 0 | * We follow this, but perform a couple of optimizations: |
719 | 0 | * |
720 | 0 | * + Use cairo's clipPath when representable natively (single object |
721 | 0 | * clip region). |
722 | 0 | *f |
723 | 0 | * + Merge opacity and masking if both used together. |
724 | 0 | */ |
725 | 0 | |
726 | 0 | /* Properties are added lazily and may have been removed by a restyle, |
727 | 0 | so make sure all applicable ones are set again. */ |
728 | 0 | SVGObserverUtils::EffectProperties effectProperties = |
729 | 0 | SVGObserverUtils::GetEffectProperties(aFrame); |
730 | 0 | if (effectProperties.HasInvalidEffects()) { |
731 | 0 | // Some resource is invalid. We shouldn't paint anything. |
732 | 0 | return; |
733 | 0 | } |
734 | 0 | |
735 | 0 | nsSVGClipPathFrame *clipPathFrame = effectProperties.GetClipPathFrame(); |
736 | 0 | nsTArray<nsSVGMaskFrame*> masks = effectProperties.GetMaskFrames(); |
737 | 0 | nsSVGMaskFrame *maskFrame = masks.IsEmpty() ? nullptr : masks[0]; |
738 | 0 |
|
739 | 0 | MixModeBlender blender(aFrame, &aContext); |
740 | 0 | gfxContext* target = blender.ShouldCreateDrawTargetForBlend() |
741 | 0 | ? blender.CreateBlendTarget(aTransform) : &aContext; |
742 | 0 |
|
743 | 0 | if (!target) { |
744 | 0 | return; |
745 | 0 | } |
746 | 0 | |
747 | 0 | /* Check if we need to do additional operations on this child's |
748 | 0 | * rendering, which necessitates rendering into another surface. */ |
749 | 0 | bool shouldGenerateMask = (maskUsage.opacity != 1.0f || |
750 | 0 | maskUsage.shouldGenerateClipMaskLayer || |
751 | 0 | maskUsage.shouldGenerateMaskLayer); |
752 | 0 | bool shouldPushMask = false; |
753 | 0 |
|
754 | 0 | if (shouldGenerateMask) { |
755 | 0 | Matrix maskTransform; |
756 | 0 | RefPtr<SourceSurface> maskSurface; |
757 | 0 |
|
758 | 0 | // maskFrame can be nullptr even if maskUsage.shouldGenerateMaskLayer is |
759 | 0 | // true. That happens when a user gives an unresolvable mask-id, such as |
760 | 0 | // mask:url() |
761 | 0 | // mask:url(#id-which-does-not-exist) |
762 | 0 | // Since we only uses nsSVGUtils with SVG elements, not like mask on an |
763 | 0 | // HTML element, we should treat an unresolvable mask as no-mask here. |
764 | 0 | if (maskUsage.shouldGenerateMaskLayer && maskFrame) { |
765 | 0 | uint8_t maskMode = |
766 | 0 | aFrame->StyleSVGReset()->mMask.mLayers[0].mMaskMode; |
767 | 0 | nsSVGMaskFrame::MaskParams params(&aContext, aFrame, aTransform, |
768 | 0 | maskUsage.opacity, &maskTransform, |
769 | 0 | maskMode, aImgParams); |
770 | 0 | maskSurface = maskFrame->GetMaskForMaskedFrame(params); |
771 | 0 |
|
772 | 0 | if (!maskSurface) { |
773 | 0 | // Either entire surface is clipped out, or gfx buffer allocation |
774 | 0 | // failure in nsSVGMaskFrame::GetMaskForMaskedFrame. |
775 | 0 | return; |
776 | 0 | } |
777 | 0 | shouldPushMask = true; |
778 | 0 | } |
779 | 0 |
|
780 | 0 | if (maskUsage.shouldGenerateClipMaskLayer) { |
781 | 0 | Matrix clippedMaskTransform; |
782 | 0 | RefPtr<SourceSurface> clipMaskSurface = |
783 | 0 | clipPathFrame->GetClipMask(aContext, aFrame, aTransform, |
784 | 0 | &clippedMaskTransform, maskSurface, |
785 | 0 | maskTransform); |
786 | 0 | if (clipMaskSurface) { |
787 | 0 | maskSurface = clipMaskSurface; |
788 | 0 | maskTransform = clippedMaskTransform; |
789 | 0 | } else { |
790 | 0 | // Either entire surface is clipped out, or gfx buffer allocation |
791 | 0 | // failure in nsSVGClipPathFrame::GetClipMask. |
792 | 0 | return; |
793 | 0 | } |
794 | 0 | shouldPushMask = true; |
795 | 0 | } |
796 | 0 |
|
797 | 0 | if (!maskUsage.shouldGenerateClipMaskLayer && |
798 | 0 | !maskUsage.shouldGenerateMaskLayer) { |
799 | 0 | shouldPushMask = true; |
800 | 0 | } |
801 | 0 |
|
802 | 0 | // SVG mask multiply opacity into maskSurface already, so we do not bother |
803 | 0 | // to apply opacity again. |
804 | 0 | if (shouldPushMask) { |
805 | 0 | target->PushGroupForBlendBack(gfxContentType::COLOR_ALPHA, |
806 | 0 | maskFrame ? 1.0 : maskUsage.opacity, |
807 | 0 | maskSurface, maskTransform); |
808 | 0 | } |
809 | 0 | } |
810 | 0 |
|
811 | 0 | /* If this frame has only a trivial clipPath, set up cairo's clipping now so |
812 | 0 | * we can just do normal painting and get it clipped appropriately. |
813 | 0 | */ |
814 | 0 | if (maskUsage.shouldApplyClipPath || maskUsage.shouldApplyBasicShapeOrPath) { |
815 | 0 | if (maskUsage.shouldApplyClipPath) { |
816 | 0 | clipPathFrame->ApplyClipPath(aContext, aFrame, aTransform); |
817 | 0 | } else { |
818 | 0 | nsCSSClipPathInstance::ApplyBasicShapeOrPathClip(aContext, aFrame); |
819 | 0 | } |
820 | 0 | } |
821 | 0 |
|
822 | 0 | /* Paint the child */ |
823 | 0 | if (effectProperties.HasValidFilter()) { |
824 | 0 | nsRegion* dirtyRegion = nullptr; |
825 | 0 | nsRegion tmpDirtyRegion; |
826 | 0 | if (aDirtyRect) { |
827 | 0 | // aDirtyRect is in outer-<svg> device pixels, but the filter code needs |
828 | 0 | // it in frame space. |
829 | 0 | gfxMatrix userToDeviceSpace = aTransform; |
830 | 0 | if (userToDeviceSpace.IsSingular()) { |
831 | 0 | return; |
832 | 0 | } |
833 | 0 | gfxMatrix deviceToUserSpace = userToDeviceSpace; |
834 | 0 | deviceToUserSpace.Invert(); |
835 | 0 | gfxRect dirtyBounds = deviceToUserSpace.TransformBounds( |
836 | 0 | gfxRect(aDirtyRect->x, aDirtyRect->y, |
837 | 0 | aDirtyRect->width, aDirtyRect->height)); |
838 | 0 | tmpDirtyRegion = |
839 | 0 | nsLayoutUtils::RoundGfxRectToAppRect( |
840 | 0 | dirtyBounds, AppUnitsPerCSSPixel()) - |
841 | 0 | aFrame->GetPosition(); |
842 | 0 | dirtyRegion = &tmpDirtyRegion; |
843 | 0 | } |
844 | 0 |
|
845 | 0 | gfxContextMatrixAutoSaveRestore autoSR(target); |
846 | 0 |
|
847 | 0 | // 'target' is currently scaled such that its user space units are CSS |
848 | 0 | // pixels (SVG user space units). But PaintFilteredFrame expects it to be |
849 | 0 | // scaled in such a way that its user space units are device pixels. So we |
850 | 0 | // have to adjust the scale. |
851 | 0 | gfxMatrix reverseScaleMatrix = nsSVGUtils::GetCSSPxToDevPxMatrix(aFrame); |
852 | 0 | DebugOnly<bool> invertible = reverseScaleMatrix.Invert(); |
853 | 0 | target->SetMatrixDouble(reverseScaleMatrix * aTransform * |
854 | 0 | target->CurrentMatrixDouble()); |
855 | 0 |
|
856 | 0 | SVGPaintCallback paintCallback; |
857 | 0 | nsFilterInstance::PaintFilteredFrame(aFrame, target, &paintCallback, |
858 | 0 | dirtyRegion, aImgParams); |
859 | 0 | } else { |
860 | 0 | svgFrame->PaintSVG(*target, aTransform, aImgParams, aDirtyRect); |
861 | 0 | } |
862 | 0 |
|
863 | 0 | if (maskUsage.shouldApplyClipPath || maskUsage.shouldApplyBasicShapeOrPath) { |
864 | 0 | aContext.PopClip(); |
865 | 0 | } |
866 | 0 |
|
867 | 0 | if (shouldPushMask) { |
868 | 0 | target->PopGroupAndBlend(); |
869 | 0 | } |
870 | 0 |
|
871 | 0 | if (blender.ShouldCreateDrawTargetForBlend()) { |
872 | 0 | MOZ_ASSERT(target != &aContext); |
873 | 0 | blender.BlendToTarget(); |
874 | 0 | } |
875 | 0 | } |
876 | | |
877 | | bool |
878 | | nsSVGUtils::HitTestClip(nsIFrame *aFrame, const gfxPoint &aPoint) |
879 | 0 | { |
880 | 0 | SVGObserverUtils::EffectProperties props = |
881 | 0 | SVGObserverUtils::GetEffectProperties(aFrame); |
882 | 0 | if (!props.mClipPath) { |
883 | 0 | const nsStyleSVGReset *style = aFrame->StyleSVGReset(); |
884 | 0 | if (style->HasClipPath()) { |
885 | 0 | return nsCSSClipPathInstance::HitTestBasicShapeOrPathClip(aFrame, aPoint); |
886 | 0 | } |
887 | 0 | return true; |
888 | 0 | } |
889 | 0 | |
890 | 0 | if (props.HasInvalidClipPath()) { |
891 | 0 | // clipPath is not a valid resource, so nothing gets painted, so |
892 | 0 | // hit-testing must fail. |
893 | 0 | return false; |
894 | 0 | } |
895 | 0 | nsSVGClipPathFrame *clipPathFrame = props.GetClipPathFrame(); |
896 | 0 |
|
897 | 0 | if (!clipPathFrame) { |
898 | 0 | // clipPath doesn't exist, ignore it. |
899 | 0 | return true; |
900 | 0 | } |
901 | 0 | |
902 | 0 | return clipPathFrame->PointIsInsideClipPath(aFrame, aPoint); |
903 | 0 | } |
904 | | |
905 | | nsIFrame * |
906 | | nsSVGUtils::HitTestChildren(nsSVGDisplayContainerFrame* aFrame, |
907 | | const gfxPoint& aPoint) |
908 | 0 | { |
909 | 0 | // First we transform aPoint into the coordinate space established by aFrame |
910 | 0 | // for its children (e.g. take account of any 'viewBox' attribute): |
911 | 0 | gfxPoint point = aPoint; |
912 | 0 | if (aFrame->GetContent()->IsSVGElement()) { // must check before cast |
913 | 0 | gfxMatrix m = static_cast<const nsSVGElement*>(aFrame->GetContent())-> |
914 | 0 | PrependLocalTransformsTo(gfxMatrix(), |
915 | 0 | eChildToUserSpace); |
916 | 0 | if (!m.IsIdentity()) { |
917 | 0 | if (!m.Invert()) { |
918 | 0 | return nullptr; |
919 | 0 | } |
920 | 0 | point = m.TransformPoint(point); |
921 | 0 | } |
922 | 0 | } |
923 | 0 |
|
924 | 0 | // Traverse the list in reverse order, so that if we get a hit we know that's |
925 | 0 | // the topmost frame that intersects the point; then we can just return it. |
926 | 0 | nsIFrame* result = nullptr; |
927 | 0 | for (nsIFrame* current = aFrame->PrincipalChildList().LastChild(); |
928 | 0 | current; |
929 | 0 | current = current->GetPrevSibling()) { |
930 | 0 | nsSVGDisplayableFrame* SVGFrame = do_QueryFrame(current); |
931 | 0 | if (SVGFrame) { |
932 | 0 | const nsIContent* content = current->GetContent(); |
933 | 0 | if (content->IsSVGElement() && |
934 | 0 | !static_cast<const nsSVGElement*>(content)->HasValidDimensions()) { |
935 | 0 | continue; |
936 | 0 | } |
937 | 0 | // GetFrameForPoint() expects a point in its frame's SVG user space, so |
938 | 0 | // we need to convert to that space: |
939 | 0 | gfxPoint p = point; |
940 | 0 | if (content->IsSVGElement()) { // must check before cast |
941 | 0 | gfxMatrix m = static_cast<const nsSVGElement*>(content)-> |
942 | 0 | PrependLocalTransformsTo(gfxMatrix(), |
943 | 0 | eUserSpaceToParent); |
944 | 0 | if (!m.IsIdentity()) { |
945 | 0 | if (!m.Invert()) { |
946 | 0 | continue; |
947 | 0 | } |
948 | 0 | p = m.TransformPoint(p); |
949 | 0 | } |
950 | 0 | } |
951 | 0 | result = SVGFrame->GetFrameForPoint(p); |
952 | 0 | if (result) |
953 | 0 | break; |
954 | 0 | } |
955 | 0 | } |
956 | 0 |
|
957 | 0 | if (result && !HitTestClip(aFrame, aPoint)) |
958 | 0 | result = nullptr; |
959 | 0 |
|
960 | 0 | return result; |
961 | 0 | } |
962 | | |
963 | | nsRect |
964 | | nsSVGUtils::TransformFrameRectToOuterSVG(const nsRect& aRect, |
965 | | const gfxMatrix& aMatrix, |
966 | | nsPresContext* aPresContext) |
967 | 0 | { |
968 | 0 | gfxRect r(aRect.x, aRect.y, aRect.width, aRect.height); |
969 | 0 | r.Scale(1.0 / AppUnitsPerCSSPixel()); |
970 | 0 | return nsLayoutUtils::RoundGfxRectToAppRect( |
971 | 0 | aMatrix.TransformBounds(r), aPresContext->AppUnitsPerDevPixel()); |
972 | 0 | } |
973 | | |
974 | | IntSize |
975 | | nsSVGUtils::ConvertToSurfaceSize(const gfxSize& aSize, |
976 | | bool *aResultOverflows) |
977 | 0 | { |
978 | 0 | IntSize surfaceSize(ClampToInt(ceil(aSize.width)), ClampToInt(ceil(aSize.height))); |
979 | 0 |
|
980 | 0 | *aResultOverflows = surfaceSize.width != ceil(aSize.width) || |
981 | 0 | surfaceSize.height != ceil(aSize.height); |
982 | 0 |
|
983 | 0 | if (!Factory::AllowedSurfaceSize(surfaceSize)) { |
984 | 0 | surfaceSize.width = std::min(NS_SVG_OFFSCREEN_MAX_DIMENSION, |
985 | 0 | surfaceSize.width); |
986 | 0 | surfaceSize.height = std::min(NS_SVG_OFFSCREEN_MAX_DIMENSION, |
987 | 0 | surfaceSize.height); |
988 | 0 | *aResultOverflows = true; |
989 | 0 | } |
990 | 0 |
|
991 | 0 | return surfaceSize; |
992 | 0 | } |
993 | | |
994 | | bool |
995 | | nsSVGUtils::HitTestRect(const gfx::Matrix &aMatrix, |
996 | | float aRX, float aRY, float aRWidth, float aRHeight, |
997 | | float aX, float aY) |
998 | 0 | { |
999 | 0 | gfx::Rect rect(aRX, aRY, aRWidth, aRHeight); |
1000 | 0 | if (rect.IsEmpty() || aMatrix.IsSingular()) { |
1001 | 0 | return false; |
1002 | 0 | } |
1003 | 0 | gfx::Matrix toRectSpace = aMatrix; |
1004 | 0 | toRectSpace.Invert(); |
1005 | 0 | gfx::Point p = toRectSpace.TransformPoint(gfx::Point(aX, aY)); |
1006 | 0 | return rect.x <= p.x && p.x <= rect.XMost() && |
1007 | 0 | rect.y <= p.y && p.y <= rect.YMost(); |
1008 | 0 | } |
1009 | | |
1010 | | gfxRect |
1011 | | nsSVGUtils::GetClipRectForFrame(nsIFrame *aFrame, |
1012 | | float aX, float aY, float aWidth, float aHeight) |
1013 | 0 | { |
1014 | 0 | const nsStyleDisplay* disp = aFrame->StyleDisplay(); |
1015 | 0 | const nsStyleEffects* effects = aFrame->StyleEffects(); |
1016 | 0 |
|
1017 | 0 | if (!(effects->mClipFlags & NS_STYLE_CLIP_RECT)) { |
1018 | 0 | NS_ASSERTION(effects->mClipFlags == NS_STYLE_CLIP_AUTO, |
1019 | 0 | "We don't know about this type of clip."); |
1020 | 0 | return gfxRect(aX, aY, aWidth, aHeight); |
1021 | 0 | } |
1022 | 0 |
|
1023 | 0 | if (disp->mOverflowX == NS_STYLE_OVERFLOW_HIDDEN || |
1024 | 0 | disp->mOverflowY == NS_STYLE_OVERFLOW_HIDDEN) { |
1025 | 0 |
|
1026 | 0 | nsIntRect clipPxRect = |
1027 | 0 | effects->mClip.ToOutsidePixels(aFrame->PresContext()->AppUnitsPerDevPixel()); |
1028 | 0 | gfxRect clipRect = |
1029 | 0 | gfxRect(clipPxRect.x, clipPxRect.y, clipPxRect.width, clipPxRect.height); |
1030 | 0 |
|
1031 | 0 | if (NS_STYLE_CLIP_RIGHT_AUTO & effects->mClipFlags) { |
1032 | 0 | clipRect.width = aWidth - clipRect.X(); |
1033 | 0 | } |
1034 | 0 | if (NS_STYLE_CLIP_BOTTOM_AUTO & effects->mClipFlags) { |
1035 | 0 | clipRect.height = aHeight - clipRect.Y(); |
1036 | 0 | } |
1037 | 0 |
|
1038 | 0 | if (disp->mOverflowX != NS_STYLE_OVERFLOW_HIDDEN) { |
1039 | 0 | clipRect.x = aX; |
1040 | 0 | clipRect.width = aWidth; |
1041 | 0 | } |
1042 | 0 | if (disp->mOverflowY != NS_STYLE_OVERFLOW_HIDDEN) { |
1043 | 0 | clipRect.y = aY; |
1044 | 0 | clipRect.height = aHeight; |
1045 | 0 | } |
1046 | 0 |
|
1047 | 0 | return clipRect; |
1048 | 0 | } |
1049 | 0 | return gfxRect(aX, aY, aWidth, aHeight); |
1050 | 0 | } |
1051 | | |
1052 | | void |
1053 | | nsSVGUtils::SetClipRect(gfxContext *aContext, |
1054 | | const gfxMatrix &aCTM, |
1055 | | const gfxRect &aRect) |
1056 | 0 | { |
1057 | 0 | if (aCTM.IsSingular()) |
1058 | 0 | return; |
1059 | 0 | |
1060 | 0 | gfxContextMatrixAutoSaveRestore matrixAutoSaveRestore(aContext); |
1061 | 0 | aContext->Multiply(aCTM); |
1062 | 0 | aContext->Clip(aRect); |
1063 | 0 | } |
1064 | | |
1065 | | gfxRect |
1066 | | nsSVGUtils::GetBBox(nsIFrame* aFrame, uint32_t aFlags, |
1067 | | const gfxMatrix* aToBoundsSpace) |
1068 | 0 | { |
1069 | 0 | if (aFrame->GetContent()->IsText()) { |
1070 | 0 | aFrame = aFrame->GetParent(); |
1071 | 0 | } |
1072 | 0 |
|
1073 | 0 | if (nsSVGUtils::IsInSVGTextSubtree(aFrame)) { |
1074 | 0 | // It is possible to apply a gradient, pattern, clipping path, mask or |
1075 | 0 | // filter to text. When one of these facilities is applied to text |
1076 | 0 | // the bounding box is the entire text element in all |
1077 | 0 | // cases. |
1078 | 0 | nsIFrame* ancestor = GetFirstNonAAncestorFrame(aFrame); |
1079 | 0 | if (ancestor && nsSVGUtils::IsInSVGTextSubtree(ancestor)) { |
1080 | 0 | while (!ancestor->IsSVGTextFrame()) { |
1081 | 0 | ancestor = ancestor->GetParent(); |
1082 | 0 | } |
1083 | 0 | } |
1084 | 0 | aFrame = ancestor; |
1085 | 0 | } |
1086 | 0 |
|
1087 | 0 | nsSVGDisplayableFrame* svg = do_QueryFrame(aFrame); |
1088 | 0 | const bool hasSVGLayout = aFrame->GetStateBits() & NS_FRAME_SVG_LAYOUT; |
1089 | 0 | if (hasSVGLayout && !svg) { |
1090 | 0 | // An SVG frame, but not one that can be displayed directly (for |
1091 | 0 | // example, nsGradientFrame). These can't contribute to the bbox. |
1092 | 0 | return gfxRect(); |
1093 | 0 | } |
1094 | 0 | |
1095 | 0 | const bool isOuterSVG = svg && !hasSVGLayout; |
1096 | 0 | MOZ_ASSERT(!isOuterSVG || aFrame->IsSVGOuterSVGFrame()); |
1097 | 0 | if (!svg || |
1098 | 0 | (isOuterSVG && (aFlags & eUseFrameBoundsForOuterSVG))) { |
1099 | 0 | // An HTML element or an SVG outer frame. |
1100 | 0 | MOZ_ASSERT(!hasSVGLayout); |
1101 | 0 | bool onlyCurrentFrame = aFlags & eIncludeOnlyCurrentFrameForNonSVGElement; |
1102 | 0 | return nsSVGIntegrationUtils::GetSVGBBoxForNonSVGFrame( |
1103 | 0 | aFrame, |
1104 | 0 | /* aUnionContinuations = */ !onlyCurrentFrame); |
1105 | 0 | } |
1106 | 0 |
|
1107 | 0 | MOZ_ASSERT(svg); |
1108 | 0 |
|
1109 | 0 | nsIContent* content = aFrame->GetContent(); |
1110 | 0 | if (content->IsSVGElement() && |
1111 | 0 | !static_cast<const nsSVGElement*>(content)->HasValidDimensions()) { |
1112 | 0 | return gfxRect(); |
1113 | 0 | } |
1114 | 0 | |
1115 | 0 | // Clean out flags which have no effects on returning bbox from now, so that |
1116 | 0 | // we can cache and reuse ObjectBoundingBoxProperty() in the code below. |
1117 | 0 | aFlags &= ~eIncludeOnlyCurrentFrameForNonSVGElement; |
1118 | 0 | aFlags &= ~eUseFrameBoundsForOuterSVG; |
1119 | 0 | if (!aFrame->IsSVGUseFrame()) { |
1120 | 0 | aFlags &= ~eUseUserSpaceOfUseElement; |
1121 | 0 | } |
1122 | 0 |
|
1123 | 0 | if (aFlags == eBBoxIncludeFillGeometry && |
1124 | 0 | // We only cache bbox in element's own user space |
1125 | 0 | !aToBoundsSpace) { |
1126 | 0 | gfxRect* prop = aFrame->GetProperty(ObjectBoundingBoxProperty()); |
1127 | 0 | if (prop) { |
1128 | 0 | return *prop; |
1129 | 0 | } |
1130 | 0 | } |
1131 | 0 | |
1132 | 0 | gfxMatrix matrix; |
1133 | 0 | if (aToBoundsSpace) { |
1134 | 0 | matrix = *aToBoundsSpace; |
1135 | 0 | } |
1136 | 0 |
|
1137 | 0 | if (aFrame->IsSVGForeignObjectFrame() || |
1138 | 0 | aFlags & nsSVGUtils::eUseUserSpaceOfUseElement) { |
1139 | 0 | // The spec says getBBox "Returns the tight bounding box in *current user |
1140 | 0 | // space*". So we should really be doing this for all elements, but that |
1141 | 0 | // needs investigation to check that we won't break too much content. |
1142 | 0 | // NOTE: When changing this to apply to other frame types, make sure to |
1143 | 0 | // also update nsSVGUtils::FrameSpaceInCSSPxToUserSpaceOffset. |
1144 | 0 | MOZ_ASSERT(content->IsSVGElement(), "bad cast"); |
1145 | 0 | nsSVGElement *element = static_cast<nsSVGElement*>(content); |
1146 | 0 | matrix = element->PrependLocalTransformsTo(matrix, eChildToUserSpace); |
1147 | 0 | } |
1148 | 0 | gfxRect bbox = |
1149 | 0 | svg->GetBBoxContribution(ToMatrix(matrix), aFlags).ToThebesRect(); |
1150 | 0 | // Account for 'clipped'. |
1151 | 0 | if (aFlags & nsSVGUtils::eBBoxIncludeClipped) { |
1152 | 0 | gfxRect clipRect(0, 0, 0, 0); |
1153 | 0 | float x, y, width, height; |
1154 | 0 | gfxMatrix tm; |
1155 | 0 | gfxRect fillBBox = |
1156 | 0 | svg->GetBBoxContribution(ToMatrix(tm), |
1157 | 0 | nsSVGUtils::eBBoxIncludeFill).ToThebesRect(); |
1158 | 0 | x = fillBBox.x; |
1159 | 0 | y = fillBBox.y; |
1160 | 0 | width = fillBBox.width; |
1161 | 0 | height = fillBBox.height; |
1162 | 0 | bool hasClip = aFrame->StyleDisplay()->IsScrollableOverflow(); |
1163 | 0 | if (hasClip) { |
1164 | 0 | clipRect = |
1165 | 0 | nsSVGUtils::GetClipRectForFrame(aFrame, x, y, width, height); |
1166 | 0 | if (aFrame->IsSVGForeignObjectFrame() || aFrame->IsSVGUseFrame()) { |
1167 | 0 | clipRect = matrix.TransformBounds(clipRect); |
1168 | 0 | } |
1169 | 0 | } |
1170 | 0 | SVGObserverUtils::EffectProperties effectProperties = |
1171 | 0 | SVGObserverUtils::GetEffectProperties(aFrame); |
1172 | 0 | if (effectProperties.HasInvalidClipPath()) { |
1173 | 0 | bbox = gfxRect(0, 0, 0, 0); |
1174 | 0 | } else { |
1175 | 0 | nsSVGClipPathFrame *clipPathFrame = |
1176 | 0 | effectProperties.GetClipPathFrame(); |
1177 | 0 | if (clipPathFrame) { |
1178 | 0 | SVGClipPathElement *clipContent = |
1179 | 0 | static_cast<SVGClipPathElement*>(clipPathFrame->GetContent()); |
1180 | 0 | if (clipContent->IsUnitsObjectBoundingBox()) { |
1181 | 0 | matrix.PreTranslate(gfxPoint(x, y)); |
1182 | 0 | matrix.PreScale(width, height); |
1183 | 0 | } else if (aFrame->IsSVGForeignObjectFrame()) { |
1184 | 0 | matrix = gfxMatrix(); |
1185 | 0 | } |
1186 | 0 | matrix = clipContent->PrependLocalTransformsTo(matrix, eUserSpaceToParent); |
1187 | 0 | bbox = |
1188 | 0 | clipPathFrame->GetBBoxForClipPathFrame(bbox, matrix, aFlags).ToThebesRect(); |
1189 | 0 | } |
1190 | 0 |
|
1191 | 0 | if (hasClip) { |
1192 | 0 | bbox = bbox.Intersect(clipRect); |
1193 | 0 | } |
1194 | 0 |
|
1195 | 0 | if (bbox.IsEmpty()) { |
1196 | 0 | bbox = gfxRect(0, 0, 0, 0); |
1197 | 0 | } |
1198 | 0 | } |
1199 | 0 | } |
1200 | 0 |
|
1201 | 0 | if (aFlags == eBBoxIncludeFillGeometry && |
1202 | 0 | // We only cache bbox in element's own user space |
1203 | 0 | !aToBoundsSpace) { |
1204 | 0 | // Obtaining the bbox for objectBoundingBox calculations is common so we |
1205 | 0 | // cache the result for future calls, since calculation can be expensive: |
1206 | 0 | aFrame->SetProperty(ObjectBoundingBoxProperty(), new gfxRect(bbox)); |
1207 | 0 | } |
1208 | 0 |
|
1209 | 0 | return bbox; |
1210 | 0 | } |
1211 | | |
1212 | | gfxPoint |
1213 | | nsSVGUtils::FrameSpaceInCSSPxToUserSpaceOffset(nsIFrame *aFrame) |
1214 | 0 | { |
1215 | 0 | if (!(aFrame->GetStateBits() & NS_FRAME_SVG_LAYOUT)) { |
1216 | 0 | // The user space for non-SVG frames is defined as the bounding box of the |
1217 | 0 | // frame's border-box rects over all continuations. |
1218 | 0 | return gfxPoint(); |
1219 | 0 | } |
1220 | 0 | |
1221 | 0 | // Leaf frames apply their own offset inside their user space. |
1222 | 0 | if (aFrame->IsFrameOfType(nsIFrame::eSVGGeometry) || |
1223 | 0 | nsSVGUtils::IsInSVGTextSubtree(aFrame)) { |
1224 | 0 | return nsLayoutUtils::RectToGfxRect(aFrame->GetRect(), |
1225 | 0 | AppUnitsPerCSSPixel()).TopLeft(); |
1226 | 0 | } |
1227 | 0 | |
1228 | 0 | // For foreignObject frames, nsSVGUtils::GetBBox applies their local |
1229 | 0 | // transform, so we need to do the same here. |
1230 | 0 | if (aFrame->IsSVGForeignObjectFrame()) { |
1231 | 0 | gfxMatrix transform = static_cast<nsSVGElement*>(aFrame->GetContent())-> |
1232 | 0 | PrependLocalTransformsTo(gfxMatrix(), eChildToUserSpace); |
1233 | 0 | NS_ASSERTION(!transform.HasNonTranslation(), "we're relying on this being an offset-only transform"); |
1234 | 0 | return transform.GetTranslation(); |
1235 | 0 | } |
1236 | 0 |
|
1237 | 0 | return gfxPoint(); |
1238 | 0 | } |
1239 | | |
1240 | | static gfxRect |
1241 | | GetBoundingBoxRelativeRect(const nsSVGLength2 *aXYWH, |
1242 | | const gfxRect& aBBox) |
1243 | 0 | { |
1244 | 0 | return gfxRect(aBBox.x + nsSVGUtils::ObjectSpace(aBBox, &aXYWH[0]), |
1245 | 0 | aBBox.y + nsSVGUtils::ObjectSpace(aBBox, &aXYWH[1]), |
1246 | 0 | nsSVGUtils::ObjectSpace(aBBox, &aXYWH[2]), |
1247 | 0 | nsSVGUtils::ObjectSpace(aBBox, &aXYWH[3])); |
1248 | 0 | } |
1249 | | |
1250 | | gfxRect |
1251 | | nsSVGUtils::GetRelativeRect(uint16_t aUnits, const nsSVGLength2 *aXYWH, |
1252 | | const gfxRect& aBBox, |
1253 | | const UserSpaceMetrics& aMetrics) |
1254 | 0 | { |
1255 | 0 | if (aUnits == SVG_UNIT_TYPE_OBJECTBOUNDINGBOX) { |
1256 | 0 | return GetBoundingBoxRelativeRect(aXYWH, aBBox); |
1257 | 0 | } |
1258 | 0 | return gfxRect(UserSpace(aMetrics, &aXYWH[0]), |
1259 | 0 | UserSpace(aMetrics, &aXYWH[1]), |
1260 | 0 | UserSpace(aMetrics, &aXYWH[2]), |
1261 | 0 | UserSpace(aMetrics, &aXYWH[3])); |
1262 | 0 | } |
1263 | | |
1264 | | gfxRect |
1265 | | nsSVGUtils::GetRelativeRect(uint16_t aUnits, const nsSVGLength2 *aXYWH, |
1266 | | const gfxRect& aBBox, nsIFrame *aFrame) |
1267 | 0 | { |
1268 | 0 | if (aUnits == SVG_UNIT_TYPE_OBJECTBOUNDINGBOX) { |
1269 | 0 | return GetBoundingBoxRelativeRect(aXYWH, aBBox); |
1270 | 0 | } |
1271 | 0 | nsIContent* content = aFrame->GetContent(); |
1272 | 0 | if (content->IsSVGElement()) { |
1273 | 0 | nsSVGElement* svgElement = static_cast<nsSVGElement*>(content); |
1274 | 0 | return GetRelativeRect(aUnits, aXYWH, aBBox, SVGElementMetrics(svgElement)); |
1275 | 0 | } |
1276 | 0 | return GetRelativeRect(aUnits, aXYWH, aBBox, NonSVGFrameUserSpaceMetrics(aFrame)); |
1277 | 0 | } |
1278 | | |
1279 | | bool |
1280 | | nsSVGUtils::CanOptimizeOpacity(nsIFrame *aFrame) |
1281 | 0 | { |
1282 | 0 | if (!(aFrame->GetStateBits() & NS_FRAME_SVG_LAYOUT)) { |
1283 | 0 | return false; |
1284 | 0 | } |
1285 | 0 | LayoutFrameType type = aFrame->Type(); |
1286 | 0 | if (type != LayoutFrameType::SVGImage && |
1287 | 0 | type != LayoutFrameType::SVGGeometry) { |
1288 | 0 | return false; |
1289 | 0 | } |
1290 | 0 | if (aFrame->StyleEffects()->HasFilters()) { |
1291 | 0 | return false; |
1292 | 0 | } |
1293 | 0 | // XXX The SVG WG is intending to allow fill, stroke and markers on <image> |
1294 | 0 | if (type == LayoutFrameType::SVGImage) { |
1295 | 0 | return true; |
1296 | 0 | } |
1297 | 0 | const nsStyleSVG *style = aFrame->StyleSVG(); |
1298 | 0 | if (style->HasMarker()) { |
1299 | 0 | return false; |
1300 | 0 | } |
1301 | 0 | |
1302 | 0 | if (nsLayoutUtils::HasAnimationOfProperty(aFrame, eCSSProperty_opacity)) { |
1303 | 0 | return false; |
1304 | 0 | } |
1305 | 0 | |
1306 | 0 | if (!style->HasFill() || !HasStroke(aFrame)) { |
1307 | 0 | return true; |
1308 | 0 | } |
1309 | 0 | return false; |
1310 | 0 | } |
1311 | | |
1312 | | gfxMatrix |
1313 | | nsSVGUtils::AdjustMatrixForUnits(const gfxMatrix &aMatrix, |
1314 | | nsSVGEnum *aUnits, |
1315 | | nsIFrame *aFrame, |
1316 | | uint32_t aFlags) |
1317 | 0 | { |
1318 | 0 | if (aFrame && |
1319 | 0 | aUnits->GetAnimValue() == SVG_UNIT_TYPE_OBJECTBOUNDINGBOX) { |
1320 | 0 | gfxRect bbox = GetBBox(aFrame, aFlags); |
1321 | 0 | gfxMatrix tm = aMatrix; |
1322 | 0 | tm.PreTranslate(gfxPoint(bbox.X(), bbox.Y())); |
1323 | 0 | tm.PreScale(bbox.Width(), bbox.Height()); |
1324 | 0 | return tm; |
1325 | 0 | } |
1326 | 0 | return aMatrix; |
1327 | 0 | } |
1328 | | |
1329 | | nsIFrame* |
1330 | | nsSVGUtils::GetFirstNonAAncestorFrame(nsIFrame* aStartFrame) |
1331 | 0 | { |
1332 | 0 | for (nsIFrame *ancestorFrame = aStartFrame; ancestorFrame; |
1333 | 0 | ancestorFrame = ancestorFrame->GetParent()) { |
1334 | 0 | if (!ancestorFrame->IsSVGAFrame()) { |
1335 | 0 | return ancestorFrame; |
1336 | 0 | } |
1337 | 0 | } |
1338 | 0 | return nullptr; |
1339 | 0 | } |
1340 | | |
1341 | | bool |
1342 | | nsSVGUtils::GetNonScalingStrokeTransform(nsIFrame *aFrame, |
1343 | | gfxMatrix* aUserToOuterSVG) |
1344 | 0 | { |
1345 | 0 | if (aFrame->GetContent()->IsText()) { |
1346 | 0 | aFrame = aFrame->GetParent(); |
1347 | 0 | } |
1348 | 0 |
|
1349 | 0 | if (!aFrame->StyleSVGReset()->HasNonScalingStroke()) { |
1350 | 0 | return false; |
1351 | 0 | } |
1352 | 0 | |
1353 | 0 | nsIContent *content = aFrame->GetContent(); |
1354 | 0 | MOZ_ASSERT(content->IsSVGElement(), "bad cast"); |
1355 | 0 |
|
1356 | 0 | *aUserToOuterSVG = ThebesMatrix(SVGContentUtils::GetCTM( |
1357 | 0 | static_cast<nsSVGElement*>(content), true)); |
1358 | 0 |
|
1359 | 0 | return !aUserToOuterSVG->IsIdentity(); |
1360 | 0 | } |
1361 | | |
1362 | | // The logic here comes from _cairo_stroke_style_max_distance_from_path |
1363 | | static gfxRect |
1364 | | PathExtentsToMaxStrokeExtents(const gfxRect& aPathExtents, |
1365 | | nsIFrame* aFrame, |
1366 | | double aStyleExpansionFactor, |
1367 | | const gfxMatrix& aMatrix) |
1368 | 0 | { |
1369 | 0 | double style_expansion = |
1370 | 0 | aStyleExpansionFactor * nsSVGUtils::GetStrokeWidth(aFrame); |
1371 | 0 |
|
1372 | 0 | gfxMatrix matrix = aMatrix; |
1373 | 0 |
|
1374 | 0 | gfxMatrix outerSVGToUser; |
1375 | 0 | if (nsSVGUtils::GetNonScalingStrokeTransform(aFrame, &outerSVGToUser)) { |
1376 | 0 | outerSVGToUser.Invert(); |
1377 | 0 | matrix.PreMultiply(outerSVGToUser); |
1378 | 0 | } |
1379 | 0 |
|
1380 | 0 | double dx = style_expansion * (fabs(matrix._11) + fabs(matrix._21)); |
1381 | 0 | double dy = style_expansion * (fabs(matrix._22) + fabs(matrix._12)); |
1382 | 0 |
|
1383 | 0 | gfxRect strokeExtents = aPathExtents; |
1384 | 0 | strokeExtents.Inflate(dx, dy); |
1385 | 0 | return strokeExtents; |
1386 | 0 | } |
1387 | | |
1388 | | /*static*/ gfxRect |
1389 | | nsSVGUtils::PathExtentsToMaxStrokeExtents(const gfxRect& aPathExtents, |
1390 | | nsTextFrame* aFrame, |
1391 | | const gfxMatrix& aMatrix) |
1392 | 0 | { |
1393 | 0 | NS_ASSERTION(nsSVGUtils::IsInSVGTextSubtree(aFrame), |
1394 | 0 | "expected an nsTextFrame for SVG text"); |
1395 | 0 | return ::PathExtentsToMaxStrokeExtents(aPathExtents, aFrame, 0.5, aMatrix); |
1396 | 0 | } |
1397 | | |
1398 | | /*static*/ gfxRect |
1399 | | nsSVGUtils::PathExtentsToMaxStrokeExtents(const gfxRect& aPathExtents, |
1400 | | SVGGeometryFrame* aFrame, |
1401 | | const gfxMatrix& aMatrix) |
1402 | 0 | { |
1403 | 0 | bool strokeMayHaveCorners = |
1404 | 0 | !SVGContentUtils::ShapeTypeHasNoCorners(aFrame->GetContent()); |
1405 | 0 |
|
1406 | 0 | // For a shape without corners the stroke can only extend half the stroke |
1407 | 0 | // width from the path in the x/y-axis directions. For shapes with corners |
1408 | 0 | // the stroke can extend by sqrt(1/2) (think 45 degree rotated rect, or line |
1409 | 0 | // with stroke-linecaps="square"). |
1410 | 0 | double styleExpansionFactor = strokeMayHaveCorners ? M_SQRT1_2 : 0.5; |
1411 | 0 |
|
1412 | 0 | // The stroke can extend even further for paths that can be affected by |
1413 | 0 | // stroke-miterlimit. |
1414 | 0 | bool affectedByMiterlimit = |
1415 | 0 | aFrame->GetContent()->IsAnyOfSVGElements(nsGkAtoms::path, |
1416 | 0 | nsGkAtoms::polyline, |
1417 | 0 | nsGkAtoms::polygon); |
1418 | 0 |
|
1419 | 0 | if (affectedByMiterlimit) { |
1420 | 0 | const nsStyleSVG* style = aFrame->StyleSVG(); |
1421 | 0 | if (style->mStrokeLinejoin == NS_STYLE_STROKE_LINEJOIN_MITER && |
1422 | 0 | styleExpansionFactor < style->mStrokeMiterlimit / 2.0) { |
1423 | 0 | styleExpansionFactor = style->mStrokeMiterlimit / 2.0; |
1424 | 0 | } |
1425 | 0 | } |
1426 | 0 |
|
1427 | 0 | return ::PathExtentsToMaxStrokeExtents(aPathExtents, |
1428 | 0 | aFrame, |
1429 | 0 | styleExpansionFactor, |
1430 | 0 | aMatrix); |
1431 | 0 | } |
1432 | | |
1433 | | // ---------------------------------------------------------------------- |
1434 | | |
1435 | | /* static */ nscolor |
1436 | | nsSVGUtils::GetFallbackOrPaintColor(ComputedStyle *aComputedStyle, |
1437 | | nsStyleSVGPaint nsStyleSVG::*aFillOrStroke) |
1438 | 0 | { |
1439 | 0 | const nsStyleSVGPaint &paint = aComputedStyle->StyleSVG()->*aFillOrStroke; |
1440 | 0 | ComputedStyle *styleIfVisited = aComputedStyle->GetStyleIfVisited(); |
1441 | 0 | nscolor color; |
1442 | 0 | switch (paint.Type()) { |
1443 | 0 | case eStyleSVGPaintType_Server: |
1444 | 0 | case eStyleSVGPaintType_ContextStroke: |
1445 | 0 | color = paint.GetFallbackType() == eStyleSVGFallbackType_Color ? |
1446 | 0 | paint.GetFallbackColor(aComputedStyle) : NS_RGBA(0, 0, 0, 0); |
1447 | 0 | break; |
1448 | 0 | case eStyleSVGPaintType_ContextFill: |
1449 | 0 | color = paint.GetFallbackType() == eStyleSVGFallbackType_Color ? |
1450 | 0 | paint.GetFallbackColor(aComputedStyle) : NS_RGB(0, 0, 0); |
1451 | 0 | break; |
1452 | 0 | default: |
1453 | 0 | color = paint.GetColor(aComputedStyle); |
1454 | 0 | break; |
1455 | 0 | } |
1456 | 0 | if (styleIfVisited) { |
1457 | 0 | const nsStyleSVGPaint &paintIfVisited = |
1458 | 0 | styleIfVisited->StyleSVG()->*aFillOrStroke; |
1459 | 0 | // To prevent Web content from detecting if a user has visited a URL |
1460 | 0 | // (via URL loading triggered by paint servers or performance |
1461 | 0 | // differences between paint servers or between a paint server and a |
1462 | 0 | // color), we do not allow whether links are visited to change which |
1463 | 0 | // paint server is used or switch between paint servers and simple |
1464 | 0 | // colors. A :visited style may only override a simple color with |
1465 | 0 | // another simple color. |
1466 | 0 | if (paintIfVisited.Type() == eStyleSVGPaintType_Color && |
1467 | 0 | paint.Type() == eStyleSVGPaintType_Color) { |
1468 | 0 | nscolor colors[2] = { color, paintIfVisited.GetColor(aComputedStyle) }; |
1469 | 0 | return ComputedStyle::CombineVisitedColors( |
1470 | 0 | colors, aComputedStyle->RelevantLinkVisited()); |
1471 | 0 | } |
1472 | 0 | } |
1473 | 0 | return color; |
1474 | 0 | } |
1475 | | |
1476 | | /* static */ void |
1477 | | nsSVGUtils::MakeFillPatternFor(nsIFrame* aFrame, |
1478 | | gfxContext* aContext, |
1479 | | GeneralPattern* aOutPattern, |
1480 | | imgDrawingParams& aImgParams, |
1481 | | SVGContextPaint* aContextPaint) |
1482 | 0 | { |
1483 | 0 | const nsStyleSVG* style = aFrame->StyleSVG(); |
1484 | 0 | if (style->mFill.Type() == eStyleSVGPaintType_None) { |
1485 | 0 | return; |
1486 | 0 | } |
1487 | 0 | |
1488 | 0 | const float opacity = aFrame->StyleEffects()->mOpacity; |
1489 | 0 |
|
1490 | 0 | float fillOpacity = GetOpacity(style->FillOpacitySource(), |
1491 | 0 | style->mFillOpacity, |
1492 | 0 | aContextPaint); |
1493 | 0 | if (opacity < 1.0f && |
1494 | 0 | nsSVGUtils::CanOptimizeOpacity(aFrame)) { |
1495 | 0 | // Combine the group opacity into the fill opacity (we will have skipped |
1496 | 0 | // creating an offscreen surface to apply the group opacity). |
1497 | 0 | fillOpacity *= opacity; |
1498 | 0 | } |
1499 | 0 |
|
1500 | 0 | const DrawTarget* dt = aContext->GetDrawTarget(); |
1501 | 0 |
|
1502 | 0 | nsSVGPaintServerFrame *ps = |
1503 | 0 | SVGObserverUtils::GetPaintServer(aFrame, &nsStyleSVG::mFill); |
1504 | 0 |
|
1505 | 0 | if (ps) { |
1506 | 0 | RefPtr<gfxPattern> pattern = |
1507 | 0 | ps->GetPaintServerPattern(aFrame, dt, aContext->CurrentMatrixDouble(), |
1508 | 0 | &nsStyleSVG::mFill, fillOpacity, aImgParams); |
1509 | 0 | if (pattern) { |
1510 | 0 | pattern->CacheColorStops(dt); |
1511 | 0 | aOutPattern->Init(*pattern->GetPattern(dt)); |
1512 | 0 | return; |
1513 | 0 | } |
1514 | 0 | } |
1515 | 0 | |
1516 | 0 | if (aContextPaint) { |
1517 | 0 | RefPtr<gfxPattern> pattern; |
1518 | 0 | switch (style->mFill.Type()) { |
1519 | 0 | case eStyleSVGPaintType_ContextFill: |
1520 | 0 | pattern = |
1521 | 0 | aContextPaint->GetFillPattern(dt, fillOpacity, |
1522 | 0 | aContext->CurrentMatrixDouble(), aImgParams); |
1523 | 0 | break; |
1524 | 0 | case eStyleSVGPaintType_ContextStroke: |
1525 | 0 | pattern = |
1526 | 0 | aContextPaint->GetStrokePattern(dt, fillOpacity, |
1527 | 0 | aContext->CurrentMatrixDouble(), aImgParams); |
1528 | 0 | break; |
1529 | 0 | default: |
1530 | 0 | ; |
1531 | 0 | } |
1532 | 0 | if (pattern) { |
1533 | 0 | aOutPattern->Init(*pattern->GetPattern(dt)); |
1534 | 0 | return; |
1535 | 0 | } |
1536 | 0 | } |
1537 | 0 | |
1538 | 0 | if (style->mFill.GetFallbackType() == eStyleSVGFallbackType_None) { |
1539 | 0 | return; |
1540 | 0 | } |
1541 | 0 | |
1542 | 0 | // On failure, use the fallback colour in case we have an |
1543 | 0 | // objectBoundingBox where the width or height of the object is zero. |
1544 | 0 | // See http://www.w3.org/TR/SVG11/coords.html#ObjectBoundingBox |
1545 | 0 | Color color(Color::FromABGR(GetFallbackOrPaintColor(aFrame->Style(), |
1546 | 0 | &nsStyleSVG::mFill))); |
1547 | 0 | color.a *= fillOpacity; |
1548 | 0 | aOutPattern->InitColorPattern(ToDeviceColor(color)); |
1549 | 0 | } |
1550 | | |
1551 | | /* static */ void |
1552 | | nsSVGUtils::MakeStrokePatternFor(nsIFrame* aFrame, |
1553 | | gfxContext* aContext, |
1554 | | GeneralPattern* aOutPattern, |
1555 | | imgDrawingParams& aImgParams, |
1556 | | SVGContextPaint* aContextPaint) |
1557 | 0 | { |
1558 | 0 | const nsStyleSVG* style = aFrame->StyleSVG(); |
1559 | 0 | if (style->mStroke.Type() == eStyleSVGPaintType_None) { |
1560 | 0 | return; |
1561 | 0 | } |
1562 | 0 | |
1563 | 0 | const float opacity = aFrame->StyleEffects()->mOpacity; |
1564 | 0 |
|
1565 | 0 | float strokeOpacity = GetOpacity(style->StrokeOpacitySource(), |
1566 | 0 | style->mStrokeOpacity, |
1567 | 0 | aContextPaint); |
1568 | 0 | if (opacity < 1.0f && |
1569 | 0 | nsSVGUtils::CanOptimizeOpacity(aFrame)) { |
1570 | 0 | // Combine the group opacity into the stroke opacity (we will have skipped |
1571 | 0 | // creating an offscreen surface to apply the group opacity). |
1572 | 0 | strokeOpacity *= opacity; |
1573 | 0 | } |
1574 | 0 |
|
1575 | 0 | const DrawTarget* dt = aContext->GetDrawTarget(); |
1576 | 0 |
|
1577 | 0 | nsSVGPaintServerFrame *ps = |
1578 | 0 | SVGObserverUtils::GetPaintServer(aFrame, &nsStyleSVG::mStroke); |
1579 | 0 |
|
1580 | 0 | if (ps) { |
1581 | 0 | RefPtr<gfxPattern> pattern = |
1582 | 0 | ps->GetPaintServerPattern(aFrame, dt, aContext->CurrentMatrixDouble(), |
1583 | 0 | &nsStyleSVG::mStroke, strokeOpacity, aImgParams); |
1584 | 0 | if (pattern) { |
1585 | 0 | pattern->CacheColorStops(dt); |
1586 | 0 | aOutPattern->Init(*pattern->GetPattern(dt)); |
1587 | 0 | return; |
1588 | 0 | } |
1589 | 0 | } |
1590 | 0 | |
1591 | 0 | if (aContextPaint) { |
1592 | 0 | RefPtr<gfxPattern> pattern; |
1593 | 0 | switch (style->mStroke.Type()) { |
1594 | 0 | case eStyleSVGPaintType_ContextFill: |
1595 | 0 | pattern = |
1596 | 0 | aContextPaint->GetFillPattern(dt, strokeOpacity, |
1597 | 0 | aContext->CurrentMatrixDouble(), aImgParams); |
1598 | 0 | break; |
1599 | 0 | case eStyleSVGPaintType_ContextStroke: |
1600 | 0 | pattern = |
1601 | 0 | aContextPaint->GetStrokePattern(dt, strokeOpacity, |
1602 | 0 | aContext->CurrentMatrixDouble(), aImgParams); |
1603 | 0 | break; |
1604 | 0 | default: |
1605 | 0 | ; |
1606 | 0 | } |
1607 | 0 | if (pattern) { |
1608 | 0 | aOutPattern->Init(*pattern->GetPattern(dt)); |
1609 | 0 | return; |
1610 | 0 | } |
1611 | 0 | } |
1612 | 0 | |
1613 | 0 | if (style->mStroke.GetFallbackType() == eStyleSVGFallbackType_None) { |
1614 | 0 | return; |
1615 | 0 | } |
1616 | 0 | |
1617 | 0 | // On failure, use the fallback colour in case we have an |
1618 | 0 | // objectBoundingBox where the width or height of the object is zero. |
1619 | 0 | // See http://www.w3.org/TR/SVG11/coords.html#ObjectBoundingBox |
1620 | 0 | Color color(Color::FromABGR(GetFallbackOrPaintColor(aFrame->Style(), |
1621 | 0 | &nsStyleSVG::mStroke))); |
1622 | 0 | color.a *= strokeOpacity; |
1623 | 0 | aOutPattern->InitColorPattern(ToDeviceColor(color)); |
1624 | 0 | } |
1625 | | |
1626 | | /* static */ float |
1627 | | nsSVGUtils::GetOpacity(nsStyleSVGOpacitySource aOpacityType, |
1628 | | const float& aOpacity, |
1629 | | SVGContextPaint *aContextPaint) |
1630 | 0 | { |
1631 | 0 | float opacity = 1.0f; |
1632 | 0 | switch (aOpacityType) { |
1633 | 0 | case eStyleSVGOpacitySource_Normal: |
1634 | 0 | opacity = aOpacity; |
1635 | 0 | break; |
1636 | 0 | case eStyleSVGOpacitySource_ContextFillOpacity: |
1637 | 0 | if (aContextPaint) { |
1638 | 0 | opacity = aContextPaint->GetFillOpacity(); |
1639 | 0 | } else { |
1640 | 0 | NS_WARNING("Content used context-fill-opacity when not in a context element"); |
1641 | 0 | } |
1642 | 0 | break; |
1643 | 0 | case eStyleSVGOpacitySource_ContextStrokeOpacity: |
1644 | 0 | if (aContextPaint) { |
1645 | 0 | opacity = aContextPaint->GetStrokeOpacity(); |
1646 | 0 | } else { |
1647 | 0 | NS_WARNING("Content used context-stroke-opacity when not in a context element"); |
1648 | 0 | } |
1649 | 0 | break; |
1650 | 0 | default: |
1651 | 0 | MOZ_ASSERT_UNREACHABLE("Unknown object opacity inheritance type for SVG " |
1652 | 0 | "glyph"); |
1653 | 0 | } |
1654 | 0 | return opacity; |
1655 | 0 | } |
1656 | | |
1657 | | bool |
1658 | | nsSVGUtils::HasStroke(nsIFrame* aFrame, SVGContextPaint* aContextPaint) |
1659 | 0 | { |
1660 | 0 | const nsStyleSVG *style = aFrame->StyleSVG(); |
1661 | 0 | return style->HasStroke() && GetStrokeWidth(aFrame, aContextPaint) > 0; |
1662 | 0 | } |
1663 | | |
1664 | | float |
1665 | | nsSVGUtils::GetStrokeWidth(nsIFrame* aFrame, SVGContextPaint* aContextPaint) |
1666 | 0 | { |
1667 | 0 | const nsStyleSVG *style = aFrame->StyleSVG(); |
1668 | 0 | if (aContextPaint && style->StrokeWidthFromObject()) { |
1669 | 0 | return aContextPaint->GetStrokeWidth(); |
1670 | 0 | } |
1671 | 0 | |
1672 | 0 | nsIContent* content = aFrame->GetContent(); |
1673 | 0 | if (content->IsText()) { |
1674 | 0 | content = content->GetParent(); |
1675 | 0 | } |
1676 | 0 |
|
1677 | 0 | nsSVGElement *ctx = static_cast<nsSVGElement*>(content); |
1678 | 0 |
|
1679 | 0 | return SVGContentUtils::CoordToFloat(ctx, style->mStrokeWidth); |
1680 | 0 | } |
1681 | | |
1682 | | void |
1683 | | nsSVGUtils::SetupStrokeGeometry(nsIFrame* aFrame, |
1684 | | gfxContext *aContext, |
1685 | | SVGContextPaint* aContextPaint) |
1686 | 0 | { |
1687 | 0 | SVGContentUtils::AutoStrokeOptions strokeOptions; |
1688 | 0 | SVGContentUtils::GetStrokeOptions( |
1689 | 0 | &strokeOptions, static_cast<nsSVGElement*>(aFrame->GetContent()), |
1690 | 0 | aFrame->Style(), aContextPaint); |
1691 | 0 |
|
1692 | 0 | if (strokeOptions.mLineWidth <= 0) { |
1693 | 0 | return; |
1694 | 0 | } |
1695 | 0 | |
1696 | 0 | aContext->SetLineWidth(strokeOptions.mLineWidth); |
1697 | 0 | aContext->SetLineCap(strokeOptions.mLineCap); |
1698 | 0 | aContext->SetMiterLimit(strokeOptions.mMiterLimit); |
1699 | 0 | aContext->SetLineJoin(strokeOptions.mLineJoin); |
1700 | 0 | aContext->SetDash(strokeOptions.mDashPattern, strokeOptions.mDashLength, |
1701 | 0 | strokeOptions.mDashOffset); |
1702 | 0 | } |
1703 | | |
1704 | | uint16_t |
1705 | | nsSVGUtils::GetGeometryHitTestFlags(nsIFrame* aFrame) |
1706 | 0 | { |
1707 | 0 | uint16_t flags = 0; |
1708 | 0 |
|
1709 | 0 | switch (aFrame->StyleUI()->mPointerEvents) { |
1710 | 0 | case NS_STYLE_POINTER_EVENTS_NONE: |
1711 | 0 | break; |
1712 | 0 | case NS_STYLE_POINTER_EVENTS_AUTO: |
1713 | 0 | case NS_STYLE_POINTER_EVENTS_VISIBLEPAINTED: |
1714 | 0 | if (aFrame->StyleVisibility()->IsVisible()) { |
1715 | 0 | if (aFrame->StyleSVG()->mFill.Type() != eStyleSVGPaintType_None) |
1716 | 0 | flags |= SVG_HIT_TEST_FILL; |
1717 | 0 | if (aFrame->StyleSVG()->mStroke.Type() != eStyleSVGPaintType_None) |
1718 | 0 | flags |= SVG_HIT_TEST_STROKE; |
1719 | 0 | if (aFrame->StyleSVG()->mStrokeOpacity > 0) |
1720 | 0 | flags |= SVG_HIT_TEST_CHECK_MRECT; |
1721 | 0 | } |
1722 | 0 | break; |
1723 | 0 | case NS_STYLE_POINTER_EVENTS_VISIBLEFILL: |
1724 | 0 | if (aFrame->StyleVisibility()->IsVisible()) { |
1725 | 0 | flags |= SVG_HIT_TEST_FILL; |
1726 | 0 | } |
1727 | 0 | break; |
1728 | 0 | case NS_STYLE_POINTER_EVENTS_VISIBLESTROKE: |
1729 | 0 | if (aFrame->StyleVisibility()->IsVisible()) { |
1730 | 0 | flags |= SVG_HIT_TEST_STROKE; |
1731 | 0 | } |
1732 | 0 | break; |
1733 | 0 | case NS_STYLE_POINTER_EVENTS_VISIBLE: |
1734 | 0 | if (aFrame->StyleVisibility()->IsVisible()) { |
1735 | 0 | flags |= SVG_HIT_TEST_FILL | SVG_HIT_TEST_STROKE; |
1736 | 0 | } |
1737 | 0 | break; |
1738 | 0 | case NS_STYLE_POINTER_EVENTS_PAINTED: |
1739 | 0 | if (aFrame->StyleSVG()->mFill.Type() != eStyleSVGPaintType_None) |
1740 | 0 | flags |= SVG_HIT_TEST_FILL; |
1741 | 0 | if (aFrame->StyleSVG()->mStroke.Type() != eStyleSVGPaintType_None) |
1742 | 0 | flags |= SVG_HIT_TEST_STROKE; |
1743 | 0 | if (aFrame->StyleSVG()->mStrokeOpacity) |
1744 | 0 | flags |= SVG_HIT_TEST_CHECK_MRECT; |
1745 | 0 | break; |
1746 | 0 | case NS_STYLE_POINTER_EVENTS_FILL: |
1747 | 0 | flags |= SVG_HIT_TEST_FILL; |
1748 | 0 | break; |
1749 | 0 | case NS_STYLE_POINTER_EVENTS_STROKE: |
1750 | 0 | flags |= SVG_HIT_TEST_STROKE; |
1751 | 0 | break; |
1752 | 0 | case NS_STYLE_POINTER_EVENTS_ALL: |
1753 | 0 | flags |= SVG_HIT_TEST_FILL | SVG_HIT_TEST_STROKE; |
1754 | 0 | break; |
1755 | 0 | default: |
1756 | 0 | NS_ERROR("not reached"); |
1757 | 0 | break; |
1758 | 0 | } |
1759 | 0 |
|
1760 | 0 | return flags; |
1761 | 0 | } |
1762 | | |
1763 | | void |
1764 | | nsSVGUtils::PaintSVGGlyph(Element* aElement, gfxContext* aContext) |
1765 | 0 | { |
1766 | 0 | nsIFrame* frame = aElement->GetPrimaryFrame(); |
1767 | 0 | nsSVGDisplayableFrame* svgFrame = do_QueryFrame(frame); |
1768 | 0 | if (!svgFrame) { |
1769 | 0 | return; |
1770 | 0 | } |
1771 | 0 | gfxMatrix m; |
1772 | 0 | if (frame->GetContent()->IsSVGElement()) { |
1773 | 0 | // PaintSVG() expects the passed transform to be the transform to its own |
1774 | 0 | // SVG user space, so we need to account for any 'transform' attribute: |
1775 | 0 | m = static_cast<nsSVGElement*>(frame->GetContent())-> |
1776 | 0 | PrependLocalTransformsTo(gfxMatrix(), eUserSpaceToParent); |
1777 | 0 | } |
1778 | 0 |
|
1779 | 0 | // SVG-in-OpenType is not allowed to paint external resources, so we can |
1780 | 0 | // just pass a dummy params into PatintSVG. |
1781 | 0 | imgDrawingParams dummy; |
1782 | 0 | svgFrame->PaintSVG(*aContext, m, dummy); |
1783 | 0 | } |
1784 | | |
1785 | | bool |
1786 | | nsSVGUtils::GetSVGGlyphExtents(Element* aElement, |
1787 | | const gfxMatrix& aSVGToAppSpace, |
1788 | | gfxRect* aResult) |
1789 | 0 | { |
1790 | 0 | nsIFrame* frame = aElement->GetPrimaryFrame(); |
1791 | 0 | nsSVGDisplayableFrame* svgFrame = do_QueryFrame(frame); |
1792 | 0 | if (!svgFrame) { |
1793 | 0 | return false; |
1794 | 0 | } |
1795 | 0 | |
1796 | 0 | gfxMatrix transform(aSVGToAppSpace); |
1797 | 0 | nsIContent* content = frame->GetContent(); |
1798 | 0 | if (content->IsSVGElement()) { |
1799 | 0 | transform = static_cast<nsSVGElement*>(content)-> |
1800 | 0 | PrependLocalTransformsTo(aSVGToAppSpace); |
1801 | 0 | } |
1802 | 0 |
|
1803 | 0 | *aResult = svgFrame->GetBBoxContribution(gfx::ToMatrix(transform), |
1804 | 0 | nsSVGUtils::eBBoxIncludeFill | nsSVGUtils::eBBoxIncludeFillGeometry | |
1805 | 0 | nsSVGUtils::eBBoxIncludeStroke | nsSVGUtils::eBBoxIncludeStrokeGeometry | |
1806 | 0 | nsSVGUtils::eBBoxIncludeMarkers).ToThebesRect(); |
1807 | 0 | return true; |
1808 | 0 | } |
1809 | | |
1810 | | nsRect |
1811 | | nsSVGUtils::ToCanvasBounds(const gfxRect &aUserspaceRect, |
1812 | | const gfxMatrix &aToCanvas, |
1813 | | const nsPresContext *presContext) |
1814 | 0 | { |
1815 | 0 | return nsLayoutUtils::RoundGfxRectToAppRect( |
1816 | 0 | aToCanvas.TransformBounds(aUserspaceRect), |
1817 | 0 | presContext->AppUnitsPerDevPixel()); |
1818 | 0 | } |
1819 | | |
1820 | | gfxMatrix |
1821 | | nsSVGUtils::GetCSSPxToDevPxMatrix(nsIFrame* aNonSVGFrame) |
1822 | 0 | { |
1823 | 0 | int32_t appUnitsPerDevPixel = aNonSVGFrame->PresContext()->AppUnitsPerDevPixel(); |
1824 | 0 | float devPxPerCSSPx = |
1825 | 0 | 1 / nsPresContext::AppUnitsToFloatCSSPixels(appUnitsPerDevPixel); |
1826 | 0 |
|
1827 | 0 | return gfxMatrix(devPxPerCSSPx, 0.0, |
1828 | 0 | 0.0, devPxPerCSSPx, |
1829 | 0 | 0.0, 0.0); |
1830 | 0 | } |