summaryrefslogtreecommitdiffstats
path: root/layout/svg/SVGClipPathFrame.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'layout/svg/SVGClipPathFrame.cpp')
-rw-r--r--layout/svg/SVGClipPathFrame.cpp482
1 files changed, 482 insertions, 0 deletions
diff --git a/layout/svg/SVGClipPathFrame.cpp b/layout/svg/SVGClipPathFrame.cpp
new file mode 100644
index 0000000000..644420b5ce
--- /dev/null
+++ b/layout/svg/SVGClipPathFrame.cpp
@@ -0,0 +1,482 @@
+/* -*- 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<bool> 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<Path> clipPath;
+
+ if (singleClipPathChild) {
+ SVGGeometryFrame* pathFrame = do_QueryFrame(singleClipPathChild);
+ if (pathFrame && pathFrame->StyleVisibility()->IsVisible()) {
+ SVGGeometryElement* pathElement =
+ static_cast<SVGGeometryElement*>(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, maskUsage);
+
+ gfxGroupForBlendAutoSaveRestore autoGroupForBlend(&aMaskContext);
+ if (maskUsage.shouldApplyClipPath) {
+ clipPathThatClipsClipPath->ApplyClipPath(aMaskContext, aClippedFrame,
+ aMatrix);
+ } else if (maskUsage.shouldGenerateClipMaskLayer) {
+ RefPtr<SourceSurface> 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 (nsIFrame* kid = mFrames.FirstChild(); kid; kid = kid->GetNextSibling()) {
+ 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
+ }
+
+ 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, maskUsage);
+ gfxGroupForBlendAutoSaveRestore autoGroupForBlend(&aTarget);
+ if (maskUsage.shouldApplyClipPath) {
+ clipPathThatClipsChild->ApplyClipPath(
+ aTarget, aClippedFrame,
+ SVGUtils::GetTransformMatrixInUserSpace(aFrame) * mMatrixForChildren);
+ } else if (maskUsage.shouldGenerateClipMaskLayer) {
+ RefPtr<SourceSurface> 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<SourceSurface> SVGClipPathFrame::GetClipMask(
+ gfxContext& aReferenceContext, nsIFrame* aClippedFrame,
+ const gfxMatrix& aMatrix, SourceSurface* aExtraMask) {
+ RefPtr<DrawTarget> maskDT =
+ aReferenceContext.GetDrawTarget()->CreateClippedDrawTarget(
+ Rect(), SurfaceFormat::A8);
+ if (!maskDT) {
+ return nullptr;
+ }
+
+ gfxContext maskContext(maskDT, /* aPreserveTransform */ true);
+ PaintClipMask(maskContext, aClippedFrame, aMatrix, aExtraMask);
+
+ RefPtr<SourceSurface> 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
+ }
+
+ 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 (nsIFrame* kid = mFrames.FirstChild(); kid; kid = kid->GetNextSibling()) {
+ 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 (nsIFrame* kid = mFrames.FirstChild(); kid; kid = kid->GetNextSibling()) {
+ 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() {
+ 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 (SVGObserverUtils::GetAndObserveClipPath(this, nullptr) ==
+ SVGObserverUtils::eHasRefsSomeInvalid) {
+ return false;
+ }
+
+ for (nsIFrame* kid = mFrames.FirstChild(); kid; kid = kid->GetNextSibling()) {
+ 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::InvalidateDirectRenderingObservers(this);
+ SVGUtils::NotifyChildrenOfSVGChange(
+ this, ISVGDisplayableFrame::TRANSFORM_CHANGED);
+ }
+ if (aAttribute == nsGkAtoms::clipPathUnits) {
+ SVGObserverUtils::InvalidateDirectRenderingObservers(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<SVGClipPathElement*>(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<SVGElement const*>(GetContent());
+ Matrix m = ToMatrix(e->PrependLocalTransformsTo({}, eUserSpaceToParent));
+
+ if (m.IsIdentity()) {
+ return false;
+ }
+
+ if (aOwnTransforms) {
+ *aOwnTransforms = m;
+ }
+
+ return true;
+}
+
+} // namespace mozilla