Bug 1039883 - release Tiled layer's gralloc when an application is background r=nical
[gecko.git] / layout / svg / nsSVGUtils.cpp
bloba65ff4afc7c863e3586bf65469dfdf25450c0886
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/. */
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>
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/Preferences.h"
20 #include "nsCSSFrameConstructor.h"
21 #include "nsDisplayList.h"
22 #include "nsFilterInstance.h"
23 #include "nsFrameList.h"
24 #include "nsGkAtoms.h"
25 #include "nsIContent.h"
26 #include "nsIDocument.h"
27 #include "nsIFrame.h"
28 #include "nsIPresShell.h"
29 #include "nsISVGChildFrame.h"
30 #include "nsPresContext.h"
31 #include "nsRenderingContext.h"
32 #include "nsStyleCoord.h"
33 #include "nsStyleStruct.h"
34 #include "nsSVGClipPathFrame.h"
35 #include "nsSVGContainerFrame.h"
36 #include "nsSVGEffects.h"
37 #include "nsSVGFilterPaintCallback.h"
38 #include "nsSVGForeignObjectFrame.h"
39 #include "gfxSVGGlyphs.h"
40 #include "nsSVGInnerSVGFrame.h"
41 #include "nsSVGIntegrationUtils.h"
42 #include "nsSVGLength2.h"
43 #include "nsSVGMaskFrame.h"
44 #include "nsSVGOuterSVGFrame.h"
45 #include "mozilla/dom/SVGClipPathElement.h"
46 #include "mozilla/dom/SVGPathElement.h"
47 #include "nsSVGPathGeometryElement.h"
48 #include "nsSVGPathGeometryFrame.h"
49 #include "nsSVGPaintServerFrame.h"
50 #include "mozilla/dom/SVGSVGElement.h"
51 #include "nsTextFrame.h"
52 #include "SVGContentUtils.h"
53 #include "mozilla/unused.h"
55 using namespace mozilla;
56 using namespace mozilla::dom;
57 using namespace mozilla::gfx;
59 static bool sSVGDisplayListHitTestingEnabled;
60 static bool sSVGDisplayListPaintingEnabled;
61 static bool sSVGNewGetBBoxEnabled;
63 bool
64 NS_SVGDisplayListHitTestingEnabled()
66 return sSVGDisplayListHitTestingEnabled;
69 bool
70 NS_SVGDisplayListPaintingEnabled()
72 return sSVGDisplayListPaintingEnabled;
75 bool
76 NS_SVGNewGetBBoxEnabled()
78 return sSVGNewGetBBoxEnabled;
82 // we only take the address of this:
83 static mozilla::gfx::UserDataKey sSVGAutoRenderStateKey;
85 SVGAutoRenderState::SVGAutoRenderState(nsRenderingContext *aContext,
86 RenderMode aMode
87 MOZ_GUARD_OBJECT_NOTIFIER_PARAM_IN_IMPL)
88 : mContext(aContext)
89 , mOriginalRenderState(nullptr)
90 , mMode(aMode)
91 , mPaintingToWindow(false)
93 MOZ_GUARD_OBJECT_NOTIFIER_INIT;
94 mOriginalRenderState = aContext->RemoveUserData(&sSVGAutoRenderStateKey);
95 // We always remove ourselves from aContext before it dies, so
96 // passing nullptr as the destroy function is okay.
97 aContext->AddUserData(&sSVGAutoRenderStateKey, this, nullptr);
100 SVGAutoRenderState::~SVGAutoRenderState()
102 mContext->RemoveUserData(&sSVGAutoRenderStateKey);
103 if (mOriginalRenderState) {
104 mContext->AddUserData(&sSVGAutoRenderStateKey, mOriginalRenderState, nullptr);
108 void
109 SVGAutoRenderState::SetPaintingToWindow(bool aPaintingToWindow)
111 mPaintingToWindow = aPaintingToWindow;
114 /* static */ SVGAutoRenderState::RenderMode
115 SVGAutoRenderState::GetRenderMode(nsRenderingContext *aContext)
117 void *state = aContext->GetUserData(&sSVGAutoRenderStateKey);
118 if (state) {
119 return static_cast<SVGAutoRenderState*>(state)->mMode;
121 return NORMAL;
124 /* static */ bool
125 SVGAutoRenderState::IsPaintingToWindow(nsRenderingContext *aContext)
127 void *state = aContext->GetUserData(&sSVGAutoRenderStateKey);
128 if (state) {
129 return static_cast<SVGAutoRenderState*>(state)->mPaintingToWindow;
131 return false;
134 void
135 nsSVGUtils::Init()
137 Preferences::AddBoolVarCache(&sSVGDisplayListHitTestingEnabled,
138 "svg.display-lists.hit-testing.enabled");
140 Preferences::AddBoolVarCache(&sSVGDisplayListPaintingEnabled,
141 "svg.display-lists.painting.enabled");
143 Preferences::AddBoolVarCache(&sSVGNewGetBBoxEnabled,
144 "svg.new-getBBox.enabled");
147 nsSVGDisplayContainerFrame*
148 nsSVGUtils::GetNearestSVGViewport(nsIFrame *aFrame)
150 NS_ASSERTION(aFrame->IsFrameOfType(nsIFrame::eSVG), "SVG frame expected");
151 if (aFrame->GetType() == nsGkAtoms::svgOuterSVGFrame) {
152 return nullptr;
154 while ((aFrame = aFrame->GetParent())) {
155 NS_ASSERTION(aFrame->IsFrameOfType(nsIFrame::eSVG), "SVG frame expected");
156 if (aFrame->GetType() == nsGkAtoms::svgInnerSVGFrame ||
157 aFrame->GetType() == nsGkAtoms::svgOuterSVGFrame) {
158 return do_QueryFrame(aFrame);
161 NS_NOTREACHED("This is not reached. It's only needed to compile.");
162 return nullptr;
165 nsRect
166 nsSVGUtils::GetPostFilterVisualOverflowRect(nsIFrame *aFrame,
167 const nsRect &aPreFilterRect)
169 NS_ABORT_IF_FALSE(aFrame->GetStateBits() & NS_FRAME_SVG_LAYOUT,
170 "Called on invalid frame type");
172 nsSVGFilterProperty *property = nsSVGEffects::GetFilterProperty(aFrame);
173 if (!property || !property->ReferencesValidResources()) {
174 return aPreFilterRect;
177 return nsFilterInstance::GetPostFilterBounds(aFrame, nullptr, &aPreFilterRect);
180 bool
181 nsSVGUtils::OuterSVGIsCallingReflowSVG(nsIFrame *aFrame)
183 return GetOuterSVGFrame(aFrame)->IsCallingReflowSVG();
186 bool
187 nsSVGUtils::AnyOuterSVGIsCallingReflowSVG(nsIFrame* aFrame)
189 nsSVGOuterSVGFrame* outer = GetOuterSVGFrame(aFrame);
190 do {
191 if (outer->IsCallingReflowSVG()) {
192 return true;
194 outer = GetOuterSVGFrame(outer->GetParent());
195 } while (outer);
196 return false;
199 void
200 nsSVGUtils::ScheduleReflowSVG(nsIFrame *aFrame)
202 NS_ABORT_IF_FALSE(aFrame->IsFrameOfType(nsIFrame::eSVG),
203 "Passed bad frame!");
205 // If this is triggered, the callers should be fixed to call us before
206 // ReflowSVG is called. If we try to mark dirty bits on frames while we're
207 // in the process of removing them, things will get messed up.
208 NS_ASSERTION(!OuterSVGIsCallingReflowSVG(aFrame),
209 "Do not call under nsISVGChildFrame::ReflowSVG!");
211 // We don't call nsSVGEffects::InvalidateRenderingObservers here because
212 // we should only be called under InvalidateAndScheduleReflowSVG (which
213 // calls InvalidateBounds) or nsSVGDisplayContainerFrame::InsertFrames
214 // (at which point the frame has no observers).
216 if (aFrame->GetStateBits() & NS_FRAME_IS_NONDISPLAY) {
217 return;
220 if (aFrame->GetStateBits() &
221 (NS_FRAME_IS_DIRTY | NS_FRAME_FIRST_REFLOW)) {
222 // Nothing to do if we're already dirty, or if the outer-<svg>
223 // hasn't yet had its initial reflow.
224 return;
227 nsSVGOuterSVGFrame *outerSVGFrame = nullptr;
229 // We must not add dirty bits to the nsSVGOuterSVGFrame or else
230 // PresShell::FrameNeedsReflow won't work when we pass it in below.
231 if (aFrame->GetStateBits() & NS_STATE_IS_OUTER_SVG) {
232 outerSVGFrame = static_cast<nsSVGOuterSVGFrame*>(aFrame);
233 } else {
234 aFrame->AddStateBits(NS_FRAME_IS_DIRTY);
236 nsIFrame *f = aFrame->GetParent();
237 while (f && !(f->GetStateBits() & NS_STATE_IS_OUTER_SVG)) {
238 if (f->GetStateBits() &
239 (NS_FRAME_IS_DIRTY | NS_FRAME_HAS_DIRTY_CHILDREN)) {
240 return;
242 f->AddStateBits(NS_FRAME_HAS_DIRTY_CHILDREN);
243 f = f->GetParent();
244 NS_ABORT_IF_FALSE(f->IsFrameOfType(nsIFrame::eSVG),
245 "NS_STATE_IS_OUTER_SVG check above not valid!");
248 outerSVGFrame = static_cast<nsSVGOuterSVGFrame*>(f);
250 NS_ABORT_IF_FALSE(outerSVGFrame &&
251 outerSVGFrame->GetType() == nsGkAtoms::svgOuterSVGFrame,
252 "Did not find nsSVGOuterSVGFrame!");
255 if (outerSVGFrame->GetStateBits() & NS_FRAME_IN_REFLOW) {
256 // We're currently under an nsSVGOuterSVGFrame::Reflow call so there is no
257 // need to call PresShell::FrameNeedsReflow, since we have an
258 // nsSVGOuterSVGFrame::DidReflow call pending.
259 return;
262 nsFrameState dirtyBit =
263 (outerSVGFrame == aFrame ? NS_FRAME_IS_DIRTY : NS_FRAME_HAS_DIRTY_CHILDREN);
265 aFrame->PresContext()->PresShell()->FrameNeedsReflow(
266 outerSVGFrame, nsIPresShell::eResize, dirtyBit);
269 bool
270 nsSVGUtils::NeedsReflowSVG(nsIFrame *aFrame)
272 NS_ABORT_IF_FALSE(aFrame->IsFrameOfType(nsIFrame::eSVG),
273 "SVG uses bits differently!");
275 // The flags we test here may change, hence why we have this separate
276 // function.
277 return NS_SUBTREE_DIRTY(aFrame);
280 void
281 nsSVGUtils::NotifyAncestorsOfFilterRegionChange(nsIFrame *aFrame)
283 NS_ABORT_IF_FALSE(!(aFrame->GetStateBits() & NS_STATE_IS_OUTER_SVG),
284 "Not expecting to be called on the outer SVG Frame");
286 aFrame = aFrame->GetParent();
288 while (aFrame) {
289 if (aFrame->GetStateBits() & NS_STATE_IS_OUTER_SVG)
290 return;
292 nsSVGFilterProperty *property = nsSVGEffects::GetFilterProperty(aFrame);
293 if (property) {
294 property->Invalidate();
296 aFrame = aFrame->GetParent();
300 float
301 nsSVGUtils::ObjectSpace(const gfxRect &aRect, const nsSVGLength2 *aLength)
303 float axis;
305 switch (aLength->GetCtxType()) {
306 case SVGContentUtils::X:
307 axis = aRect.Width();
308 break;
309 case SVGContentUtils::Y:
310 axis = aRect.Height();
311 break;
312 case SVGContentUtils::XY:
313 axis = float(SVGContentUtils::ComputeNormalizedHypotenuse(
314 aRect.Width(), aRect.Height()));
315 break;
316 default:
317 NS_NOTREACHED("unexpected ctx type");
318 axis = 0.0f;
319 break;
321 if (aLength->IsPercentage()) {
322 // Multiply first to avoid precision errors:
323 return axis * aLength->GetAnimValInSpecifiedUnits() / 100;
325 return aLength->GetAnimValue(static_cast<SVGSVGElement*>(nullptr)) * axis;
328 float
329 nsSVGUtils::UserSpace(nsSVGElement *aSVGElement, const nsSVGLength2 *aLength)
331 return aLength->GetAnimValue(aSVGElement);
334 float
335 nsSVGUtils::UserSpace(nsIFrame *aNonSVGContext, const nsSVGLength2 *aLength)
337 return aLength->GetAnimValue(aNonSVGContext);
340 nsSVGOuterSVGFrame *
341 nsSVGUtils::GetOuterSVGFrame(nsIFrame *aFrame)
343 while (aFrame) {
344 if (aFrame->GetStateBits() & NS_STATE_IS_OUTER_SVG) {
345 return static_cast<nsSVGOuterSVGFrame*>(aFrame);
347 aFrame = aFrame->GetParent();
350 return nullptr;
353 nsIFrame*
354 nsSVGUtils::GetOuterSVGFrameAndCoveredRegion(nsIFrame* aFrame, nsRect* aRect)
356 nsISVGChildFrame* svg = do_QueryFrame(aFrame);
357 if (!svg)
358 return nullptr;
359 nsSVGOuterSVGFrame* outer = GetOuterSVGFrame(aFrame);
360 if (outer == svg) {
361 return nullptr;
363 *aRect = (aFrame->GetStateBits() & NS_FRAME_IS_NONDISPLAY) ?
364 nsRect(0, 0, 0, 0) : svg->GetCoveredRegion();
365 return outer;
368 gfxMatrix
369 nsSVGUtils::GetCanvasTM(nsIFrame *aFrame, uint32_t aFor,
370 nsIFrame* aTransformRoot)
372 // XXX yuck, we really need a common interface for GetCanvasTM
374 if (!aFrame->IsFrameOfType(nsIFrame::eSVG)) {
375 if (aFor == nsISVGChildFrame::FOR_HIT_TESTING &&
376 NS_SVGDisplayListHitTestingEnabled()) {
377 return gfxMatrix();
379 return nsSVGIntegrationUtils::GetCSSPxToDevPxMatrix(aFrame);
382 if (!(aFrame->GetStateBits() & NS_FRAME_IS_NONDISPLAY) &&
383 !aTransformRoot) {
384 if (aFor == nsISVGChildFrame::FOR_PAINTING &&
385 NS_SVGDisplayListPaintingEnabled()) {
386 return nsSVGIntegrationUtils::GetCSSPxToDevPxMatrix(aFrame);
388 if (aFor == nsISVGChildFrame::FOR_HIT_TESTING &&
389 NS_SVGDisplayListHitTestingEnabled()) {
390 return gfxMatrix();
394 nsIAtom* type = aFrame->GetType();
395 if (type == nsGkAtoms::svgForeignObjectFrame) {
396 return static_cast<nsSVGForeignObjectFrame*>(aFrame)->
397 GetCanvasTM(aFor, aTransformRoot);
399 if (type == nsGkAtoms::svgOuterSVGFrame) {
400 return nsSVGIntegrationUtils::GetCSSPxToDevPxMatrix(aFrame);
403 nsSVGContainerFrame *containerFrame = do_QueryFrame(aFrame);
404 if (containerFrame) {
405 return containerFrame->GetCanvasTM(aFor, aTransformRoot);
408 return static_cast<nsSVGPathGeometryFrame*>(aFrame)->
409 GetCanvasTM(aFor, aTransformRoot);
412 gfxMatrix
413 nsSVGUtils::GetUserToCanvasTM(nsIFrame *aFrame, uint32_t aFor)
415 NS_ASSERTION(aFor == nsISVGChildFrame::FOR_OUTERSVG_TM,
416 "Unexpected aFor?");
418 nsISVGChildFrame* svgFrame = do_QueryFrame(aFrame);
419 NS_ASSERTION(svgFrame, "bad frame");
421 gfxMatrix tm;
422 if (svgFrame) {
423 nsSVGElement *content = static_cast<nsSVGElement*>(aFrame->GetContent());
424 tm = content->PrependLocalTransformsTo(
425 GetCanvasTM(aFrame->GetParent(), aFor),
426 nsSVGElement::eUserSpaceToParent);
428 return tm;
431 void
432 nsSVGUtils::NotifyChildrenOfSVGChange(nsIFrame *aFrame, uint32_t aFlags)
434 nsIFrame *kid = aFrame->GetFirstPrincipalChild();
436 while (kid) {
437 nsISVGChildFrame* SVGFrame = do_QueryFrame(kid);
438 if (SVGFrame) {
439 SVGFrame->NotifySVGChanged(aFlags);
440 } else {
441 NS_ASSERTION(kid->IsFrameOfType(nsIFrame::eSVG) || kid->IsSVGText(),
442 "SVG frame expected");
443 // recurse into the children of container frames e.g. <clipPath>, <mask>
444 // in case they have child frames with transformation matrices
445 if (kid->IsFrameOfType(nsIFrame::eSVG)) {
446 NotifyChildrenOfSVGChange(kid, aFlags);
449 kid = kid->GetNextSibling();
453 // ************************************************************
455 class SVGPaintCallback : public nsSVGFilterPaintCallback
457 public:
458 virtual void Paint(nsRenderingContext *aContext, nsIFrame *aTarget,
459 const nsIntRect* aDirtyRect,
460 nsIFrame* aTransformRoot) MOZ_OVERRIDE
462 nsISVGChildFrame *svgChildFrame = do_QueryFrame(aTarget);
463 NS_ASSERTION(svgChildFrame, "Expected SVG frame here");
465 nsIntRect* dirtyRect = nullptr;
466 nsIntRect tmpDirtyRect;
468 // aDirtyRect is in user-space pixels, we need to convert to
469 // outer-SVG-frame-relative device pixels.
470 if (aDirtyRect) {
471 gfxMatrix userToDeviceSpace =
472 nsSVGUtils::GetCanvasTM(aTarget, nsISVGChildFrame::FOR_PAINTING, aTransformRoot);
473 if (userToDeviceSpace.IsSingular()) {
474 return;
476 gfxRect dirtyBounds = userToDeviceSpace.TransformBounds(
477 gfxRect(aDirtyRect->x, aDirtyRect->y, aDirtyRect->width, aDirtyRect->height));
478 dirtyBounds.RoundOut();
479 if (gfxUtils::GfxRectToIntRect(dirtyBounds, &tmpDirtyRect)) {
480 dirtyRect = &tmpDirtyRect;
484 svgChildFrame->PaintSVG(aContext, dirtyRect, aTransformRoot);
488 void
489 nsSVGUtils::PaintFrameWithEffects(nsRenderingContext *aContext,
490 const nsIntRect *aDirtyRect,
491 nsIFrame *aFrame,
492 nsIFrame *aTransformRoot)
494 NS_ASSERTION(!NS_SVGDisplayListPaintingEnabled() ||
495 (aFrame->GetStateBits() & NS_FRAME_IS_NONDISPLAY) ||
496 aFrame->PresContext()->IsGlyph(),
497 "If display lists are enabled, only painting of non-display "
498 "SVG should take this code path");
500 nsISVGChildFrame *svgChildFrame = do_QueryFrame(aFrame);
501 if (!svgChildFrame)
502 return;
504 float opacity = aFrame->StyleDisplay()->mOpacity;
505 if (opacity == 0.0f)
506 return;
508 const nsIContent* content = aFrame->GetContent();
509 if (content->IsSVG() &&
510 !static_cast<const nsSVGElement*>(content)->HasValidDimensions()) {
511 return;
514 /* Properties are added lazily and may have been removed by a restyle,
515 so make sure all applicable ones are set again. */
517 nsSVGEffects::EffectProperties effectProperties =
518 nsSVGEffects::GetEffectProperties(aFrame);
520 bool isOK = effectProperties.HasNoFilterOrHasValidFilter();
522 if (aDirtyRect &&
523 !(aFrame->GetStateBits() & NS_FRAME_IS_NONDISPLAY)) {
524 // Here we convert aFrame's paint bounds to outer-<svg> device space,
525 // compare it to aDirtyRect, and return early if they don't intersect.
526 // We don't do this optimization for nondisplay SVG since nondisplay
527 // SVG doesn't maintain bounds/overflow rects.
528 nsRect overflowRect = aFrame->GetVisualOverflowRectRelativeToSelf();
529 if (aFrame->IsFrameOfType(nsIFrame::eSVGGeometry) ||
530 aFrame->IsSVGText()) {
531 // Unlike containers, leaf frames do not include GetPosition() in
532 // GetCanvasTM().
533 overflowRect = overflowRect + aFrame->GetPosition();
535 int32_t appUnitsPerDevPx = aFrame->PresContext()->AppUnitsPerDevPixel();
536 gfxMatrix tm = GetCanvasTM(aFrame, nsISVGChildFrame::FOR_PAINTING, aTransformRoot);
537 if (aFrame->IsFrameOfType(nsIFrame::eSVG | nsIFrame::eSVGContainer)) {
538 gfx::Matrix childrenOnlyTM;
539 if (static_cast<nsSVGContainerFrame*>(aFrame)->
540 HasChildrenOnlyTransform(&childrenOnlyTM)) {
541 // Undo the children-only transform:
542 if (!childrenOnlyTM.Invert()) {
543 return;
545 tm = ThebesMatrix(childrenOnlyTM) * tm;
548 nsIntRect bounds = TransformFrameRectToOuterSVG(overflowRect,
549 tm, aFrame->PresContext()).
550 ToOutsidePixels(appUnitsPerDevPx);
551 if (!aDirtyRect->Intersects(bounds)) {
552 return;
556 /* SVG defines the following rendering model:
558 * 1. Render fill
559 * 2. Render stroke
560 * 3. Render markers
561 * 4. Apply filter
562 * 5. Apply clipping, masking, group opacity
564 * We follow this, but perform a couple of optimizations:
566 * + Use cairo's clipPath when representable natively (single object
567 * clip region).
569 * + Merge opacity and masking if both used together.
572 if (opacity != 1.0f && CanOptimizeOpacity(aFrame))
573 opacity = 1.0f;
575 gfxContext *gfx = aContext->ThebesContext();
576 bool complexEffects = false;
578 nsSVGClipPathFrame *clipPathFrame = effectProperties.GetClipPathFrame(&isOK);
579 nsSVGMaskFrame *maskFrame = effectProperties.GetMaskFrame(&isOK);
581 bool isTrivialClip = clipPathFrame ? clipPathFrame->IsTrivial() : true;
583 if (!isOK) {
584 // Some resource is invalid. We shouldn't paint anything.
585 return;
588 gfxMatrix matrix;
589 if (clipPathFrame || maskFrame)
590 matrix = GetCanvasTM(aFrame, nsISVGChildFrame::FOR_PAINTING, aTransformRoot);
592 /* Check if we need to do additional operations on this child's
593 * rendering, which necessitates rendering into another surface. */
594 if (opacity != 1.0f || maskFrame || (clipPathFrame && !isTrivialClip)
595 || aFrame->StyleDisplay()->mMixBlendMode != NS_STYLE_BLEND_NORMAL) {
596 complexEffects = true;
597 gfx->Save();
598 if (!(aFrame->GetStateBits() & NS_FRAME_IS_NONDISPLAY)) {
599 // aFrame has a valid visual overflow rect, so clip to it before calling
600 // PushGroup() to minimize the size of the surfaces we'll composite:
601 gfxContextMatrixAutoSaveRestore matrixAutoSaveRestore(gfx);
602 gfx->Multiply(GetCanvasTM(aFrame, nsISVGChildFrame::FOR_PAINTING, aTransformRoot));
603 nsRect overflowRect = aFrame->GetVisualOverflowRectRelativeToSelf();
604 if (aFrame->IsFrameOfType(nsIFrame::eSVGGeometry) ||
605 aFrame->IsSVGText()) {
606 // Unlike containers, leaf frames do not include GetPosition() in
607 // GetCanvasTM().
608 overflowRect = overflowRect + aFrame->GetPosition();
610 aContext->IntersectClip(overflowRect);
612 gfx->PushGroup(gfxContentType::COLOR_ALPHA);
615 /* If this frame has only a trivial clipPath, set up cairo's clipping now so
616 * we can just do normal painting and get it clipped appropriately.
618 if (clipPathFrame && isTrivialClip) {
619 gfx->Save();
620 clipPathFrame->ClipPaint(aContext, aFrame, matrix);
623 /* Paint the child */
624 if (effectProperties.HasValidFilter()) {
625 nsRegion* dirtyRegion = nullptr;
626 nsRegion tmpDirtyRegion;
627 if (aDirtyRect) {
628 // aDirtyRect is in outer-<svg> device pixels, but the filter code needs
629 // it in frame space.
630 gfxMatrix userToDeviceSpace =
631 GetUserToCanvasTM(aFrame, nsISVGChildFrame::FOR_OUTERSVG_TM);
632 if (userToDeviceSpace.IsSingular()) {
633 return;
635 gfxMatrix deviceToUserSpace = userToDeviceSpace;
636 deviceToUserSpace.Invert();
637 gfxRect dirtyBounds = deviceToUserSpace.TransformBounds(
638 gfxRect(aDirtyRect->x, aDirtyRect->y,
639 aDirtyRect->width, aDirtyRect->height));
640 tmpDirtyRegion =
641 nsLayoutUtils::RoundGfxRectToAppRect(
642 dirtyBounds, aFrame->PresContext()->AppUnitsPerCSSPixel()) -
643 aFrame->GetPosition();
644 dirtyRegion = &tmpDirtyRegion;
646 SVGPaintCallback paintCallback;
647 nsFilterInstance::PaintFilteredFrame(aContext, aFrame, &paintCallback,
648 dirtyRegion, aTransformRoot);
649 } else {
650 svgChildFrame->PaintSVG(aContext, aDirtyRect, aTransformRoot);
653 if (clipPathFrame && isTrivialClip) {
654 gfx->Restore();
657 /* No more effects, we're done. */
658 if (!complexEffects)
659 return;
661 gfx->PopGroupToSource();
663 nsRefPtr<gfxPattern> maskSurface =
664 maskFrame ? maskFrame->GetMaskForMaskedFrame(aContext->ThebesContext(),
665 aFrame, matrix, opacity)
666 : nullptr;
668 nsRefPtr<gfxPattern> clipMaskSurface;
669 if (clipPathFrame && !isTrivialClip) {
670 gfx->PushGroup(gfxContentType::COLOR_ALPHA);
672 nsresult rv = clipPathFrame->ClipPaint(aContext, aFrame, matrix);
673 clipMaskSurface = gfx->PopGroup();
675 if (NS_SUCCEEDED(rv) && clipMaskSurface) {
676 // Still more set after clipping, so clip to another surface
677 if (maskSurface || opacity != 1.0f) {
678 gfx->PushGroup(gfxContentType::COLOR_ALPHA);
679 gfx->Mask(clipMaskSurface);
680 gfx->PopGroupToSource();
681 } else {
682 gfx->Mask(clipMaskSurface);
687 if (maskSurface) {
688 gfx->Mask(maskSurface);
689 } else if (opacity != 1.0f) {
690 gfx->Paint(opacity);
693 gfx->Restore();
696 bool
697 nsSVGUtils::HitTestClip(nsIFrame *aFrame, const nsPoint &aPoint)
699 nsSVGEffects::EffectProperties props =
700 nsSVGEffects::GetEffectProperties(aFrame);
701 if (!props.mClipPath)
702 return true;
704 bool isOK = true;
705 nsSVGClipPathFrame *clipPathFrame = props.GetClipPathFrame(&isOK);
706 if (!isOK) {
707 // clipPath is not a valid resource, so nothing gets painted, so
708 // hit-testing must fail.
709 return false;
711 if (!clipPathFrame) {
712 // clipPath doesn't exist, ignore it.
713 return true;
716 return clipPathFrame->ClipHitTest(aFrame, GetCanvasTM(aFrame,
717 nsISVGChildFrame::FOR_HIT_TESTING), aPoint);
720 nsIFrame *
721 nsSVGUtils::HitTestChildren(nsIFrame *aFrame, const nsPoint &aPoint)
723 // Traverse the list in reverse order, so that if we get a hit we know that's
724 // the topmost frame that intersects the point; then we can just return it.
725 nsIFrame* result = nullptr;
726 for (nsIFrame* current = aFrame->PrincipalChildList().LastChild();
727 current;
728 current = current->GetPrevSibling()) {
729 nsISVGChildFrame* SVGFrame = do_QueryFrame(current);
730 if (SVGFrame) {
731 const nsIContent* content = current->GetContent();
732 if (content->IsSVG() &&
733 !static_cast<const nsSVGElement*>(content)->HasValidDimensions()) {
734 continue;
736 result = SVGFrame->GetFrameForPoint(aPoint);
737 if (result)
738 break;
742 if (result && !HitTestClip(aFrame, aPoint))
743 result = nullptr;
745 return result;
748 nsRect
749 nsSVGUtils::GetCoveredRegion(const nsFrameList &aFrames)
751 nsRect rect;
753 for (nsIFrame* kid = aFrames.FirstChild();
754 kid;
755 kid = kid->GetNextSibling()) {
756 nsISVGChildFrame* child = do_QueryFrame(kid);
757 if (child) {
758 nsRect childRect = child->GetCoveredRegion();
759 rect.UnionRect(rect, childRect);
763 return rect;
766 nsPoint
767 nsSVGUtils::TransformOuterSVGPointToChildFrame(nsPoint aPoint,
768 const gfxMatrix& aFrameToCanvasTM,
769 nsPresContext* aPresContext)
771 NS_ABORT_IF_FALSE(!aFrameToCanvasTM.IsSingular(),
772 "Callers must not pass a singular matrix");
773 gfxMatrix canvasDevToFrameUserSpace = aFrameToCanvasTM;
774 canvasDevToFrameUserSpace.Invert();
775 gfxPoint cssPxPt =
776 gfxPoint(aPoint.x, aPoint.y) / aPresContext->AppUnitsPerCSSPixel();
777 gfxPoint userPt = canvasDevToFrameUserSpace.Transform(cssPxPt);
778 gfxPoint appPt = (userPt * aPresContext->AppUnitsPerCSSPixel()).Round();
779 userPt.x = clamped(appPt.x, gfxFloat(nscoord_MIN), gfxFloat(nscoord_MAX));
780 userPt.y = clamped(appPt.y, gfxFloat(nscoord_MIN), gfxFloat(nscoord_MAX));
781 // now guaranteed to be safe:
782 return nsPoint(nscoord(userPt.x), nscoord(userPt.y));
785 nsRect
786 nsSVGUtils::TransformFrameRectToOuterSVG(const nsRect& aRect,
787 const gfxMatrix& aMatrix,
788 nsPresContext* aPresContext)
790 gfxRect r(aRect.x, aRect.y, aRect.width, aRect.height);
791 r.Scale(1.0 / nsPresContext::AppUnitsPerCSSPixel());
792 return nsLayoutUtils::RoundGfxRectToAppRect(
793 aMatrix.TransformBounds(r), aPresContext->AppUnitsPerDevPixel());
796 gfxIntSize
797 nsSVGUtils::ConvertToSurfaceSize(const gfxSize& aSize,
798 bool *aResultOverflows)
800 gfxIntSize surfaceSize(ClampToInt(ceil(aSize.width)), ClampToInt(ceil(aSize.height)));
802 *aResultOverflows = surfaceSize.width != ceil(aSize.width) ||
803 surfaceSize.height != ceil(aSize.height);
805 if (!gfxASurface::CheckSurfaceSize(surfaceSize)) {
806 surfaceSize.width = std::min(NS_SVG_OFFSCREEN_MAX_DIMENSION,
807 surfaceSize.width);
808 surfaceSize.height = std::min(NS_SVG_OFFSCREEN_MAX_DIMENSION,
809 surfaceSize.height);
810 *aResultOverflows = true;
813 return surfaceSize;
816 bool
817 nsSVGUtils::HitTestRect(const gfx::Matrix &aMatrix,
818 float aRX, float aRY, float aRWidth, float aRHeight,
819 float aX, float aY)
821 gfx::Rect rect(aRX, aRY, aRWidth, aRHeight);
822 if (rect.IsEmpty() || aMatrix.IsSingular()) {
823 return false;
825 gfx::Matrix toRectSpace = aMatrix;
826 toRectSpace.Invert();
827 gfx::Point p = toRectSpace * gfx::Point(aX, aY);
828 return rect.x <= p.x && p.x <= rect.XMost() &&
829 rect.y <= p.y && p.y <= rect.YMost();
832 gfxRect
833 nsSVGUtils::GetClipRectForFrame(nsIFrame *aFrame,
834 float aX, float aY, float aWidth, float aHeight)
836 const nsStyleDisplay* disp = aFrame->StyleDisplay();
838 if (!(disp->mClipFlags & NS_STYLE_CLIP_RECT)) {
839 NS_ASSERTION(disp->mClipFlags == NS_STYLE_CLIP_AUTO,
840 "We don't know about this type of clip.");
841 return gfxRect(aX, aY, aWidth, aHeight);
844 if (disp->mOverflowX == NS_STYLE_OVERFLOW_HIDDEN ||
845 disp->mOverflowY == NS_STYLE_OVERFLOW_HIDDEN) {
847 nsIntRect clipPxRect =
848 disp->mClip.ToOutsidePixels(aFrame->PresContext()->AppUnitsPerDevPixel());
849 gfxRect clipRect =
850 gfxRect(clipPxRect.x, clipPxRect.y, clipPxRect.width, clipPxRect.height);
852 if (NS_STYLE_CLIP_RIGHT_AUTO & disp->mClipFlags) {
853 clipRect.width = aWidth - clipRect.X();
855 if (NS_STYLE_CLIP_BOTTOM_AUTO & disp->mClipFlags) {
856 clipRect.height = aHeight - clipRect.Y();
859 if (disp->mOverflowX != NS_STYLE_OVERFLOW_HIDDEN) {
860 clipRect.x = aX;
861 clipRect.width = aWidth;
863 if (disp->mOverflowY != NS_STYLE_OVERFLOW_HIDDEN) {
864 clipRect.y = aY;
865 clipRect.height = aHeight;
868 return clipRect;
870 return gfxRect(aX, aY, aWidth, aHeight);
873 void
874 nsSVGUtils::SetClipRect(gfxContext *aContext,
875 const gfxMatrix &aCTM,
876 const gfxRect &aRect)
878 if (aCTM.IsSingular())
879 return;
881 gfxContextMatrixAutoSaveRestore matrixAutoSaveRestore(aContext);
882 aContext->Multiply(aCTM);
883 aContext->Clip(aRect);
886 gfxRect
887 nsSVGUtils::GetBBox(nsIFrame *aFrame, uint32_t aFlags)
889 if (aFrame->GetContent()->IsNodeOfType(nsINode::eTEXT)) {
890 aFrame = aFrame->GetParent();
892 gfxRect bbox;
893 nsISVGChildFrame *svg = do_QueryFrame(aFrame);
894 if (svg || aFrame->IsSVGText()) {
895 // It is possible to apply a gradient, pattern, clipping path, mask or
896 // filter to text. When one of these facilities is applied to text
897 // the bounding box is the entire text element in all
898 // cases.
899 if (aFrame->IsSVGText()) {
900 nsIFrame* ancestor = GetFirstNonAAncestorFrame(aFrame);
901 if (ancestor && ancestor->IsSVGText()) {
902 while (ancestor->GetType() != nsGkAtoms::svgTextFrame) {
903 ancestor = ancestor->GetParent();
906 svg = do_QueryFrame(ancestor);
908 nsIContent* content = aFrame->GetContent();
909 if (content->IsSVG() &&
910 !static_cast<const nsSVGElement*>(content)->HasValidDimensions()) {
911 return bbox;
913 gfxMatrix matrix;
914 if (aFrame->GetType() == nsGkAtoms::svgForeignObjectFrame ||
915 aFrame->GetType() == nsGkAtoms::svgUseFrame) {
916 // The spec says getBBox "Returns the tight bounding box in *current user
917 // space*". So we should really be doing this for all elements, but that
918 // needs investigation to check that we won't break too much content.
919 // NOTE: When changing this to apply to other frame types, make sure to
920 // also update nsSVGUtils::FrameSpaceInCSSPxToUserSpaceOffset.
921 NS_ABORT_IF_FALSE(content->IsSVG(), "bad cast");
922 nsSVGElement *element = static_cast<nsSVGElement*>(content);
923 matrix = element->PrependLocalTransformsTo(matrix,
924 nsSVGElement::eChildToUserSpace);
926 bbox = svg->GetBBoxContribution(ToMatrix(matrix), aFlags).ToThebesRect();
927 // Account for 'clipped'.
928 if (aFlags & nsSVGUtils::eBBoxIncludeClipped) {
929 gfxRect clipRect(0, 0, 0, 0);
930 float x, y, width, height;
931 gfxMatrix tm;
932 gfxRect fillBBox =
933 svg->GetBBoxContribution(ToMatrix(tm),
934 nsSVGUtils::eBBoxIncludeFill).ToThebesRect();
935 x = fillBBox.x;
936 y = fillBBox.y;
937 width = fillBBox.width;
938 height = fillBBox.height;
939 bool hasClip = aFrame->StyleDisplay()->IsScrollableOverflow();
940 if (hasClip) {
941 clipRect =
942 nsSVGUtils::GetClipRectForFrame(aFrame, x, y, width, height);
943 if (aFrame->GetType() == nsGkAtoms::svgForeignObjectFrame ||
944 aFrame->GetType() == nsGkAtoms::svgUseFrame) {
945 clipRect = matrix.TransformBounds(clipRect);
948 nsSVGEffects::EffectProperties effectProperties =
949 nsSVGEffects::GetEffectProperties(aFrame);
950 bool isOK = true;
951 nsSVGClipPathFrame *clipPathFrame =
952 effectProperties.GetClipPathFrame(&isOK);
953 if (clipPathFrame && isOK) {
954 SVGClipPathElement *clipContent =
955 static_cast<SVGClipPathElement*>(clipPathFrame->GetContent());
956 nsRefPtr<SVGAnimatedEnumeration> units = clipContent->ClipPathUnits();
957 if (units->AnimVal() == SVG_UNIT_TYPE_OBJECTBOUNDINGBOX) {
958 matrix = gfxMatrix().Scale(width, height) *
959 gfxMatrix().Translate(gfxPoint(x, y)) *
960 matrix;
961 } else if (aFrame->GetType() == nsGkAtoms::svgForeignObjectFrame) {
962 matrix.Reset();
964 bbox =
965 clipPathFrame->GetBBoxForClipPathFrame(bbox, matrix).ToThebesRect();
966 if (hasClip) {
967 bbox = bbox.Intersect(clipRect);
969 } else {
970 if (!isOK) {
971 bbox = gfxRect(0, 0, 0, 0);
972 } else {
973 if (hasClip) {
974 bbox = bbox.Intersect(clipRect);
978 if (bbox.IsEmpty()) {
979 bbox = gfxRect(0, 0, 0, 0);
982 return bbox;
984 return nsSVGIntegrationUtils::GetSVGBBoxForNonSVGFrame(aFrame);
987 gfxPoint
988 nsSVGUtils::FrameSpaceInCSSPxToUserSpaceOffset(nsIFrame *aFrame)
990 if (!(aFrame->GetStateBits() & NS_FRAME_SVG_LAYOUT)) {
991 // The user space for non-SVG frames is defined as the bounding box of the
992 // frame's border-box rects over all continuations.
993 return gfxPoint();
996 // Leaf frames apply their own offset inside their user space.
997 if (aFrame->IsFrameOfType(nsIFrame::eSVGGeometry) ||
998 aFrame->IsSVGText()) {
999 return nsLayoutUtils::RectToGfxRect(aFrame->GetRect(),
1000 nsPresContext::AppUnitsPerCSSPixel()).TopLeft();
1003 // For foreignObject frames, nsSVGUtils::GetBBox applies their local
1004 // transform, so we need to do the same here.
1005 if (aFrame->GetType() == nsGkAtoms::svgForeignObjectFrame ||
1006 aFrame->GetType() == nsGkAtoms::svgUseFrame) {
1007 gfxMatrix transform = static_cast<nsSVGElement*>(aFrame->GetContent())->
1008 PrependLocalTransformsTo(gfxMatrix(),
1009 nsSVGElement::eChildToUserSpace);
1010 NS_ASSERTION(!transform.HasNonTranslation(), "we're relying on this being an offset-only transform");
1011 return transform.GetTranslation();
1014 return gfxPoint();
1017 gfxRect
1018 nsSVGUtils::GetRelativeRect(uint16_t aUnits, const nsSVGLength2 *aXYWH,
1019 const gfxRect &aBBox, nsIFrame *aFrame)
1021 float x, y, width, height;
1022 if (aUnits == SVG_UNIT_TYPE_OBJECTBOUNDINGBOX) {
1023 x = aBBox.X() + ObjectSpace(aBBox, &aXYWH[0]);
1024 y = aBBox.Y() + ObjectSpace(aBBox, &aXYWH[1]);
1025 width = ObjectSpace(aBBox, &aXYWH[2]);
1026 height = ObjectSpace(aBBox, &aXYWH[3]);
1027 } else {
1028 x = UserSpace(aFrame, &aXYWH[0]);
1029 y = UserSpace(aFrame, &aXYWH[1]);
1030 width = UserSpace(aFrame, &aXYWH[2]);
1031 height = UserSpace(aFrame, &aXYWH[3]);
1033 return gfxRect(x, y, width, height);
1036 bool
1037 nsSVGUtils::CanOptimizeOpacity(nsIFrame *aFrame)
1039 if (!(aFrame->GetStateBits() & NS_FRAME_SVG_LAYOUT)) {
1040 return false;
1042 nsIAtom *type = aFrame->GetType();
1043 if (type != nsGkAtoms::svgImageFrame &&
1044 type != nsGkAtoms::svgPathGeometryFrame) {
1045 return false;
1047 if (aFrame->StyleSVGReset()->HasFilters()) {
1048 return false;
1050 // XXX The SVG WG is intending to allow fill, stroke and markers on <image>
1051 if (type == nsGkAtoms::svgImageFrame) {
1052 return true;
1054 const nsStyleSVG *style = aFrame->StyleSVG();
1055 if (style->HasMarker()) {
1056 return false;
1058 if (!style->HasFill() || !HasStroke(aFrame)) {
1059 return true;
1061 return false;
1064 gfxMatrix
1065 nsSVGUtils::AdjustMatrixForUnits(const gfxMatrix &aMatrix,
1066 nsSVGEnum *aUnits,
1067 nsIFrame *aFrame)
1069 if (aFrame &&
1070 aUnits->GetAnimValue() == SVG_UNIT_TYPE_OBJECTBOUNDINGBOX) {
1071 gfxRect bbox = GetBBox(aFrame);
1072 return gfxMatrix().Scale(bbox.Width(), bbox.Height()) *
1073 gfxMatrix().Translate(gfxPoint(bbox.X(), bbox.Y())) *
1074 aMatrix;
1076 return aMatrix;
1079 nsIFrame*
1080 nsSVGUtils::GetFirstNonAAncestorFrame(nsIFrame* aStartFrame)
1082 for (nsIFrame *ancestorFrame = aStartFrame; ancestorFrame;
1083 ancestorFrame = ancestorFrame->GetParent()) {
1084 if (ancestorFrame->GetType() != nsGkAtoms::svgAFrame) {
1085 return ancestorFrame;
1088 return nullptr;
1091 gfxMatrix
1092 nsSVGUtils::GetStrokeTransform(nsIFrame *aFrame)
1094 if (aFrame->GetContent()->IsNodeOfType(nsINode::eTEXT)) {
1095 aFrame = aFrame->GetParent();
1098 if (aFrame->StyleSVGReset()->mVectorEffect ==
1099 NS_STYLE_VECTOR_EFFECT_NON_SCALING_STROKE) {
1101 nsIContent *content = aFrame->GetContent();
1102 NS_ABORT_IF_FALSE(content->IsSVG(), "bad cast");
1104 // a non-scaling stroke is in the screen co-ordinate
1105 // space rather so we need to invert the transform
1106 // to the screen co-ordinate space to get there.
1107 // See http://www.w3.org/TR/SVGTiny12/painting.html#NonScalingStroke
1108 gfx::Matrix transform = SVGContentUtils::GetCTM(
1109 static_cast<nsSVGElement*>(content), true);
1110 if (!transform.IsSingular()) {
1111 transform.Invert();
1112 return ThebesMatrix(transform);
1115 return gfxMatrix();
1118 // The logic here comes from _cairo_stroke_style_max_distance_from_path
1119 static gfxRect
1120 PathExtentsToMaxStrokeExtents(const gfxRect& aPathExtents,
1121 nsIFrame* aFrame,
1122 double aStyleExpansionFactor,
1123 const gfxMatrix& aMatrix)
1125 double style_expansion =
1126 aStyleExpansionFactor * nsSVGUtils::GetStrokeWidth(aFrame);
1128 gfxMatrix matrix = aMatrix * nsSVGUtils::GetStrokeTransform(aFrame);
1130 double dx = style_expansion * (fabs(matrix._11) + fabs(matrix._21));
1131 double dy = style_expansion * (fabs(matrix._22) + fabs(matrix._12));
1133 gfxRect strokeExtents = aPathExtents;
1134 strokeExtents.Inflate(dx, dy);
1135 return strokeExtents;
1138 /*static*/ gfxRect
1139 nsSVGUtils::PathExtentsToMaxStrokeExtents(const gfxRect& aPathExtents,
1140 nsTextFrame* aFrame,
1141 const gfxMatrix& aMatrix)
1143 NS_ASSERTION(aFrame->IsSVGText(), "expected an nsTextFrame for SVG text");
1144 return ::PathExtentsToMaxStrokeExtents(aPathExtents, aFrame, 0.5, aMatrix);
1147 /*static*/ gfxRect
1148 nsSVGUtils::PathExtentsToMaxStrokeExtents(const gfxRect& aPathExtents,
1149 nsSVGPathGeometryFrame* aFrame,
1150 const gfxMatrix& aMatrix)
1152 double styleExpansionFactor = 0.5;
1154 if (static_cast<nsSVGPathGeometryElement*>(aFrame->GetContent())->IsMarkable()) {
1155 const nsStyleSVG* style = aFrame->StyleSVG();
1157 if (style->mStrokeLinecap == NS_STYLE_STROKE_LINECAP_SQUARE) {
1158 styleExpansionFactor = M_SQRT1_2;
1161 if (style->mStrokeLinejoin == NS_STYLE_STROKE_LINEJOIN_MITER &&
1162 styleExpansionFactor < style->mStrokeMiterlimit &&
1163 aFrame->GetContent()->Tag() != nsGkAtoms::line) {
1164 styleExpansionFactor = style->mStrokeMiterlimit;
1168 return ::PathExtentsToMaxStrokeExtents(aPathExtents,
1169 aFrame,
1170 styleExpansionFactor,
1171 aMatrix);
1174 // ----------------------------------------------------------------------
1176 /* static */ nscolor
1177 nsSVGUtils::GetFallbackOrPaintColor(gfxContext *aContext, nsStyleContext *aStyleContext,
1178 nsStyleSVGPaint nsStyleSVG::*aFillOrStroke)
1180 const nsStyleSVGPaint &paint = aStyleContext->StyleSVG()->*aFillOrStroke;
1181 nsStyleContext *styleIfVisited = aStyleContext->GetStyleIfVisited();
1182 bool isServer = paint.mType == eStyleSVGPaintType_Server ||
1183 paint.mType == eStyleSVGPaintType_ContextFill ||
1184 paint.mType == eStyleSVGPaintType_ContextStroke;
1185 nscolor color = isServer ? paint.mFallbackColor : paint.mPaint.mColor;
1186 if (styleIfVisited) {
1187 const nsStyleSVGPaint &paintIfVisited =
1188 styleIfVisited->StyleSVG()->*aFillOrStroke;
1189 // To prevent Web content from detecting if a user has visited a URL
1190 // (via URL loading triggered by paint servers or performance
1191 // differences between paint servers or between a paint server and a
1192 // color), we do not allow whether links are visited to change which
1193 // paint server is used or switch between paint servers and simple
1194 // colors. A :visited style may only override a simple color with
1195 // another simple color.
1196 if (paintIfVisited.mType == eStyleSVGPaintType_Color &&
1197 paint.mType == eStyleSVGPaintType_Color) {
1198 nscolor colors[2] = { color, paintIfVisited.mPaint.mColor };
1199 return nsStyleContext::CombineVisitedColors(
1200 colors, aStyleContext->RelevantLinkVisited());
1203 return color;
1206 static void
1207 SetupFallbackOrPaintColor(gfxContext *aContext, nsStyleContext *aStyleContext,
1208 nsStyleSVGPaint nsStyleSVG::*aFillOrStroke,
1209 float aOpacity)
1211 nscolor color = nsSVGUtils::GetFallbackOrPaintColor(
1212 aContext, aStyleContext, aFillOrStroke);
1214 aContext->SetColor(gfxRGBA(NS_GET_R(color)/255.0,
1215 NS_GET_G(color)/255.0,
1216 NS_GET_B(color)/255.0,
1217 NS_GET_A(color)/255.0 * aOpacity));
1220 static float
1221 MaybeOptimizeOpacity(nsIFrame *aFrame, float aFillOrStrokeOpacity)
1223 float opacity = aFrame->StyleDisplay()->mOpacity;
1224 if (opacity < 1 && nsSVGUtils::CanOptimizeOpacity(aFrame)) {
1225 return aFillOrStrokeOpacity * opacity;
1227 return aFillOrStrokeOpacity;
1230 /* static */ bool
1231 nsSVGUtils::SetupContextPaint(gfxContext *aContext,
1232 gfxTextContextPaint *aContextPaint,
1233 const nsStyleSVGPaint &aPaint,
1234 float aOpacity)
1236 nsRefPtr<gfxPattern> pattern;
1238 if (!aContextPaint) {
1239 return false;
1242 switch (aPaint.mType) {
1243 case eStyleSVGPaintType_ContextFill:
1244 pattern = aContextPaint->GetFillPattern(aOpacity, aContext->CurrentMatrix());
1245 break;
1246 case eStyleSVGPaintType_ContextStroke:
1247 pattern = aContextPaint->GetStrokePattern(aOpacity, aContext->CurrentMatrix());
1248 break;
1249 default:
1250 return false;
1253 if (!pattern) {
1254 return false;
1257 aContext->SetPattern(pattern);
1259 return true;
1262 bool
1263 nsSVGUtils::SetupCairoFillPaint(nsIFrame *aFrame, gfxContext* aContext,
1264 gfxTextContextPaint *aContextPaint)
1266 const nsStyleSVG* style = aFrame->StyleSVG();
1267 if (style->mFill.mType == eStyleSVGPaintType_None)
1268 return false;
1270 if (style->mFillRule == NS_STYLE_FILL_RULE_EVENODD)
1271 aContext->SetFillRule(gfxContext::FILL_RULE_EVEN_ODD);
1272 else
1273 aContext->SetFillRule(gfxContext::FILL_RULE_WINDING);
1275 float opacity = MaybeOptimizeOpacity(aFrame,
1276 GetOpacity(style->mFillOpacitySource,
1277 style->mFillOpacity,
1278 aContextPaint));
1279 nsSVGPaintServerFrame *ps =
1280 nsSVGEffects::GetPaintServer(aFrame, &style->mFill, nsSVGEffects::FillProperty());
1281 if (ps && ps->SetupPaintServer(aContext, aFrame, &nsStyleSVG::mFill, opacity))
1282 return true;
1284 if (SetupContextPaint(aContext, aContextPaint, style->mFill, opacity)) {
1285 return true;
1288 // On failure, use the fallback colour in case we have an
1289 // objectBoundingBox where the width or height of the object is zero.
1290 // See http://www.w3.org/TR/SVG11/coords.html#ObjectBoundingBox
1291 SetupFallbackOrPaintColor(aContext, aFrame->StyleContext(),
1292 &nsStyleSVG::mFill, opacity);
1294 return true;
1297 bool
1298 nsSVGUtils::SetupCairoStrokePaint(nsIFrame *aFrame, gfxContext* aContext,
1299 gfxTextContextPaint *aContextPaint)
1301 const nsStyleSVG* style = aFrame->StyleSVG();
1302 if (style->mStroke.mType == eStyleSVGPaintType_None)
1303 return false;
1305 float opacity = MaybeOptimizeOpacity(aFrame,
1306 GetOpacity(style->mStrokeOpacitySource,
1307 style->mStrokeOpacity,
1308 aContextPaint));
1310 nsSVGPaintServerFrame *ps =
1311 nsSVGEffects::GetPaintServer(aFrame, &style->mStroke, nsSVGEffects::StrokeProperty());
1312 if (ps && ps->SetupPaintServer(aContext, aFrame, &nsStyleSVG::mStroke, opacity))
1313 return true;
1315 if (SetupContextPaint(aContext, aContextPaint, style->mStroke, opacity)) {
1316 return true;
1319 // On failure, use the fallback colour in case we have an
1320 // objectBoundingBox where the width or height of the object is zero.
1321 // See http://www.w3.org/TR/SVG11/coords.html#ObjectBoundingBox
1322 SetupFallbackOrPaintColor(aContext, aFrame->StyleContext(),
1323 &nsStyleSVG::mStroke, opacity);
1325 return true;
1328 /* static */ float
1329 nsSVGUtils::GetOpacity(nsStyleSVGOpacitySource aOpacityType,
1330 const float& aOpacity,
1331 gfxTextContextPaint *aOuterContextPaint)
1333 float opacity = 1.0f;
1334 switch (aOpacityType) {
1335 case eStyleSVGOpacitySource_Normal:
1336 opacity = aOpacity;
1337 break;
1338 case eStyleSVGOpacitySource_ContextFillOpacity:
1339 if (aOuterContextPaint) {
1340 opacity = aOuterContextPaint->GetFillOpacity();
1341 } else {
1342 NS_WARNING("context-fill-opacity used outside of an SVG glyph");
1344 break;
1345 case eStyleSVGOpacitySource_ContextStrokeOpacity:
1346 if (aOuterContextPaint) {
1347 opacity = aOuterContextPaint->GetStrokeOpacity();
1348 } else {
1349 NS_WARNING("context-stroke-opacity used outside of an SVG glyph");
1351 break;
1352 default:
1353 NS_NOTREACHED("Unknown object opacity inheritance type for SVG glyph");
1355 return opacity;
1358 bool
1359 nsSVGUtils::HasStroke(nsIFrame* aFrame, gfxTextContextPaint *aContextPaint)
1361 const nsStyleSVG *style = aFrame->StyleSVG();
1362 return style->HasStroke() && GetStrokeWidth(aFrame, aContextPaint) > 0;
1365 float
1366 nsSVGUtils::GetStrokeWidth(nsIFrame* aFrame, gfxTextContextPaint *aContextPaint)
1368 const nsStyleSVG *style = aFrame->StyleSVG();
1369 if (aContextPaint && style->mStrokeWidthFromObject) {
1370 return aContextPaint->GetStrokeWidth();
1373 nsIContent* content = aFrame->GetContent();
1374 if (content->IsNodeOfType(nsINode::eTEXT)) {
1375 content = content->GetParent();
1378 nsSVGElement *ctx = static_cast<nsSVGElement*>(content);
1380 return SVGContentUtils::CoordToFloat(ctx, style->mStrokeWidth);
1383 void
1384 nsSVGUtils::SetupCairoStrokeBBoxGeometry(nsIFrame* aFrame,
1385 gfxContext *aContext,
1386 gfxTextContextPaint *aContextPaint)
1388 float width = GetStrokeWidth(aFrame, aContextPaint);
1389 if (width <= 0)
1390 return;
1391 aContext->SetLineWidth(width);
1393 // Apply any stroke-specific transform
1394 gfxMatrix strokeTransform = GetStrokeTransform(aFrame);
1395 if (!strokeTransform.IsIdentity()) {
1396 aContext->Multiply(strokeTransform);
1399 const nsStyleSVG* style = aFrame->StyleSVG();
1401 switch (style->mStrokeLinecap) {
1402 case NS_STYLE_STROKE_LINECAP_BUTT:
1403 aContext->SetLineCap(gfxContext::LINE_CAP_BUTT);
1404 break;
1405 case NS_STYLE_STROKE_LINECAP_ROUND:
1406 aContext->SetLineCap(gfxContext::LINE_CAP_ROUND);
1407 break;
1408 case NS_STYLE_STROKE_LINECAP_SQUARE:
1409 aContext->SetLineCap(gfxContext::LINE_CAP_SQUARE);
1410 break;
1413 aContext->SetMiterLimit(style->mStrokeMiterlimit);
1415 switch (style->mStrokeLinejoin) {
1416 case NS_STYLE_STROKE_LINEJOIN_MITER:
1417 aContext->SetLineJoin(gfxContext::LINE_JOIN_MITER);
1418 break;
1419 case NS_STYLE_STROKE_LINEJOIN_ROUND:
1420 aContext->SetLineJoin(gfxContext::LINE_JOIN_ROUND);
1421 break;
1422 case NS_STYLE_STROKE_LINEJOIN_BEVEL:
1423 aContext->SetLineJoin(gfxContext::LINE_JOIN_BEVEL);
1424 break;
1428 static bool
1429 GetStrokeDashData(nsIFrame* aFrame,
1430 FallibleTArray<gfxFloat>& aDashes,
1431 gfxFloat* aDashOffset,
1432 gfxTextContextPaint *aContextPaint)
1434 const nsStyleSVG* style = aFrame->StyleSVG();
1435 nsIContent *content = aFrame->GetContent();
1436 nsSVGElement *ctx = static_cast<nsSVGElement*>
1437 (content->IsNodeOfType(nsINode::eTEXT) ?
1438 content->GetParent() : content);
1440 gfxFloat totalLength = 0.0;
1441 if (aContextPaint && style->mStrokeDasharrayFromObject) {
1442 aDashes = aContextPaint->GetStrokeDashArray();
1444 for (uint32_t i = 0; i < aDashes.Length(); i++) {
1445 if (aDashes[i] < 0.0) {
1446 return false;
1448 totalLength += aDashes[i];
1451 } else {
1452 uint32_t count = style->mStrokeDasharrayLength;
1453 if (!count || !aDashes.SetLength(count)) {
1454 return false;
1457 gfxFloat pathScale = 1.0;
1459 if (content->Tag() == nsGkAtoms::path) {
1460 pathScale = static_cast<SVGPathElement*>(content)->
1461 GetPathLengthScale(SVGPathElement::eForStroking);
1462 if (pathScale <= 0) {
1463 return false;
1467 const nsStyleCoord *dasharray = style->mStrokeDasharray;
1469 for (uint32_t i = 0; i < count; i++) {
1470 aDashes[i] = SVGContentUtils::CoordToFloat(ctx,
1471 dasharray[i]) * pathScale;
1472 if (aDashes[i] < 0.0) {
1473 return false;
1475 totalLength += aDashes[i];
1479 if (aContextPaint && style->mStrokeDashoffsetFromObject) {
1480 *aDashOffset = aContextPaint->GetStrokeDashOffset();
1481 } else {
1482 *aDashOffset = SVGContentUtils::CoordToFloat(ctx,
1483 style->mStrokeDashoffset);
1486 return (totalLength > 0.0);
1489 void
1490 nsSVGUtils::SetupCairoStrokeGeometry(nsIFrame* aFrame, gfxContext* aContext,
1491 gfxTextContextPaint *aContextPaint)
1493 SetupCairoStrokeBBoxGeometry(aFrame, aContext, aContextPaint);
1495 AutoFallibleTArray<gfxFloat, 10> dashes;
1496 gfxFloat dashOffset;
1497 if (GetStrokeDashData(aFrame, dashes, &dashOffset, aContextPaint)) {
1498 aContext->SetDash(dashes.Elements(), dashes.Length(), dashOffset);
1502 uint16_t
1503 nsSVGUtils::GetGeometryHitTestFlags(nsIFrame* aFrame)
1505 uint16_t flags = 0;
1507 switch(aFrame->StyleVisibility()->mPointerEvents) {
1508 case NS_STYLE_POINTER_EVENTS_NONE:
1509 break;
1510 case NS_STYLE_POINTER_EVENTS_AUTO:
1511 case NS_STYLE_POINTER_EVENTS_VISIBLEPAINTED:
1512 if (aFrame->StyleVisibility()->IsVisible()) {
1513 if (aFrame->StyleSVG()->mFill.mType != eStyleSVGPaintType_None)
1514 flags |= SVG_HIT_TEST_FILL;
1515 if (aFrame->StyleSVG()->mStroke.mType != eStyleSVGPaintType_None)
1516 flags |= SVG_HIT_TEST_STROKE;
1517 if (aFrame->StyleSVG()->mStrokeOpacity > 0)
1518 flags |= SVG_HIT_TEST_CHECK_MRECT;
1520 break;
1521 case NS_STYLE_POINTER_EVENTS_VISIBLEFILL:
1522 if (aFrame->StyleVisibility()->IsVisible()) {
1523 flags |= SVG_HIT_TEST_FILL;
1525 break;
1526 case NS_STYLE_POINTER_EVENTS_VISIBLESTROKE:
1527 if (aFrame->StyleVisibility()->IsVisible()) {
1528 flags |= SVG_HIT_TEST_STROKE;
1530 break;
1531 case NS_STYLE_POINTER_EVENTS_VISIBLE:
1532 if (aFrame->StyleVisibility()->IsVisible()) {
1533 flags |= SVG_HIT_TEST_FILL | SVG_HIT_TEST_STROKE;
1535 break;
1536 case NS_STYLE_POINTER_EVENTS_PAINTED:
1537 if (aFrame->StyleSVG()->mFill.mType != eStyleSVGPaintType_None)
1538 flags |= SVG_HIT_TEST_FILL;
1539 if (aFrame->StyleSVG()->mStroke.mType != eStyleSVGPaintType_None)
1540 flags |= SVG_HIT_TEST_STROKE;
1541 if (aFrame->StyleSVG()->mStrokeOpacity)
1542 flags |= SVG_HIT_TEST_CHECK_MRECT;
1543 break;
1544 case NS_STYLE_POINTER_EVENTS_FILL:
1545 flags |= SVG_HIT_TEST_FILL;
1546 break;
1547 case NS_STYLE_POINTER_EVENTS_STROKE:
1548 flags |= SVG_HIT_TEST_STROKE;
1549 break;
1550 case NS_STYLE_POINTER_EVENTS_ALL:
1551 flags |= SVG_HIT_TEST_FILL | SVG_HIT_TEST_STROKE;
1552 break;
1553 default:
1554 NS_ERROR("not reached");
1555 break;
1558 return flags;
1561 bool
1562 nsSVGUtils::SetupCairoStroke(nsIFrame* aFrame, gfxContext* aContext,
1563 gfxTextContextPaint *aContextPaint)
1565 if (!HasStroke(aFrame, aContextPaint)) {
1566 return false;
1568 SetupCairoStrokeGeometry(aFrame, aContext, aContextPaint);
1570 return SetupCairoStrokePaint(aFrame, aContext, aContextPaint);
1573 bool
1574 nsSVGUtils::PaintSVGGlyph(Element* aElement, gfxContext* aContext,
1575 DrawMode aDrawMode,
1576 gfxTextContextPaint* aContextPaint)
1578 nsIFrame* frame = aElement->GetPrimaryFrame();
1579 nsISVGChildFrame* svgFrame = do_QueryFrame(frame);
1580 if (!svgFrame) {
1581 return false;
1583 nsRefPtr<nsRenderingContext> context(new nsRenderingContext());
1584 context->Init(frame->PresContext()->DeviceContext(), aContext);
1585 context->AddUserData(&gfxTextContextPaint::sUserDataKey, aContextPaint,
1586 nullptr);
1587 svgFrame->NotifySVGChanged(nsISVGChildFrame::TRANSFORM_CHANGED);
1588 nsresult rv = svgFrame->PaintSVG(context, nullptr, frame);
1589 return NS_SUCCEEDED(rv);
1592 bool
1593 nsSVGUtils::GetSVGGlyphExtents(Element* aElement,
1594 const gfxMatrix& aSVGToAppSpace,
1595 gfxRect* aResult)
1597 nsIFrame* frame = aElement->GetPrimaryFrame();
1598 nsISVGChildFrame* svgFrame = do_QueryFrame(frame);
1599 if (!svgFrame) {
1600 return false;
1603 gfxMatrix transform(aSVGToAppSpace);
1604 nsIContent* content = frame->GetContent();
1605 if (content->IsSVG()) {
1606 transform = static_cast<nsSVGElement*>(content)->
1607 PrependLocalTransformsTo(aSVGToAppSpace);
1610 *aResult = svgFrame->GetBBoxContribution(gfx::ToMatrix(transform),
1611 nsSVGUtils::eBBoxIncludeFill | nsSVGUtils::eBBoxIncludeFillGeometry |
1612 nsSVGUtils::eBBoxIncludeStroke | nsSVGUtils::eBBoxIncludeStrokeGeometry |
1613 nsSVGUtils::eBBoxIncludeMarkers).ToThebesRect();
1614 return true;
1617 nsRect
1618 nsSVGUtils::ToCanvasBounds(const gfxRect &aUserspaceRect,
1619 const gfxMatrix &aToCanvas,
1620 const nsPresContext *presContext)
1622 return nsLayoutUtils::RoundGfxRectToAppRect(
1623 aToCanvas.TransformBounds(aUserspaceRect),
1624 presContext->AppUnitsPerDevPixel());