michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: // Main header first: michael@0: #include "nsSVGClipPathFrame.h" michael@0: michael@0: // Keep others in (case-insensitive) order: michael@0: #include "gfxContext.h" michael@0: #include "nsGkAtoms.h" michael@0: #include "nsRenderingContext.h" michael@0: #include "mozilla/dom/SVGClipPathElement.h" michael@0: #include "nsSVGEffects.h" michael@0: #include "nsSVGUtils.h" michael@0: michael@0: using namespace mozilla::dom; michael@0: michael@0: //---------------------------------------------------------------------- michael@0: // Implementation michael@0: michael@0: nsIFrame* michael@0: NS_NewSVGClipPathFrame(nsIPresShell* aPresShell, nsStyleContext* aContext) michael@0: { michael@0: return new (aPresShell) nsSVGClipPathFrame(aContext); michael@0: } michael@0: michael@0: NS_IMPL_FRAMEARENA_HELPERS(nsSVGClipPathFrame) michael@0: michael@0: nsresult michael@0: nsSVGClipPathFrame::ClipPaint(nsRenderingContext* aContext, michael@0: nsIFrame* aParent, michael@0: const gfxMatrix &aMatrix) michael@0: { michael@0: // If the flag is set when we get here, it means this clipPath frame michael@0: // has already been used painting the current clip, and the document michael@0: // has a clip reference loop. michael@0: if (mInUse) { michael@0: NS_WARNING("Clip loop detected!"); michael@0: return NS_OK; michael@0: } michael@0: AutoClipPathReferencer clipRef(this); michael@0: michael@0: mClipParent = aParent; michael@0: if (mClipParentMatrix) { michael@0: *mClipParentMatrix = aMatrix; michael@0: } else { michael@0: mClipParentMatrix = new gfxMatrix(aMatrix); michael@0: } michael@0: michael@0: gfxContext *gfx = aContext->ThebesContext(); michael@0: michael@0: nsISVGChildFrame *singleClipPathChild = nullptr; michael@0: michael@0: if (IsTrivial(&singleClipPathChild)) { michael@0: // Notify our child that it's painting as part of a clipPath, and that michael@0: // we only require it to draw its path (it should skip filling, etc.): michael@0: SVGAutoRenderState mode(aContext, SVGAutoRenderState::CLIP); michael@0: michael@0: if (!singleClipPathChild) { michael@0: // We have no children - the spec says clip away everything: michael@0: gfx->Rectangle(gfxRect()); michael@0: } else { michael@0: singleClipPathChild->NotifySVGChanged( michael@0: nsISVGChildFrame::TRANSFORM_CHANGED); michael@0: singleClipPathChild->PaintSVG(aContext, nullptr); michael@0: } michael@0: gfx->Clip(); michael@0: gfx->NewPath(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: // Seems like this is a non-trivial clipPath, so we need to use a clip mask. michael@0: michael@0: // Notify our children that they're painting into a clip mask: michael@0: SVGAutoRenderState mode(aContext, SVGAutoRenderState::CLIP_MASK); michael@0: michael@0: // Check if this clipPath is itself clipped by another clipPath: michael@0: nsSVGClipPathFrame *clipPathFrame = michael@0: nsSVGEffects::GetEffectProperties(this).GetClipPathFrame(nullptr); michael@0: bool referencedClipIsTrivial; michael@0: if (clipPathFrame) { michael@0: referencedClipIsTrivial = clipPathFrame->IsTrivial(); michael@0: gfx->Save(); michael@0: if (referencedClipIsTrivial) { michael@0: clipPathFrame->ClipPaint(aContext, aParent, aMatrix); michael@0: } else { michael@0: gfx->PushGroup(gfxContentType::ALPHA); michael@0: } michael@0: } michael@0: michael@0: for (nsIFrame* kid = mFrames.FirstChild(); kid; michael@0: kid = kid->GetNextSibling()) { michael@0: nsISVGChildFrame* SVGFrame = do_QueryFrame(kid); michael@0: if (SVGFrame) { michael@0: // The CTM of each frame referencing us can be different. michael@0: SVGFrame->NotifySVGChanged(nsISVGChildFrame::TRANSFORM_CHANGED); michael@0: michael@0: bool isOK = true; michael@0: nsSVGClipPathFrame *clipPathFrame = michael@0: nsSVGEffects::GetEffectProperties(kid).GetClipPathFrame(&isOK); michael@0: if (!isOK) { michael@0: continue; michael@0: } michael@0: michael@0: bool isTrivial; michael@0: michael@0: if (clipPathFrame) { michael@0: isTrivial = clipPathFrame->IsTrivial(); michael@0: gfx->Save(); michael@0: if (isTrivial) { michael@0: clipPathFrame->ClipPaint(aContext, aParent, aMatrix); michael@0: } else { michael@0: gfx->PushGroup(gfxContentType::ALPHA); michael@0: } michael@0: } michael@0: michael@0: SVGFrame->PaintSVG(aContext, nullptr); michael@0: michael@0: if (clipPathFrame) { michael@0: if (!isTrivial) { michael@0: gfx->PopGroupToSource(); michael@0: michael@0: nsRefPtr clipMaskSurface; michael@0: gfx->PushGroup(gfxContentType::ALPHA); michael@0: michael@0: clipPathFrame->ClipPaint(aContext, aParent, aMatrix); michael@0: clipMaskSurface = gfx->PopGroup(); michael@0: michael@0: if (clipMaskSurface) { michael@0: gfx->Mask(clipMaskSurface); michael@0: } michael@0: } michael@0: gfx->Restore(); michael@0: } michael@0: } michael@0: } michael@0: michael@0: if (clipPathFrame) { michael@0: if (!referencedClipIsTrivial) { michael@0: gfx->PopGroupToSource(); michael@0: michael@0: nsRefPtr clipMaskSurface; michael@0: gfx->PushGroup(gfxContentType::ALPHA); michael@0: michael@0: clipPathFrame->ClipPaint(aContext, aParent, aMatrix); michael@0: clipMaskSurface = gfx->PopGroup(); michael@0: michael@0: if (clipMaskSurface) { michael@0: gfx->Mask(clipMaskSurface); michael@0: } michael@0: } michael@0: gfx->Restore(); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: bool michael@0: nsSVGClipPathFrame::ClipHitTest(nsIFrame* aParent, michael@0: const gfxMatrix &aMatrix, michael@0: const nsPoint &aPoint) michael@0: { michael@0: // If the flag is set when we get here, it means this clipPath frame michael@0: // has already been used in hit testing against the current clip, michael@0: // and the document has a clip reference loop. michael@0: if (mInUse) { michael@0: NS_WARNING("Clip loop detected!"); michael@0: return false; michael@0: } michael@0: AutoClipPathReferencer clipRef(this); michael@0: michael@0: mClipParent = aParent; michael@0: if (mClipParentMatrix) { michael@0: *mClipParentMatrix = aMatrix; michael@0: } else { michael@0: mClipParentMatrix = new gfxMatrix(aMatrix); michael@0: } michael@0: michael@0: nsSVGClipPathFrame *clipPathFrame = michael@0: nsSVGEffects::GetEffectProperties(this).GetClipPathFrame(nullptr); michael@0: if (clipPathFrame && !clipPathFrame->ClipHitTest(aParent, aMatrix, aPoint)) michael@0: return false; michael@0: michael@0: for (nsIFrame* kid = mFrames.FirstChild(); kid; michael@0: kid = kid->GetNextSibling()) { michael@0: nsISVGChildFrame* SVGFrame = do_QueryFrame(kid); michael@0: if (SVGFrame) { michael@0: // Notify the child frame that we may be working with a michael@0: // different transform, so it can update its covered region michael@0: // (used to shortcut hit testing). michael@0: SVGFrame->NotifySVGChanged(nsISVGChildFrame::TRANSFORM_CHANGED); michael@0: michael@0: if (SVGFrame->GetFrameForPoint(aPoint)) michael@0: return true; michael@0: } michael@0: } michael@0: return false; michael@0: } michael@0: michael@0: bool michael@0: nsSVGClipPathFrame::IsTrivial(nsISVGChildFrame **aSingleChild) michael@0: { michael@0: // If the clip path is clipped then it's non-trivial michael@0: if (nsSVGEffects::GetEffectProperties(this).GetClipPathFrame(nullptr)) michael@0: return false; michael@0: michael@0: if (aSingleChild) { michael@0: *aSingleChild = nullptr; michael@0: } michael@0: michael@0: nsISVGChildFrame *foundChild = nullptr; michael@0: michael@0: for (nsIFrame* kid = mFrames.FirstChild(); kid; michael@0: kid = kid->GetNextSibling()) { michael@0: nsISVGChildFrame *svgChild = do_QueryFrame(kid); michael@0: if (svgChild) { michael@0: // We consider a non-trivial clipPath to be one containing michael@0: // either more than one svg child and/or a svg container michael@0: if (foundChild || svgChild->IsDisplayContainer()) michael@0: return false; michael@0: michael@0: // or where the child is itself clipped michael@0: if (nsSVGEffects::GetEffectProperties(kid).GetClipPathFrame(nullptr)) michael@0: return false; michael@0: michael@0: foundChild = svgChild; michael@0: } michael@0: } michael@0: if (aSingleChild) { michael@0: *aSingleChild = foundChild; michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: nsSVGClipPathFrame::IsValid() michael@0: { michael@0: if (mInUse) { michael@0: NS_WARNING("Clip loop detected!"); michael@0: return false; michael@0: } michael@0: AutoClipPathReferencer clipRef(this); michael@0: michael@0: bool isOK = true; michael@0: nsSVGEffects::GetEffectProperties(this).GetClipPathFrame(&isOK); michael@0: if (!isOK) { michael@0: return false; michael@0: } michael@0: michael@0: for (nsIFrame* kid = mFrames.FirstChild(); kid; michael@0: kid = kid->GetNextSibling()) { michael@0: michael@0: nsIAtom *type = kid->GetType(); michael@0: michael@0: if (type == nsGkAtoms::svgUseFrame) { michael@0: for (nsIFrame* grandKid = kid->GetFirstPrincipalChild(); grandKid; michael@0: grandKid = grandKid->GetNextSibling()) { michael@0: michael@0: nsIAtom *type = grandKid->GetType(); michael@0: michael@0: if (type != nsGkAtoms::svgPathGeometryFrame && michael@0: type != nsGkAtoms::svgTextFrame) { michael@0: return false; michael@0: } michael@0: } michael@0: continue; michael@0: } michael@0: if (type != nsGkAtoms::svgPathGeometryFrame && michael@0: type != nsGkAtoms::svgTextFrame) { michael@0: return false; michael@0: } michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: nsresult michael@0: nsSVGClipPathFrame::AttributeChanged(int32_t aNameSpaceID, michael@0: nsIAtom* aAttribute, michael@0: int32_t aModType) michael@0: { michael@0: if (aNameSpaceID == kNameSpaceID_None) { michael@0: if (aAttribute == nsGkAtoms::transform) { michael@0: nsSVGEffects::InvalidateDirectRenderingObservers(this); michael@0: nsSVGUtils::NotifyChildrenOfSVGChange(this, michael@0: nsISVGChildFrame::TRANSFORM_CHANGED); michael@0: } michael@0: if (aAttribute == nsGkAtoms::clipPathUnits) { michael@0: nsSVGEffects::InvalidateRenderingObservers(this); michael@0: } michael@0: } michael@0: michael@0: return nsSVGClipPathFrameBase::AttributeChanged(aNameSpaceID, michael@0: aAttribute, aModType); michael@0: } michael@0: michael@0: void michael@0: nsSVGClipPathFrame::Init(nsIContent* aContent, michael@0: nsIFrame* aParent, michael@0: nsIFrame* aPrevInFlow) michael@0: { michael@0: NS_ASSERTION(aContent->IsSVG(nsGkAtoms::clipPath), michael@0: "Content is not an SVG clipPath!"); michael@0: michael@0: AddStateBits(NS_STATE_SVG_CLIPPATH_CHILD); michael@0: nsSVGClipPathFrameBase::Init(aContent, aParent, aPrevInFlow); michael@0: } michael@0: michael@0: nsIAtom * michael@0: nsSVGClipPathFrame::GetType() const michael@0: { michael@0: return nsGkAtoms::svgClipPathFrame; michael@0: } michael@0: michael@0: gfxMatrix michael@0: nsSVGClipPathFrame::GetCanvasTM(uint32_t aFor, nsIFrame* aTransformRoot) michael@0: { michael@0: SVGClipPathElement *content = static_cast(mContent); michael@0: michael@0: gfxMatrix tm = michael@0: content->PrependLocalTransformsTo(mClipParentMatrix ? michael@0: *mClipParentMatrix : gfxMatrix()); michael@0: michael@0: return nsSVGUtils::AdjustMatrixForUnits(tm, michael@0: &content->mEnumAttributes[SVGClipPathElement::CLIPPATHUNITS], michael@0: mClipParent); michael@0: }