/* -*- 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->GetProperty(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->GetProperty(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& 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& 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 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 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; } struct MaskPaintResult { RefPtr maskSurface; Matrix maskTransform; DrawResult result; bool transparentBlackMask; bool opacityApplied; MaskPaintResult() : result(DrawResult::SUCCESS), transparentBlackMask(false), opacityApplied(false) {} }; static MaskPaintResult CreateAndPaintMaskSurface(const PaintFramesParams& aParams, float aOpacity, nsStyleContext* aSC, const nsTArray& aMaskFrames, const nsPoint& aOffsetToUserSpace) { const nsStyleSVGReset *svgReset = aSC->StyleSVGReset(); MOZ_ASSERT(aMaskFrames.Length() > 0); MaskPaintResult paintResult; gfxContext& ctx = aParams.ctx; // Optimization for single SVG mask. if (((aMaskFrames.Length() == 1) && aMaskFrames[0])) { gfxMatrix cssPxToDevPxMatrix = nsSVGIntegrationUtils::GetCSSPxToDevPxMatrix(aParams.frame); paintResult.opacityApplied = true; paintResult.maskSurface = aMaskFrames[0]->GetMaskForMaskedFrame(&ctx, aParams.frame, cssPxToDevPxMatrix, aOpacity, &paintResult.maskTransform, svgReset->mMask.mLayers[0].mMaskMode); if (!paintResult.maskSurface) { paintResult.transparentBlackMask = true; } return paintResult; } const IntRect& maskSurfaceRect = aParams.maskRect; if (maskSurfaceRect.IsEmpty()) { paintResult.transparentBlackMask = true; return paintResult; } RefPtr maskDT = ctx.GetDrawTarget()->CreateSimilarDrawTarget(maskSurfaceRect.Size(), SurfaceFormat::A8); if (!maskDT || !maskDT->IsValid()) { paintResult.result = DrawResult::TEMPORARY_ERROR; return paintResult; } // 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. paintResult.opacityApplied = !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()); paintResult.result = PaintMaskSurface(aParams, maskDT, paintResult.opacityApplied ? aOpacity : 1.0, aSC, aMaskFrames, maskSurfaceMatrix, aOffsetToUserSpace); if (paintResult.result != DrawResult::SUCCESS) { // Now we know the status of mask resource since we used it while painting. // According to the return value of PaintMaskSurface, we know whether mask // resource is resolvable or not. // // For a HTML doc: // According to css-masking spec, always create a mask surface when // we have any item in maskFrame even if all of those items are // non-resolvable or . // Set paintResult.transparentBlackMask as true, the caller should stop // painting masked content as if this mask is a transparent black one. // For a SVG doc: // SVG 1.1 say that if we fail to resolve a mask, we should draw the // object unmasked. // Left patinResult.maskSurface empty, the caller should paint all // masked content as if this mask is an opaque white one(no mask). paintResult.transparentBlackMask = !(aParams.frame->GetStateBits() & NS_FRAME_SVG_LAYOUT); MOZ_ASSERT(!paintResult.maskSurface); return paintResult; } paintResult.maskTransform = ToMatrix(maskSurfaceMatrix); if (!paintResult.maskTransform.Invert()) { return paintResult; } paintResult.maskSurface = maskDT->Snapshot(); return paintResult; } 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(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 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 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 maskFrames = effectProperties.GetMaskFrames(); nsPoint offsetToBoundingBox; nsPoint offsetToUserSpace; bool shouldGenerateMask = (maskUsage.opacity != 1.0f || maskUsage.shouldGenerateClipMaskLayer || maskUsage.shouldGenerateMaskLayer); bool shouldPushMask = false; /* 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 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); MaskPaintResult paintResult = CreateAndPaintMaskSurface(aParams, maskUsage.opacity, firstFrame->StyleContext(), maskFrames, offsetToUserSpace); if (paintResult.transparentBlackMask) { MOZ_ASSERT(paintResult.result != DrawResult::SUCCESS); return paintResult.result; } result &= paintResult.result; maskSurface = paintResult.maskSurface; if (maskSurface) { MOZ_ASSERT(paintResult.result == DrawResult::SUCCESS); shouldPushMask = true; maskTransform = paintResult.maskTransform; opacityApplied = paintResult.opacityApplied; } } if (maskUsage.shouldGenerateClipMaskLayer) { matSR.Restore(); matSR.SetContext(&context); SetupContextMatrix(firstFrame, aParams, offsetToBoundingBox, offsetToUserSpace); Matrix clippedMaskTransform; RefPtr 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; } shouldPushMask = true; } // opacity != 1.0f. if (!maskUsage.shouldGenerateClipMaskLayer && !maskUsage.shouldGenerateMaskLayer) { MOZ_ASSERT(maskUsage.opacity != 1.0f); matSR.SetContext(&context); SetupContextMatrix(firstFrame, aParams, offsetToBoundingBox, offsetToUserSpace); shouldPushMask = true; } if (shouldPushMask) { 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 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 (shouldPushMask) { 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 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(aFrame); gfxRect overrideBounds(0, 0, aPaintServerSize.width, aPaintServerSize.height); overrideBounds.ScaleInverse(aFrame->PresContext()->AppUnitsPerDevPixel()); RefPtr 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 drawable = new gfxPatternDrawable(pattern, aRenderSize); return drawable.forget(); } if (aFrame->IsFrameOfType(nsIFrame::eSVG) && !static_cast(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 cb = new PaintFrameCallback(aFrame, aPaintServerSize, aRenderSize, aFlags); RefPtr drawable = new gfxCallbackDrawable(cb, aRenderSize); return drawable.forget(); }