/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=8 sts=2 et sw=2 tw=80: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ // Main header first: #include "SVGClipPathFrame.h" // Keep others in (case-insensitive) order: #include "AutoReferenceChainGuard.h" #include "ImgDrawResult.h" #include "gfxContext.h" #include "mozilla/PresShell.h" #include "mozilla/SVGGeometryFrame.h" #include "mozilla/SVGObserverUtils.h" #include "mozilla/SVGUtils.h" #include "mozilla/dom/SVGClipPathElement.h" #include "mozilla/dom/SVGGeometryElement.h" #include "nsGkAtoms.h" using namespace mozilla::dom; using namespace mozilla::gfx; using namespace mozilla::image; //---------------------------------------------------------------------- // Implementation nsIFrame* NS_NewSVGClipPathFrame(mozilla::PresShell* aPresShell, mozilla::ComputedStyle* aStyle) { return new (aPresShell) mozilla::SVGClipPathFrame(aStyle, aPresShell->GetPresContext()); } namespace mozilla { NS_IMPL_FRAMEARENA_HELPERS(SVGClipPathFrame) void SVGClipPathFrame::ApplyClipPath(gfxContext& aContext, nsIFrame* aClippedFrame, const gfxMatrix& aMatrix) { ISVGDisplayableFrame* singleClipPathChild = nullptr; DebugOnly trivial = IsTrivial(&singleClipPathChild); MOZ_ASSERT(trivial, "Caller needs to use GetClipMask"); const DrawTarget* drawTarget = aContext.GetDrawTarget(); // No need for AutoReferenceChainGuard since simple clip paths by definition // don't reference another clip path. // Restore current transform after applying clip path: gfxContextMatrixAutoSaveRestore autoRestoreTransform(&aContext); RefPtr clipPath; if (singleClipPathChild) { SVGGeometryFrame* pathFrame = do_QueryFrame(singleClipPathChild); if (pathFrame && pathFrame->StyleVisibility()->IsVisible()) { SVGGeometryElement* pathElement = static_cast(pathFrame->GetContent()); gfxMatrix toChildsUserSpace = SVGUtils::GetTransformMatrixInUserSpace(pathFrame) * (GetClipPathTransform(aClippedFrame) * aMatrix); gfxMatrix newMatrix = aContext.CurrentMatrixDouble() .PreMultiply(toChildsUserSpace) .NudgeToIntegers(); if (!newMatrix.IsSingular()) { aContext.SetMatrixDouble(newMatrix); FillRule clipRule = SVGUtils::ToFillRule(pathFrame->StyleSVG()->mClipRule); clipPath = pathElement->GetOrBuildPath(drawTarget, clipRule); } } } if (clipPath) { aContext.Clip(clipPath); } else { // The spec says clip away everything if we have no children or the // clipping path otherwise can't be resolved: aContext.Clip(Rect()); } } static void ComposeExtraMask(DrawTarget* aTarget, SourceSurface* aExtraMask) { MOZ_ASSERT(aExtraMask); Matrix origin = aTarget->GetTransform(); aTarget->SetTransform(Matrix()); aTarget->MaskSurface(ColorPattern(DeviceColor(0.0, 0.0, 0.0, 1.0)), aExtraMask, Point(0, 0), DrawOptions(1.0, CompositionOp::OP_IN)); aTarget->SetTransform(origin); } void SVGClipPathFrame::PaintChildren(gfxContext& aMaskContext, nsIFrame* aClippedFrame, const gfxMatrix& aMatrix) { // Check if this clipPath is itself clipped by another clipPath: SVGClipPathFrame* clipPathThatClipsClipPath; // XXX check return value? SVGObserverUtils::GetAndObserveClipPath(this, &clipPathThatClipsClipPath); SVGUtils::MaskUsage maskUsage = SVGUtils::DetermineMaskUsage(this, true); gfxGroupForBlendAutoSaveRestore autoGroupForBlend(&aMaskContext); if (maskUsage.ShouldApplyClipPath()) { clipPathThatClipsClipPath->ApplyClipPath(aMaskContext, aClippedFrame, aMatrix); } else if (maskUsage.ShouldGenerateClipMaskLayer()) { RefPtr maskSurface = clipPathThatClipsClipPath->GetClipMask( aMaskContext, aClippedFrame, aMatrix); // We want the mask to be untransformed so use the inverse of the current // transform as the maskTransform to compensate. Matrix maskTransform = aMaskContext.CurrentMatrix(); maskTransform.Invert(); autoGroupForBlend.PushGroupForBlendBack(gfxContentType::ALPHA, 1.0f, maskSurface, maskTransform); } // Paint our children into the mask: for (auto* kid : mFrames) { PaintFrameIntoMask(kid, aClippedFrame, aMaskContext); } if (maskUsage.ShouldApplyClipPath()) { aMaskContext.PopClip(); } } void SVGClipPathFrame::PaintClipMask(gfxContext& aMaskContext, nsIFrame* aClippedFrame, const gfxMatrix& aMatrix, SourceSurface* aExtraMask) { static int16_t sRefChainLengthCounter = AutoReferenceChainGuard::noChain; // A clipPath can reference another clipPath, creating a chain of clipPaths // that must all be applied. We re-enter this method for each clipPath in a // chain, so we need to protect against reference chain related crashes etc.: AutoReferenceChainGuard refChainGuard(this, &mIsBeingProcessed, &sRefChainLengthCounter); if (MOZ_UNLIKELY(!refChainGuard.Reference())) { return; // Break reference chain } if (!IsValid()) { return; } DrawTarget* maskDT = aMaskContext.GetDrawTarget(); MOZ_ASSERT(maskDT->GetFormat() == SurfaceFormat::A8); // Paint this clipPath's contents into aMaskDT: // We need to set mMatrixForChildren here so that under the PaintSVG calls // on our children (below) our GetCanvasTM() method will return the correct // transform. mMatrixForChildren = GetClipPathTransform(aClippedFrame) * aMatrix; PaintChildren(aMaskContext, aClippedFrame, aMatrix); if (aExtraMask) { ComposeExtraMask(maskDT, aExtraMask); } } void SVGClipPathFrame::PaintFrameIntoMask(nsIFrame* aFrame, nsIFrame* aClippedFrame, gfxContext& aTarget) { ISVGDisplayableFrame* frame = do_QueryFrame(aFrame); if (!frame) { return; } // The CTM of each frame referencing us can be different. frame->NotifySVGChanged(ISVGDisplayableFrame::TRANSFORM_CHANGED); // Children of this clipPath may themselves be clipped. SVGClipPathFrame* clipPathThatClipsChild; // XXX check return value? if (SVGObserverUtils::GetAndObserveClipPath(aFrame, &clipPathThatClipsChild) == SVGObserverUtils::eHasRefsSomeInvalid) { return; } SVGUtils::MaskUsage maskUsage = SVGUtils::DetermineMaskUsage(aFrame, true); gfxGroupForBlendAutoSaveRestore autoGroupForBlend(&aTarget); if (maskUsage.ShouldApplyClipPath()) { clipPathThatClipsChild->ApplyClipPath( aTarget, aClippedFrame, SVGUtils::GetTransformMatrixInUserSpace(aFrame) * mMatrixForChildren); } else if (maskUsage.ShouldGenerateClipMaskLayer()) { RefPtr maskSurface = clipPathThatClipsChild->GetClipMask( aTarget, aClippedFrame, SVGUtils::GetTransformMatrixInUserSpace(aFrame) * mMatrixForChildren); // We want the mask to be untransformed so use the inverse of the current // transform as the maskTransform to compensate. Matrix maskTransform = aTarget.CurrentMatrix(); maskTransform.Invert(); autoGroupForBlend.PushGroupForBlendBack(gfxContentType::ALPHA, 1.0f, maskSurface, maskTransform); } gfxMatrix toChildsUserSpace = mMatrixForChildren; nsIFrame* child = do_QueryFrame(frame); nsIContent* childContent = child->GetContent(); if (childContent->IsSVGElement()) { toChildsUserSpace = SVGUtils::GetTransformMatrixInUserSpace(child) * mMatrixForChildren; } // clipPath does not result in any image rendering, so we just use a dummy // imgDrawingParams instead of requiring our caller to pass one. image::imgDrawingParams imgParams; // Our children have NS_STATE_SVG_CLIPPATH_CHILD set on them, and // SVGGeometryFrame::Render checks for that state bit and paints // only the geometry (opaque black) if set. frame->PaintSVG(aTarget, toChildsUserSpace, imgParams); if (maskUsage.ShouldApplyClipPath()) { aTarget.PopClip(); } } already_AddRefed SVGClipPathFrame::GetClipMask( gfxContext& aReferenceContext, nsIFrame* aClippedFrame, const gfxMatrix& aMatrix, SourceSurface* aExtraMask) { RefPtr maskDT = aReferenceContext.GetDrawTarget()->CreateClippedDrawTarget( Rect(), SurfaceFormat::A8); if (!maskDT) { return nullptr; } gfxContext maskContext(maskDT, /* aPreserveTransform */ true); PaintClipMask(maskContext, aClippedFrame, aMatrix, aExtraMask); RefPtr surface = maskDT->Snapshot(); return surface.forget(); } bool SVGClipPathFrame::PointIsInsideClipPath(nsIFrame* aClippedFrame, const gfxPoint& aPoint) { static int16_t sRefChainLengthCounter = AutoReferenceChainGuard::noChain; // A clipPath can reference another clipPath, creating a chain of clipPaths // that must all be applied. We re-enter this method for each clipPath in a // chain, so we need to protect against reference chain related crashes etc.: AutoReferenceChainGuard refChainGuard(this, &mIsBeingProcessed, &sRefChainLengthCounter); if (MOZ_UNLIKELY(!refChainGuard.Reference())) { return false; // Break reference chain } if (!IsValid()) { return false; } gfxMatrix matrix = GetClipPathTransform(aClippedFrame); if (!matrix.Invert()) { return false; } gfxPoint point = matrix.TransformPoint(aPoint); // clipPath elements can themselves be clipped by a different clip path. In // that case the other clip path further clips away the element that is being // clipped by the original clipPath. If this clipPath is being clipped by a // different clip path we need to check if it prevents the original element // from receiving events at aPoint: SVGClipPathFrame* clipPathFrame; // XXX check return value? SVGObserverUtils::GetAndObserveClipPath(this, &clipPathFrame); if (clipPathFrame && !clipPathFrame->PointIsInsideClipPath(aClippedFrame, aPoint)) { return false; } for (auto* kid : mFrames) { ISVGDisplayableFrame* SVGFrame = do_QueryFrame(kid); if (SVGFrame) { gfxPoint pointForChild = point; gfxMatrix m = SVGUtils::GetTransformMatrixInUserSpace(kid); if (!m.IsIdentity()) { if (!m.Invert()) { return false; } pointForChild = m.TransformPoint(point); } if (SVGFrame->GetFrameForPoint(pointForChild)) { return true; } } } return false; } bool SVGClipPathFrame::IsTrivial(ISVGDisplayableFrame** aSingleChild) { // If the clip path is clipped then it's non-trivial if (SVGObserverUtils::GetAndObserveClipPath(this, nullptr) == SVGObserverUtils::eHasRefsAllValid) { return false; } if (aSingleChild) { *aSingleChild = nullptr; } ISVGDisplayableFrame* foundChild = nullptr; for (auto* kid : mFrames) { ISVGDisplayableFrame* svgChild = do_QueryFrame(kid); if (svgChild) { // We consider a non-trivial clipPath to be one containing // either more than one svg child and/or a svg container if (foundChild || svgChild->IsDisplayContainer()) { return false; } // or where the child is itself clipped if (SVGObserverUtils::GetAndObserveClipPath(kid, nullptr) == SVGObserverUtils::eHasRefsAllValid) { return false; } foundChild = svgChild; } } if (aSingleChild) { *aSingleChild = foundChild; } return true; } bool SVGClipPathFrame::IsValid() { if (SVGObserverUtils::GetAndObserveClipPath(this, nullptr) == SVGObserverUtils::eHasRefsSomeInvalid) { return false; } for (auto* kid : mFrames) { LayoutFrameType kidType = kid->Type(); if (kidType == LayoutFrameType::SVGUse) { for (nsIFrame* grandKid : kid->PrincipalChildList()) { LayoutFrameType grandKidType = grandKid->Type(); if (grandKidType != LayoutFrameType::SVGGeometry && grandKidType != LayoutFrameType::SVGText) { return false; } } continue; } if (kidType != LayoutFrameType::SVGGeometry && kidType != LayoutFrameType::SVGText) { return false; } } return true; } nsresult SVGClipPathFrame::AttributeChanged(int32_t aNameSpaceID, nsAtom* aAttribute, int32_t aModType) { if (aNameSpaceID == kNameSpaceID_None) { if (aAttribute == nsGkAtoms::transform) { SVGObserverUtils::InvalidateRenderingObservers(this); SVGUtils::NotifyChildrenOfSVGChange( this, ISVGDisplayableFrame::TRANSFORM_CHANGED); } if (aAttribute == nsGkAtoms::clipPathUnits) { SVGObserverUtils::InvalidateRenderingObservers(this); } } return SVGContainerFrame::AttributeChanged(aNameSpaceID, aAttribute, aModType); } #ifdef DEBUG void SVGClipPathFrame::Init(nsIContent* aContent, nsContainerFrame* aParent, nsIFrame* aPrevInFlow) { NS_ASSERTION(aContent->IsSVGElement(nsGkAtoms::clipPath), "Content is not an SVG clipPath!"); SVGContainerFrame::Init(aContent, aParent, aPrevInFlow); } #endif gfxMatrix SVGClipPathFrame::GetCanvasTM() { return mMatrixForChildren; } gfxMatrix SVGClipPathFrame::GetClipPathTransform(nsIFrame* aClippedFrame) { SVGClipPathElement* content = static_cast(GetContent()); gfxMatrix tm = content->PrependLocalTransformsTo({}, eChildToUserSpace) * SVGUtils::GetTransformMatrixInUserSpace(this); SVGAnimatedEnumeration* clipPathUnits = &content->mEnumAttributes[SVGClipPathElement::CLIPPATHUNITS]; uint32_t flags = SVGUtils::eBBoxIncludeFillGeometry | (aClippedFrame->StyleBorder()->mBoxDecorationBreak == StyleBoxDecorationBreak::Clone ? SVGUtils::eIncludeOnlyCurrentFrameForNonSVGElement : 0); return SVGUtils::AdjustMatrixForUnits(tm, clipPathUnits, aClippedFrame, flags); } SVGBBox SVGClipPathFrame::GetBBoxForClipPathFrame(const SVGBBox& aBBox, const gfxMatrix& aMatrix, uint32_t aFlags) { SVGClipPathFrame* clipPathThatClipsClipPath; if (SVGObserverUtils::GetAndObserveClipPath(this, &clipPathThatClipsClipPath) == SVGObserverUtils::eHasRefsSomeInvalid) { return SVGBBox(); } nsIContent* node = GetContent()->GetFirstChild(); SVGBBox unionBBox, tmpBBox; for (; node; node = node->GetNextSibling()) { if (nsIFrame* frame = node->GetPrimaryFrame()) { ISVGDisplayableFrame* svg = do_QueryFrame(frame); if (svg) { gfxMatrix matrix = SVGUtils::GetTransformMatrixInUserSpace(frame) * aMatrix; tmpBBox = svg->GetBBoxContribution(gfx::ToMatrix(matrix), SVGUtils::eBBoxIncludeFill); SVGClipPathFrame* clipPathFrame; if (SVGObserverUtils::GetAndObserveClipPath(frame, &clipPathFrame) != SVGObserverUtils::eHasRefsSomeInvalid && clipPathFrame) { tmpBBox = clipPathFrame->GetBBoxForClipPathFrame(tmpBBox, aMatrix, aFlags); } if (!(aFlags & SVGUtils::eDoNotClipToBBoxOfContentInsideClipPath)) { tmpBBox.Intersect(aBBox); } unionBBox.UnionEdges(tmpBBox); } } } if (clipPathThatClipsClipPath) { tmpBBox = clipPathThatClipsClipPath->GetBBoxForClipPathFrame(aBBox, aMatrix, aFlags); unionBBox.Intersect(tmpBBox); } return unionBBox; } bool SVGClipPathFrame::IsSVGTransformed(Matrix* aOwnTransforms, Matrix* aFromParentTransforms) const { const auto* e = static_cast(GetContent()); Matrix m = ToMatrix(e->PrependLocalTransformsTo({}, eUserSpaceToParent)); if (m.IsIdentity()) { return false; } if (aOwnTransforms) { *aOwnTransforms = m; } return true; } } // namespace mozilla