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