diff options
Diffstat (limited to 'layout/svg/nsSVGIntegrationUtils.cpp')
-rw-r--r-- | layout/svg/nsSVGIntegrationUtils.cpp | 1142 |
1 files changed, 1142 insertions, 0 deletions
diff --git a/layout/svg/nsSVGIntegrationUtils.cpp b/layout/svg/nsSVGIntegrationUtils.cpp new file mode 100644 index 000000000..498f69393 --- /dev/null +++ b/layout/svg/nsSVGIntegrationUtils.cpp @@ -0,0 +1,1142 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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 "nsSVGIntegrationUtils.h" + +// Keep others in (case-insensitive) order: +#include "gfxDrawable.h" +#include "nsCSSAnonBoxes.h" +#include "nsCSSClipPathInstance.h" +#include "nsDisplayList.h" +#include "nsFilterInstance.h" +#include "nsLayoutUtils.h" +#include "nsRenderingContext.h" +#include "nsSVGClipPathFrame.h" +#include "nsSVGEffects.h" +#include "nsSVGElement.h" +#include "nsSVGFilterPaintCallback.h" +#include "nsSVGMaskFrame.h" +#include "nsSVGPaintServerFrame.h" +#include "nsSVGUtils.h" +#include "FrameLayerBuilder.h" +#include "BasicLayers.h" +#include "mozilla/gfx/Point.h" +#include "nsCSSRendering.h" +#include "mozilla/Unused.h" + +using namespace mozilla; +using namespace mozilla::layers; +using namespace mozilla::gfx; +using namespace mozilla::image; + +// ---------------------------------------------------------------------- + +/** + * This class is used to get the pre-effects visual overflow rect of a frame, + * or, in the case of a frame with continuations, to collect the union of the + * pre-effects visual overflow rects of all the continuations. The result is + * relative to the origin (top left corner of the border box) of the frame, or, + * if the frame has continuations, the origin of the _first_ continuation. + */ +class PreEffectsVisualOverflowCollector : public nsLayoutUtils::BoxCallback +{ +public: + /** + * If the pre-effects visual overflow rect of the frame being examined + * happens to be known, it can be passed in as aCurrentFrame and its + * pre-effects visual overflow rect can be passed in as + * aCurrentFrameOverflowArea. This is just an optimization to save a + * frame property lookup - these arguments are optional. + */ + PreEffectsVisualOverflowCollector(nsIFrame* aFirstContinuation, + nsIFrame* aCurrentFrame, + const nsRect& aCurrentFrameOverflowArea) + : mFirstContinuation(aFirstContinuation) + , mCurrentFrame(aCurrentFrame) + , mCurrentFrameOverflowArea(aCurrentFrameOverflowArea) + { + NS_ASSERTION(!mFirstContinuation->GetPrevContinuation(), + "We want the first continuation here"); + } + + virtual void AddBox(nsIFrame* aFrame) override { + nsRect overflow = (aFrame == mCurrentFrame) ? + mCurrentFrameOverflowArea : GetPreEffectsVisualOverflowRect(aFrame); + mResult.UnionRect(mResult, overflow + aFrame->GetOffsetTo(mFirstContinuation)); + } + + nsRect GetResult() const { + return mResult; + } + +private: + + static nsRect GetPreEffectsVisualOverflowRect(nsIFrame* aFrame) { + nsRect* r = aFrame->Properties().Get(nsIFrame::PreEffectsBBoxProperty()); + if (r) { + return *r; + } + // Despite the fact that we're invoked for frames with SVG effects applied, + // we can actually get here. All continuations and IB split siblings of a + // frame with SVG effects applied will have the PreEffectsBBoxProperty + // property set on them. Therefore, the frames that are passed to us will + // always have that property set...well, with one exception. If the frames + // for an element with SVG effects applied have been subject to an "IB + // split", then the block frame(s) that caused the split will have been + // wrapped in anonymous, inline-block, nsBlockFrames of pseudo-type + // nsCSSAnonBoxes::mozAnonymousBlock. These "IB split sibling" anonymous + // blocks will have the PreEffectsBBoxProperty property set on them, but + // they will never be passed to us. Instead, we'll be passed the block + // children that they wrap, which don't have the PreEffectsBBoxProperty + // property set on them. This is actually okay. What we care about is + // collecting the _pre_ effects visual overflow rects of the frames to + // which the SVG effects have been applied. Since the IB split results in + // any overflow rect adjustments for transforms, effects, etc. taking + // place on the anonymous block wrappers, the wrapped children are left + // with their overflow rects unaffected. In other words, calling + // GetVisualOverflowRect() on the children will return their pre-effects + // visual overflow rects, just as we need. + // + // A couple of tests that demonstrate the IB split and cause us to get here + // are: + // + // * reftests/svg/svg-integration/clipPath-html-06.xhtml + // * reftests/svg/svg-integration/clipPath-html-06-extref.xhtml + // + // If we ever got passed a frame with the PreTransformOverflowAreasProperty + // property set, that would be bad, since then our GetVisualOverflowRect() + // call would give us the post-effects, and post-transform, overflow rect. + // + NS_ASSERTION(aFrame->GetParent()->StyleContext()->GetPseudo() == + nsCSSAnonBoxes::mozAnonymousBlock, + "How did we getting here, then?"); + NS_ASSERTION(!aFrame->Properties().Get( + aFrame->PreTransformOverflowAreasProperty()), + "GetVisualOverflowRect() won't return the pre-effects rect!"); + return aFrame->GetVisualOverflowRect(); + } + + nsIFrame* mFirstContinuation; + nsIFrame* mCurrentFrame; + const nsRect& mCurrentFrameOverflowArea; + nsRect mResult; +}; + +/** + * Gets the union of the pre-effects visual overflow rects of all of a frame's + * continuations, in "user space". + */ +static nsRect +GetPreEffectsVisualOverflowUnion(nsIFrame* aFirstContinuation, + nsIFrame* aCurrentFrame, + const nsRect& aCurrentFramePreEffectsOverflow, + const nsPoint& aFirstContinuationToUserSpace) +{ + NS_ASSERTION(!aFirstContinuation->GetPrevContinuation(), + "Need first continuation here"); + PreEffectsVisualOverflowCollector collector(aFirstContinuation, + aCurrentFrame, + aCurrentFramePreEffectsOverflow); + // Compute union of all overflow areas relative to aFirstContinuation: + nsLayoutUtils::GetAllInFlowBoxes(aFirstContinuation, &collector); + // Return the result in user space: + return collector.GetResult() + aFirstContinuationToUserSpace; +} + + +bool +nsSVGIntegrationUtils::UsingEffectsForFrame(const nsIFrame* aFrame) +{ + // Even when SVG display lists are disabled, returning true for SVG frames + // does not adversely affect any of our callers. Therefore we don't bother + // checking the SDL prefs here, since we don't know if we're being called for + // painting or hit-testing anyway. + const nsStyleSVGReset *style = aFrame->StyleSVGReset(); + return aFrame->StyleEffects()->HasFilters() || + style->HasClipPath() || + style->mMask.HasLayerWithImage(); +} + +bool +nsSVGIntegrationUtils::UsingMaskOrClipPathForFrame(const nsIFrame* aFrame) +{ + const nsStyleSVGReset *style = aFrame->StyleSVGReset(); + return style->HasClipPath() || + style->mMask.HasLayerWithImage(); +} + +nsPoint +nsSVGIntegrationUtils::GetOffsetToBoundingBox(nsIFrame* aFrame) +{ + if ((aFrame->GetStateBits() & NS_FRAME_SVG_LAYOUT)) { + // Do NOT call GetAllInFlowRectsUnion for SVG - it will get the + // covered region relative to the nsSVGOuterSVGFrame, which is absolutely + // not what we want. SVG frames are always in user space, so they have + // no offset adjustment to make. + return nsPoint(); + } + + // The GetAllInFlowRectsUnion() call gets the union of the frame border-box + // rects over all continuations, relative to the origin (top-left of the + // border box) of its second argument (here, aFrame, the first continuation). + return -nsLayoutUtils::GetAllInFlowRectsUnion(aFrame, aFrame).TopLeft(); +} + +/* static */ nsSize +nsSVGIntegrationUtils::GetContinuationUnionSize(nsIFrame* aNonSVGFrame) +{ + NS_ASSERTION(!aNonSVGFrame->IsFrameOfType(nsIFrame::eSVG), + "SVG frames should not get here"); + nsIFrame* firstFrame = + nsLayoutUtils::FirstContinuationOrIBSplitSibling(aNonSVGFrame); + return nsLayoutUtils::GetAllInFlowRectsUnion(firstFrame, firstFrame).Size(); +} + +/* static */ gfx::Size +nsSVGIntegrationUtils::GetSVGCoordContextForNonSVGFrame(nsIFrame* aNonSVGFrame) +{ + NS_ASSERTION(!aNonSVGFrame->IsFrameOfType(nsIFrame::eSVG), + "SVG frames should not get here"); + nsIFrame* firstFrame = + nsLayoutUtils::FirstContinuationOrIBSplitSibling(aNonSVGFrame); + nsRect r = nsLayoutUtils::GetAllInFlowRectsUnion(firstFrame, firstFrame); + nsPresContext* presContext = firstFrame->PresContext(); + return gfx::Size(presContext->AppUnitsToFloatCSSPixels(r.width), + presContext->AppUnitsToFloatCSSPixels(r.height)); +} + +gfxRect +nsSVGIntegrationUtils::GetSVGBBoxForNonSVGFrame(nsIFrame* aNonSVGFrame) +{ + NS_ASSERTION(!aNonSVGFrame->IsFrameOfType(nsIFrame::eSVG), + "SVG frames should not get here"); + nsIFrame* firstFrame = + nsLayoutUtils::FirstContinuationOrIBSplitSibling(aNonSVGFrame); + // 'r' is in "user space": + nsRect r = GetPreEffectsVisualOverflowUnion(firstFrame, nullptr, nsRect(), + GetOffsetToBoundingBox(firstFrame)); + return nsLayoutUtils::RectToGfxRect(r, + aNonSVGFrame->PresContext()->AppUnitsPerCSSPixel()); +} + +// XXX Since we're called during reflow, this method is broken for frames with +// continuations. When we're called for a frame with continuations, we're +// called for each continuation in turn as it's reflowed. However, it isn't +// until the last continuation is reflowed that this method's +// GetOffsetToBoundingBox() and GetPreEffectsVisualOverflowUnion() calls will +// obtain valid border boxes for all the continuations. As a result, we'll +// end up returning bogus post-filter visual overflow rects for all the prior +// continuations. Unfortunately, by the time the last continuation is +// reflowed, it's too late to go back and set and propagate the overflow +// rects on the previous continuations. +// +// The reason that we need to pass an override bbox to +// GetPreEffectsVisualOverflowUnion rather than just letting it call into our +// GetSVGBBoxForNonSVGFrame method is because we get called by +// ComputeEffectsRect when it has been called with +// aStoreRectProperties set to false. In this case the pre-effects visual +// overflow rect that it has been passed may be different to that stored on +// aFrame, resulting in a different bbox. +// +// XXXjwatt The pre-effects visual overflow rect passed to +// ComputeEffectsRect won't include continuation overflows, so +// for frames with continuation the following filter analysis will likely end +// up being carried out with a bbox created as if the frame didn't have +// continuations. +// +// XXXjwatt Using aPreEffectsOverflowRect to create the bbox isn't really right +// for SVG frames, since for SVG frames the SVG spec defines the bbox to be +// something quite different to the pre-effects visual overflow rect. However, +// we're essentially calculating an invalidation area here, and using the +// pre-effects overflow rect will actually overestimate that area which, while +// being a bit wasteful, isn't otherwise a problem. +// +nsRect + nsSVGIntegrationUtils:: + ComputePostEffectsVisualOverflowRect(nsIFrame* aFrame, + const nsRect& aPreEffectsOverflowRect) +{ + NS_ASSERTION(!(aFrame->GetStateBits() & NS_FRAME_SVG_LAYOUT), + "Don't call this on SVG child frames"); + + nsIFrame* firstFrame = + nsLayoutUtils::FirstContinuationOrIBSplitSibling(aFrame); + nsSVGEffects::EffectProperties effectProperties = + nsSVGEffects::GetEffectProperties(firstFrame); + if (!effectProperties.HasValidFilter()) { + return aPreEffectsOverflowRect; + } + + // Create an override bbox - see comment above: + nsPoint firstFrameToBoundingBox = GetOffsetToBoundingBox(firstFrame); + // overrideBBox is in "user space", in _CSS_ pixels: + // XXX Why are we rounding out to pixel boundaries? We don't do that in + // GetSVGBBoxForNonSVGFrame, and it doesn't appear to be necessary. + gfxRect overrideBBox = + nsLayoutUtils::RectToGfxRect( + GetPreEffectsVisualOverflowUnion(firstFrame, aFrame, + aPreEffectsOverflowRect, + firstFrameToBoundingBox), + aFrame->PresContext()->AppUnitsPerCSSPixel()); + overrideBBox.RoundOut(); + + nsRect overflowRect = + nsFilterInstance::GetPostFilterBounds(firstFrame, &overrideBBox); + + // Return overflowRect relative to aFrame, rather than "user space": + return overflowRect - (aFrame->GetOffsetTo(firstFrame) + firstFrameToBoundingBox); +} + +nsIntRegion +nsSVGIntegrationUtils::AdjustInvalidAreaForSVGEffects(nsIFrame* aFrame, + const nsPoint& aToReferenceFrame, + const nsIntRegion& aInvalidRegion) +{ + if (aInvalidRegion.IsEmpty()) { + return nsIntRect(); + } + + // Don't bother calling GetEffectProperties; the filter property should + // already have been set up during reflow/ComputeFrameEffectsRect + nsIFrame* firstFrame = + nsLayoutUtils::FirstContinuationOrIBSplitSibling(aFrame); + nsSVGFilterProperty *prop = nsSVGEffects::GetFilterProperty(firstFrame); + if (!prop || !prop->IsInObserverLists()) { + return aInvalidRegion; + } + + int32_t appUnitsPerDevPixel = aFrame->PresContext()->AppUnitsPerDevPixel(); + + if (!prop || !prop->ReferencesValidResources()) { + // The frame is either not there or not currently available, + // perhaps because we're in the middle of tearing stuff down. + // Be conservative, return our visual overflow rect relative + // to the reference frame. + nsRect overflow = aFrame->GetVisualOverflowRect() + aToReferenceFrame; + return overflow.ToOutsidePixels(appUnitsPerDevPixel); + } + + // Convert aInvalidRegion into bounding box frame space in app units: + nsPoint toBoundingBox = + aFrame->GetOffsetTo(firstFrame) + GetOffsetToBoundingBox(firstFrame); + // The initial rect was relative to the reference frame, so we need to + // remove that offset to get a rect relative to the current frame. + toBoundingBox -= aToReferenceFrame; + nsRegion preEffectsRegion = aInvalidRegion.ToAppUnits(appUnitsPerDevPixel).MovedBy(toBoundingBox); + + // Adjust the dirty area for effects, and shift it back to being relative to + // the reference frame. + nsRegion result = nsFilterInstance::GetPostFilterDirtyArea(firstFrame, + preEffectsRegion).MovedBy(-toBoundingBox); + // Return the result, in pixels relative to the reference frame. + return result.ToOutsidePixels(appUnitsPerDevPixel); +} + +nsRect +nsSVGIntegrationUtils::GetRequiredSourceForInvalidArea(nsIFrame* aFrame, + const nsRect& aDirtyRect) +{ + // Don't bother calling GetEffectProperties; the filter property should + // already have been set up during reflow/ComputeFrameEffectsRect + nsIFrame* firstFrame = + nsLayoutUtils::FirstContinuationOrIBSplitSibling(aFrame); + nsSVGFilterProperty *prop = nsSVGEffects::GetFilterProperty(firstFrame); + if (!prop || !prop->ReferencesValidResources()) { + return aDirtyRect; + } + + // Convert aDirtyRect into "user space" in app units: + nsPoint toUserSpace = + aFrame->GetOffsetTo(firstFrame) + GetOffsetToBoundingBox(firstFrame); + nsRect postEffectsRect = aDirtyRect + toUserSpace; + + // Return ther result, relative to aFrame, not in user space: + return nsFilterInstance::GetPreFilterNeededArea(firstFrame, postEffectsRect).GetBounds() + - toUserSpace; +} + +bool +nsSVGIntegrationUtils::HitTestFrameForEffects(nsIFrame* aFrame, const nsPoint& aPt) +{ + nsIFrame* firstFrame = + nsLayoutUtils::FirstContinuationOrIBSplitSibling(aFrame); + // Convert aPt to user space: + nsPoint toUserSpace; + if (aFrame->GetStateBits() & NS_FRAME_SVG_LAYOUT) { + // XXXmstange Isn't this wrong for svg:use and innerSVG frames? + toUserSpace = aFrame->GetPosition(); + } else { + toUserSpace = + aFrame->GetOffsetTo(firstFrame) + GetOffsetToBoundingBox(firstFrame); + } + nsPoint pt = aPt + toUserSpace; + gfxPoint userSpacePt = + gfxPoint(pt.x, pt.y) / aFrame->PresContext()->AppUnitsPerCSSPixel(); + return nsSVGUtils::HitTestClip(firstFrame, userSpacePt); +} + +class RegularFramePaintCallback : public nsSVGFilterPaintCallback +{ +public: + RegularFramePaintCallback(nsDisplayListBuilder* aBuilder, + LayerManager* aManager, + const nsPoint& aOffset) + : mBuilder(aBuilder), mLayerManager(aManager), + mOffset(aOffset) {} + + virtual DrawResult Paint(gfxContext& aContext, nsIFrame *aTarget, + const gfxMatrix& aTransform, + const nsIntRect* aDirtyRect) override + { + BasicLayerManager* basic = mLayerManager->AsBasicLayerManager(); + basic->SetTarget(&aContext); + + gfxPoint devPixelOffset = + nsLayoutUtils::PointToGfxPoint(-mOffset, + aTarget->PresContext()->AppUnitsPerDevPixel()); + + gfxContextMatrixAutoSaveRestore autoSR(&aContext); + aContext.SetMatrix(aContext.CurrentMatrix().Translate(devPixelOffset)); + + mLayerManager->EndTransaction(FrameLayerBuilder::DrawPaintedLayer, mBuilder); + return DrawResult::SUCCESS; + } + +private: + nsDisplayListBuilder* mBuilder; + LayerManager* mLayerManager; + nsPoint mOffset; +}; + +/** + * Returns true if any of the masks is an image mask (and not an SVG mask). + */ +static bool +HasNonSVGMask(const nsTArray<nsSVGMaskFrame*>& aMaskFrames) +{ + for (size_t i = 0; i < aMaskFrames.Length() ; i++) { + nsSVGMaskFrame *maskFrame = aMaskFrames[i]; + if (!maskFrame) { + return true; + } + } + + return false; +} + +typedef nsSVGIntegrationUtils::PaintFramesParams PaintFramesParams; + +/** + * Paint css-positioned-mask onto a given target(aMaskDT). + */ +static DrawResult +PaintMaskSurface(const PaintFramesParams& aParams, + DrawTarget* aMaskDT, float aOpacity, nsStyleContext* aSC, + const nsTArray<nsSVGMaskFrame*>& aMaskFrames, + const gfxMatrix& aMaskSurfaceMatrix, + const nsPoint& aOffsetToUserSpace) +{ + MOZ_ASSERT(aMaskFrames.Length() > 0); + MOZ_ASSERT(aMaskDT->GetFormat() == SurfaceFormat::A8); + + const nsStyleSVGReset *svgReset = aSC->StyleSVGReset(); + gfxMatrix cssPxToDevPxMatrix = + nsSVGIntegrationUtils::GetCSSPxToDevPxMatrix(aParams.frame); + + nsPresContext* presContext = aParams.frame->PresContext(); + gfxPoint devPixelOffsetToUserSpace = + nsLayoutUtils::PointToGfxPoint(aOffsetToUserSpace, + presContext->AppUnitsPerDevPixel()); + + RefPtr<gfxContext> maskContext = gfxContext::CreateOrNull(aMaskDT); + MOZ_ASSERT(maskContext); + maskContext->SetMatrix(aMaskSurfaceMatrix); + + // Multiple SVG masks interleave with image mask. Paint each layer onto + // aMaskDT one at a time. + for (int i = aMaskFrames.Length() - 1; i >= 0 ; i--) { + nsSVGMaskFrame *maskFrame = aMaskFrames[i]; + + CompositionOp compositionOp = (i == int(aMaskFrames.Length() - 1)) + ? CompositionOp::OP_OVER + : nsCSSRendering::GetGFXCompositeMode(svgReset->mMask.mLayers[i].mComposite); + + // maskFrame != nullptr means we get a SVG mask. + // maskFrame == nullptr means we get an image mask. + if (maskFrame) { + Matrix svgMaskMatrix; + RefPtr<SourceSurface> svgMask = + maskFrame->GetMaskForMaskedFrame(maskContext, aParams.frame, + cssPxToDevPxMatrix, + aOpacity, + &svgMaskMatrix, + svgReset->mMask.mLayers[i].mMaskMode); + if (svgMask) { + gfxContextMatrixAutoSaveRestore matRestore(maskContext); + + maskContext->Multiply(ThebesMatrix(svgMaskMatrix)); + Rect drawRect = IntRectToRect(IntRect(IntPoint(0, 0), svgMask->GetSize())); + aMaskDT->MaskSurface(ColorPattern(Color(0.0, 0.0, 0.0, 1.0)), svgMask, + drawRect.TopLeft(), + DrawOptions(1.0, compositionOp)); + } + } else { + gfxContextMatrixAutoSaveRestore matRestore(maskContext); + + maskContext->Multiply(gfxMatrix::Translation(-devPixelOffsetToUserSpace)); + nsRenderingContext rc(maskContext); + nsCSSRendering::PaintBGParams params = + nsCSSRendering::PaintBGParams::ForSingleLayer(*presContext, + rc, aParams.dirtyRect, + aParams.borderArea, + aParams.frame, + aParams.builder->GetBackgroundPaintFlags() | + nsCSSRendering::PAINTBG_MASK_IMAGE, + i, compositionOp); + + DrawResult result = + nsCSSRendering::PaintBackgroundWithSC(params, aSC, + *aParams.frame->StyleBorder()); + if (result != DrawResult::SUCCESS) { + return result; + } + } + } + + return DrawResult::SUCCESS; +} + +static DrawResult +CreateAndPaintMaskSurface(const PaintFramesParams& aParams, + float aOpacity, nsStyleContext* aSC, + const nsTArray<nsSVGMaskFrame*>& aMaskFrames, + const nsPoint& aOffsetToUserSpace, + Matrix& aOutMaskTransform, + RefPtr<SourceSurface>& aOutMaskSurface, + bool& aOpacityApplied) +{ + const nsStyleSVGReset *svgReset = aSC->StyleSVGReset(); + MOZ_ASSERT(aMaskFrames.Length() > 0); + + gfxContext& ctx = aParams.ctx; + + // There is only one SVG mask. + if (((aMaskFrames.Length() == 1) && aMaskFrames[0])) { + gfxMatrix cssPxToDevPxMatrix = + nsSVGIntegrationUtils::GetCSSPxToDevPxMatrix(aParams.frame); + + aOpacityApplied = true; + aOutMaskSurface = + aMaskFrames[0]->GetMaskForMaskedFrame(&ctx, aParams.frame, + cssPxToDevPxMatrix, aOpacity, + &aOutMaskTransform, + svgReset->mMask.mLayers[0].mMaskMode); + return DrawResult::SUCCESS; + } + + const IntRect& maskSurfaceRect = aParams.maskRect; + if (maskSurfaceRect.IsEmpty()) { + return DrawResult::SUCCESS; + } + + RefPtr<DrawTarget> maskDT = + ctx.GetDrawTarget()->CreateSimilarDrawTarget(maskSurfaceRect.Size(), + SurfaceFormat::A8); + if (!maskDT || !maskDT->IsValid()) { + return DrawResult::TEMPORARY_ERROR; + } + + // Set aAppliedOpacity as true only if all mask layers are svg mask. + // In this case, we will apply opacity into the final mask surface, so the + // caller does not need to apply it again. + aOpacityApplied = !HasNonSVGMask(aMaskFrames); + + // Set context's matrix on maskContext, offset by the maskSurfaceRect's + // position. This makes sure that we combine the masks in device space. + gfxMatrix maskSurfaceMatrix = + ctx.CurrentMatrix() * gfxMatrix::Translation(-aParams.maskRect.TopLeft()); + + DrawResult result = PaintMaskSurface(aParams, maskDT, + aOpacityApplied ? aOpacity : 1.0, + aSC, aMaskFrames, maskSurfaceMatrix, + aOffsetToUserSpace); + if (result != DrawResult::SUCCESS) { + return result; + } + + aOutMaskTransform = ToMatrix(maskSurfaceMatrix); + if (!aOutMaskTransform.Invert()) { + return DrawResult::SUCCESS; + } + + aOutMaskSurface = maskDT->Snapshot(); + return DrawResult::SUCCESS; +} + +static bool +ValidateSVGFrame(nsIFrame* aFrame) +{ +#ifdef DEBUG + NS_ASSERTION(!(aFrame->GetStateBits() & NS_FRAME_SVG_LAYOUT) || + (NS_SVGDisplayListPaintingEnabled() && + !(aFrame->GetStateBits() & NS_FRAME_IS_NONDISPLAY)), + "Should not use nsSVGIntegrationUtils on this SVG frame"); +#endif + + bool hasSVGLayout = (aFrame->GetStateBits() & NS_FRAME_SVG_LAYOUT); + if (hasSVGLayout) { +#ifdef DEBUG + nsISVGChildFrame *svgChildFrame = do_QueryFrame(aFrame); + MOZ_ASSERT(svgChildFrame && aFrame->GetContent()->IsSVGElement(), + "A non-SVG frame carries NS_FRAME_SVG_LAYOUT flag?"); +#endif + + const nsIContent* content = aFrame->GetContent(); + if (!static_cast<const nsSVGElement*>(content)->HasValidDimensions()) { + // The SVG spec says not to draw _anything_ + return false; + } + } + + return true; +} + +/** + * Setup transform matrix of a gfx context by a specific frame. Depend on + * aClipCtx, this function may clip that context by the visual overflow area + * of aFrame. + * + * @param aFrame is the target frame. + * @param aOffsetToBoundingBox returns the offset between the reference frame + * and the bounding box of aFrame. + * @oaram aOffsetToUserSpace returns the offset between the reference frame and + * the user space coordinate of aFrame. + */ +static void +SetupContextMatrix(nsIFrame* aFrame, const PaintFramesParams& aParams, + nsPoint& aOffsetToBoundingBox, nsPoint& aOffsetToUserSpace) +{ + aOffsetToBoundingBox = aParams.builder->ToReferenceFrame(aFrame) - + nsSVGIntegrationUtils::GetOffsetToBoundingBox(aFrame); + if (!aFrame->IsFrameOfType(nsIFrame::eSVG)) { + /* Snap the offset if the reference frame is not a SVG frame, + * since other frames will be snapped to pixel when rendering. */ + aOffsetToBoundingBox = nsPoint( + aFrame->PresContext()->RoundAppUnitsToNearestDevPixels(aOffsetToBoundingBox.x), + aFrame->PresContext()->RoundAppUnitsToNearestDevPixels(aOffsetToBoundingBox.y)); + } + + // After applying only "aOffsetToBoundingBox", aParams.ctx would have its + // origin at the top left corner of frame's bounding box (over all + // continuations). + // However, SVG painting needs the origin to be located at the origin of the + // SVG frame's "user space", i.e. the space in which, for example, the + // frame's BBox lives. + // SVG geometry frames and foreignObject frames apply their own offsets, so + // their position is relative to their user space. So for these frame types, + // if we want aCtx to be in user space, we first need to subtract the + // frame's position so that SVG painting can later add it again and the + // frame is painted in the right place. + + gfxPoint toUserSpaceGfx = nsSVGUtils::FrameSpaceInCSSPxToUserSpaceOffset(aFrame); + nsPoint toUserSpace = + nsPoint(nsPresContext::CSSPixelsToAppUnits(float(toUserSpaceGfx.x)), + nsPresContext::CSSPixelsToAppUnits(float(toUserSpaceGfx.y))); + + aOffsetToUserSpace = aOffsetToBoundingBox - toUserSpace; + +#ifdef DEBUG + bool hasSVGLayout = (aFrame->GetStateBits() & NS_FRAME_SVG_LAYOUT); + NS_ASSERTION(hasSVGLayout || aOffsetToBoundingBox == aOffsetToUserSpace, + "For non-SVG frames there shouldn't be any additional offset"); +#endif + + gfxPoint devPixelOffsetToUserSpace = + nsLayoutUtils::PointToGfxPoint(aOffsetToUserSpace, + aFrame->PresContext()->AppUnitsPerDevPixel()); + gfxContext& context = aParams.ctx; + context.SetMatrix(context.CurrentMatrix().Translate(devPixelOffsetToUserSpace)); +} + +bool +nsSVGIntegrationUtils::IsMaskResourceReady(nsIFrame* aFrame) +{ + nsIFrame* firstFrame = + nsLayoutUtils::FirstContinuationOrIBSplitSibling(aFrame); + nsSVGEffects::EffectProperties effectProperties = + nsSVGEffects::GetEffectProperties(firstFrame); + nsTArray<nsSVGMaskFrame*> maskFrames = effectProperties.GetMaskFrames(); + const nsStyleSVGReset* svgReset = firstFrame->StyleSVGReset(); + + for (uint32_t i = 0; i < maskFrames.Length(); i++) { + // Refers to a valid SVG mask. + if (maskFrames[i]) { + continue; + } + + // Refers to an external resource, which is not ready yet. + if (!svgReset->mMask.mLayers[i].mImage.IsComplete()) { + return false; + } + } + + // Either all mask resources are ready, or no mask resource is needed. + return true; +} + +DrawResult +nsSVGIntegrationUtils::PaintMask(const PaintFramesParams& aParams) +{ + nsSVGUtils::MaskUsage maskUsage; + nsSVGUtils::DetermineMaskUsage(aParams.frame, aParams.handleOpacity, + maskUsage); + MOZ_ASSERT(maskUsage.shouldGenerateMaskLayer); + + nsIFrame* frame = aParams.frame; + if (!ValidateSVGFrame(frame)) { + return DrawResult::SUCCESS; + } + + if (maskUsage.opacity == 0.0f) { + return DrawResult::SUCCESS; + } + + gfxContext& ctx = aParams.ctx; + + gfxContextMatrixAutoSaveRestore matSR(&ctx); + + nsIFrame* firstFrame = + nsLayoutUtils::FirstContinuationOrIBSplitSibling(frame); + nsSVGEffects::EffectProperties effectProperties = + nsSVGEffects::GetEffectProperties(firstFrame); + nsTArray<nsSVGMaskFrame *> maskFrames = effectProperties.GetMaskFrames(); + bool opacityApplied = !HasNonSVGMask(maskFrames); + + nsPoint offsetToBoundingBox; + nsPoint offsetToUserSpace; + SetupContextMatrix(frame, aParams, offsetToBoundingBox, + offsetToUserSpace); + + return PaintMaskSurface(aParams, ctx.GetDrawTarget(), + opacityApplied ? maskUsage.opacity : 1.0, + firstFrame->StyleContext(), maskFrames, + ctx.CurrentMatrix(), offsetToUserSpace); +} + +DrawResult +nsSVGIntegrationUtils::PaintMaskAndClipPath(const PaintFramesParams& aParams) +{ + MOZ_ASSERT(UsingMaskOrClipPathForFrame(aParams.frame), + "Should not use this method when no mask or clipPath effect" + "on this frame"); + + /* SVG defines the following rendering model: + * + * 1. Render geometry + * 2. Apply filter + * 3. Apply clipping, masking, group opacity + * + * We handle #3 here and perform a couple of optimizations: + * + * + Use cairo's clipPath when representable natively (single object + * clip region). + * + * + Merge opacity and masking if both used together. + */ + nsIFrame* frame = aParams.frame; + DrawResult result = DrawResult::SUCCESS; + if (!ValidateSVGFrame(frame)) { + return result; + } + + nsSVGUtils::MaskUsage maskUsage; + nsSVGUtils::DetermineMaskUsage(aParams.frame, aParams.handleOpacity, + maskUsage); + + if (maskUsage.opacity == 0.0f) { + return DrawResult::SUCCESS; + } + + gfxContext& context = aParams.ctx; + gfxContextMatrixAutoSaveRestore matrixAutoSaveRestore(&context); + + /* Properties are added lazily and may have been removed by a restyle, + so make sure all applicable ones are set again. */ + nsIFrame* firstFrame = + nsLayoutUtils::FirstContinuationOrIBSplitSibling(frame); + nsSVGEffects::EffectProperties effectProperties = + nsSVGEffects::GetEffectProperties(firstFrame); + + bool isOK = effectProperties.HasNoFilterOrHasValidFilter(); + nsSVGClipPathFrame *clipPathFrame = effectProperties.GetClipPathFrame(&isOK); + + gfxMatrix cssPxToDevPxMatrix = GetCSSPxToDevPxMatrix(frame); + nsTArray<nsSVGMaskFrame*> maskFrames = effectProperties.GetMaskFrames(); + + nsPoint offsetToBoundingBox; + nsPoint offsetToUserSpace; + + bool shouldGenerateMask = (maskUsage.opacity != 1.0f || + maskUsage.shouldGenerateClipMaskLayer || + maskUsage.shouldGenerateMaskLayer); + + /* Check if we need to do additional operations on this child's + * rendering, which necessitates rendering into another surface. */ + if (shouldGenerateMask) { + gfxContextMatrixAutoSaveRestore matSR; + + Matrix maskTransform; + RefPtr<SourceSurface> maskSurface; + bool opacityApplied = false; + + if (maskUsage.shouldGenerateMaskLayer) { + matSR.SetContext(&context); + + // For css-mask, we want to generate a mask for each continuation frame, + // so we setup context matrix by the position of the current frame, + // instead of the first continuation frame. + SetupContextMatrix(frame, aParams, offsetToBoundingBox, + offsetToUserSpace); + result = CreateAndPaintMaskSurface(aParams, maskUsage.opacity, + firstFrame->StyleContext(), + maskFrames, offsetToUserSpace, + maskTransform, maskSurface, + opacityApplied); + if (!maskSurface) { + // Entire surface is clipped out. + return result; + } + } + + if (maskUsage.shouldGenerateClipMaskLayer) { + matSR.Restore(); + matSR.SetContext(&context); + + SetupContextMatrix(firstFrame, aParams, offsetToBoundingBox, + offsetToUserSpace); + Matrix clippedMaskTransform; + RefPtr<SourceSurface> clipMaskSurface = + clipPathFrame->GetClipMask(context, frame, cssPxToDevPxMatrix, + &clippedMaskTransform, maskSurface, + maskTransform, &result); + + if (clipMaskSurface) { + maskSurface = clipMaskSurface; + maskTransform = clippedMaskTransform; + } else { + // Either entire surface is clipped out, or gfx buffer allocation + // failure in nsSVGClipPathFrame::GetClipMask. + return result; + } + } + + // opacity != 1.0f. + if (!maskUsage.shouldGenerateClipMaskLayer && + !maskUsage.shouldGenerateMaskLayer) { + MOZ_ASSERT(maskUsage.opacity != 1.0f); + + matSR.SetContext(&context); + SetupContextMatrix(firstFrame, aParams, offsetToBoundingBox, + offsetToUserSpace); + } + + if (aParams.layerManager->GetRoot()->GetContentFlags() & Layer::CONTENT_COMPONENT_ALPHA) { + context.PushGroupAndCopyBackground(gfxContentType::COLOR_ALPHA, + opacityApplied + ? 1.0 + : maskUsage.opacity, + maskSurface, maskTransform); + } else { + context.PushGroupForBlendBack(gfxContentType::COLOR_ALPHA, + opacityApplied ? 1.0 : maskUsage.opacity, + maskSurface, maskTransform); + } + } + + /* If this frame has only a trivial clipPath, set up cairo's clipping now so + * we can just do normal painting and get it clipped appropriately. + */ + if (maskUsage.shouldApplyClipPath || maskUsage.shouldApplyBasicShape) { + gfxContextMatrixAutoSaveRestore matSR(&context); + + SetupContextMatrix(firstFrame, aParams, offsetToBoundingBox, + offsetToUserSpace); + + MOZ_ASSERT(!maskUsage.shouldApplyClipPath || + !maskUsage.shouldApplyBasicShape); + if (maskUsage.shouldApplyClipPath) { + clipPathFrame->ApplyClipPath(context, frame, cssPxToDevPxMatrix); + } else { + nsCSSClipPathInstance::ApplyBasicShapeClip(context, frame); + } + } + + /* Paint the child */ + context.SetMatrix(matrixAutoSaveRestore.Matrix()); + BasicLayerManager* basic = aParams.layerManager->AsBasicLayerManager(); + RefPtr<gfxContext> oldCtx = basic->GetTarget(); + basic->SetTarget(&context); + aParams.layerManager->EndTransaction(FrameLayerBuilder::DrawPaintedLayer, + aParams.builder); + basic->SetTarget(oldCtx); + + if (gfxPrefs::DrawMaskLayer()) { + gfxContextAutoSaveRestore saver(&context); + + context.NewPath(); + gfxRect drawingRect = + nsLayoutUtils::RectToGfxRect(aParams.borderArea, + frame->PresContext()->AppUnitsPerDevPixel()); + context.Rectangle(drawingRect, true); + context.SetColor(Color(0.0, 1.0, 0.0, 1.0)); + context.Fill(); + } + + if (maskUsage.shouldApplyClipPath || maskUsage.shouldApplyBasicShape) { + context.PopClip(); + } + + if (shouldGenerateMask) { + context.PopGroupAndBlend(); + } + + return result; +} + +DrawResult +nsSVGIntegrationUtils::PaintFilter(const PaintFramesParams& aParams) +{ + MOZ_ASSERT(!aParams.builder->IsForGenerateGlyphMask(), + "Filter effect is discarded while generating glyph mask."); + MOZ_ASSERT(aParams.frame->StyleEffects()->HasFilters(), + "Should not use this method when no filter effect on this frame"); + + nsIFrame* frame = aParams.frame; + if (!ValidateSVGFrame(frame)) { + return DrawResult::SUCCESS; + } + + float opacity = nsSVGUtils::ComputeOpacity(frame, aParams.handleOpacity); + if (opacity == 0.0f) { + return DrawResult::SUCCESS; + } + + /* Properties are added lazily and may have been removed by a restyle, + so make sure all applicable ones are set again. */ + nsIFrame* firstFrame = + nsLayoutUtils::FirstContinuationOrIBSplitSibling(frame); + nsSVGEffects::EffectProperties effectProperties = + nsSVGEffects::GetEffectProperties(firstFrame); + + if (!effectProperties.HasValidFilter()) { + return DrawResult::NOT_READY; + } + + gfxContext& context = aParams.ctx; + nsPoint offsetToBoundingBox; + nsPoint offsetToUserSpace; + + gfxContextAutoSaveRestore autoSR(&context); + SetupContextMatrix(firstFrame, aParams, offsetToBoundingBox, + offsetToUserSpace); + + if (opacity != 1.0f) { + context.PushGroupForBlendBack(gfxContentType::COLOR_ALPHA, opacity, + nullptr, Matrix()); + } + + /* Paint the child and apply filters */ + RegularFramePaintCallback callback(aParams.builder, aParams.layerManager, + offsetToUserSpace); + nsRegion dirtyRegion = aParams.dirtyRect - offsetToBoundingBox; + gfxMatrix tm = nsSVGIntegrationUtils::GetCSSPxToDevPxMatrix(frame); + nsFilterInstance::PaintFilteredFrame(frame, context.GetDrawTarget(), + tm, &callback, &dirtyRegion); + + if (opacity != 1.0f) { + context.PopGroupAndBlend(); + } + + return DrawResult::SUCCESS; +} + +gfxMatrix +nsSVGIntegrationUtils::GetCSSPxToDevPxMatrix(nsIFrame* aNonSVGFrame) +{ + int32_t appUnitsPerDevPixel = aNonSVGFrame->PresContext()->AppUnitsPerDevPixel(); + float devPxPerCSSPx = + 1 / nsPresContext::AppUnitsToFloatCSSPixels(appUnitsPerDevPixel); + + return gfxMatrix(devPxPerCSSPx, 0.0, + 0.0, devPxPerCSSPx, + 0.0, 0.0); +} + +class PaintFrameCallback : public gfxDrawingCallback { +public: + PaintFrameCallback(nsIFrame* aFrame, + const nsSize aPaintServerSize, + const IntSize aRenderSize, + uint32_t aFlags) + : mFrame(aFrame) + , mPaintServerSize(aPaintServerSize) + , mRenderSize(aRenderSize) + , mFlags (aFlags) + {} + virtual bool operator()(gfxContext* aContext, + const gfxRect& aFillRect, + const SamplingFilter aSamplingFilter, + const gfxMatrix& aTransform) override; +private: + nsIFrame* mFrame; + nsSize mPaintServerSize; + IntSize mRenderSize; + uint32_t mFlags; +}; + +bool +PaintFrameCallback::operator()(gfxContext* aContext, + const gfxRect& aFillRect, + const SamplingFilter aSamplingFilter, + const gfxMatrix& aTransform) +{ + if (mFrame->GetStateBits() & NS_FRAME_DRAWING_AS_PAINTSERVER) + return false; + + mFrame->AddStateBits(NS_FRAME_DRAWING_AS_PAINTSERVER); + + aContext->Save(); + + // Clip to aFillRect so that we don't paint outside. + aContext->NewPath(); + aContext->Rectangle(aFillRect); + aContext->Clip(); + + gfxMatrix invmatrix = aTransform; + if (!invmatrix.Invert()) { + return false; + } + aContext->Multiply(invmatrix); + + // nsLayoutUtils::PaintFrame will anchor its painting at mFrame. But we want + // to have it anchored at the top left corner of the bounding box of all of + // mFrame's continuations. So we add a translation transform. + int32_t appUnitsPerDevPixel = mFrame->PresContext()->AppUnitsPerDevPixel(); + nsPoint offset = nsSVGIntegrationUtils::GetOffsetToBoundingBox(mFrame); + gfxPoint devPxOffset = gfxPoint(offset.x, offset.y) / appUnitsPerDevPixel; + aContext->Multiply(gfxMatrix::Translation(devPxOffset)); + + gfxSize paintServerSize = + gfxSize(mPaintServerSize.width, mPaintServerSize.height) / + mFrame->PresContext()->AppUnitsPerDevPixel(); + + // nsLayoutUtils::PaintFrame wants to render with paintServerSize, but we + // want it to render with mRenderSize, so we need to set up a scale transform. + gfxFloat scaleX = mRenderSize.width / paintServerSize.width; + gfxFloat scaleY = mRenderSize.height / paintServerSize.height; + aContext->Multiply(gfxMatrix::Scaling(scaleX, scaleY)); + + // Draw. + nsRect dirty(-offset.x, -offset.y, + mPaintServerSize.width, mPaintServerSize.height); + + using PaintFrameFlags = nsLayoutUtils::PaintFrameFlags; + PaintFrameFlags flags = PaintFrameFlags::PAINT_IN_TRANSFORM; + if (mFlags & nsSVGIntegrationUtils::FLAG_SYNC_DECODE_IMAGES) { + flags |= PaintFrameFlags::PAINT_SYNC_DECODE_IMAGES; + } + nsRenderingContext context(aContext); + nsLayoutUtils::PaintFrame(&context, mFrame, + dirty, NS_RGBA(0, 0, 0, 0), + nsDisplayListBuilderMode::PAINTING, + flags); + + nsIFrame* currentFrame = mFrame; + while ((currentFrame = currentFrame->GetNextContinuation()) != nullptr) { + offset = currentFrame->GetOffsetToCrossDoc(mFrame); + devPxOffset = gfxPoint(offset.x, offset.y) / appUnitsPerDevPixel; + + aContext->Save(); + aContext->Multiply(gfxMatrix::Scaling(1/scaleX, 1/scaleY)); + aContext->Multiply(gfxMatrix::Translation(devPxOffset)); + aContext->Multiply(gfxMatrix::Scaling(scaleX, scaleY)); + + nsLayoutUtils::PaintFrame(&context, currentFrame, + dirty - offset, NS_RGBA(0, 0, 0, 0), + nsDisplayListBuilderMode::PAINTING, + flags); + + aContext->Restore(); + } + + aContext->Restore(); + + mFrame->RemoveStateBits(NS_FRAME_DRAWING_AS_PAINTSERVER); + + return true; +} + +/* static */ already_AddRefed<gfxDrawable> +nsSVGIntegrationUtils::DrawableFromPaintServer(nsIFrame* aFrame, + nsIFrame* aTarget, + const nsSize& aPaintServerSize, + const IntSize& aRenderSize, + const DrawTarget* aDrawTarget, + const gfxMatrix& aContextMatrix, + uint32_t aFlags) +{ + // aPaintServerSize is the size that would be filled when using + // background-repeat:no-repeat and background-size:auto. For normal background + // images, this would be the intrinsic size of the image; for gradients and + // patterns this would be the whole target frame fill area. + // aRenderSize is what we will be actually filling after accounting for + // background-size. + if (aFrame->IsFrameOfType(nsIFrame::eSVGPaintServer)) { + // aFrame is either a pattern or a gradient. These fill the whole target + // frame by default, so aPaintServerSize is the whole target background fill + // area. + nsSVGPaintServerFrame* server = + static_cast<nsSVGPaintServerFrame*>(aFrame); + + gfxRect overrideBounds(0, 0, + aPaintServerSize.width, aPaintServerSize.height); + overrideBounds.ScaleInverse(aFrame->PresContext()->AppUnitsPerDevPixel()); + RefPtr<gfxPattern> pattern = + server->GetPaintServerPattern(aTarget, aDrawTarget, + aContextMatrix, &nsStyleSVG::mFill, 1.0, + &overrideBounds); + + if (!pattern) + return nullptr; + + // pattern is now set up to fill aPaintServerSize. But we want it to + // fill aRenderSize, so we need to add a scaling transform. + // We couldn't just have set overrideBounds to aRenderSize - it would have + // worked for gradients, but for patterns it would result in a different + // pattern size. + gfxFloat scaleX = overrideBounds.Width() / aRenderSize.width; + gfxFloat scaleY = overrideBounds.Height() / aRenderSize.height; + gfxMatrix scaleMatrix = gfxMatrix::Scaling(scaleX, scaleY); + pattern->SetMatrix(scaleMatrix * pattern->GetMatrix()); + RefPtr<gfxDrawable> drawable = + new gfxPatternDrawable(pattern, aRenderSize); + return drawable.forget(); + } + + if (aFrame->IsFrameOfType(nsIFrame::eSVG) && + !static_cast<nsISVGChildFrame*>(do_QueryFrame(aFrame))) { + MOZ_ASSERT_UNREACHABLE("We should prevent painting of unpaintable SVG " + "before we get here"); + return nullptr; + } + + // We don't want to paint into a surface as long as we don't need to, so we + // set up a drawing callback. + RefPtr<gfxDrawingCallback> cb = + new PaintFrameCallback(aFrame, aPaintServerSize, aRenderSize, aFlags); + RefPtr<gfxDrawable> drawable = new gfxCallbackDrawable(cb, aRenderSize); + return drawable.forget(); +} |