diff -r 000000000000 -r 6474c204b198 layout/svg/nsSVGIntegrationUtils.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/layout/svg/nsSVGIntegrationUtils.cpp Wed Dec 31 06:09:35 2014 +0100 @@ -0,0 +1,721 @@ +/* -*- 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 "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" + +using namespace mozilla; +using namespace mozilla::layers; + +// ---------------------------------------------------------------------- + +/** + * 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) MOZ_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 = static_cast + (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 (style->HasFilters() || style->mClipPath || style->mMask); +} + +// For non-SVG frames, this gives the offset to the frame's "user space". +// For SVG frames, this returns a zero offset. +static nsPoint +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(); + } + // We could allow aFrame to be any continuation, but since that would require + // a GetPrevContinuation() virtual call and conditional returns, and since + // all our current consumers always pass in the first continuation, we don't + // currently bother. + NS_ASSERTION(!aFrame->GetPrevContinuation(), "Not first continuation"); + + // 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; + return nsSVGUtils::HitTestClip(firstFrame, pt); +} + +class RegularFramePaintCallback : public nsSVGFilterPaintCallback +{ +public: + RegularFramePaintCallback(nsDisplayListBuilder* aBuilder, + LayerManager* aManager, + const nsPoint& aOffset) + : mBuilder(aBuilder), mLayerManager(aManager), + mOffset(aOffset) {} + + virtual void Paint(nsRenderingContext *aContext, nsIFrame *aTarget, + const nsIntRect* aDirtyRect, + nsIFrame* aTransformRoot) MOZ_OVERRIDE + { + BasicLayerManager* basic = static_cast(mLayerManager); + basic->SetTarget(aContext->ThebesContext()); + nsRenderingContext::AutoPushTranslation push(aContext, -mOffset); + mLayerManager->EndTransaction(FrameLayerBuilder::DrawThebesLayer, mBuilder); + } + +private: + nsDisplayListBuilder* mBuilder; + LayerManager* mLayerManager; + nsPoint mOffset; +}; + +void +nsSVGIntegrationUtils::PaintFramesWithEffects(nsRenderingContext* aCtx, + nsIFrame* aFrame, + const nsRect& aDirtyRect, + nsDisplayListBuilder* aBuilder, + LayerManager *aLayerManager) +{ +#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 + + /* SVG defines the following rendering model: + * + * 1. Render geometry + * 2. Apply filter + * 3. Apply clipping, masking, group opacity + * + * We follow this, but perform a couple of optimizations: + * + * + Use cairo's clipPath when representable natively (single object + * clip region). + * + * + Merge opacity and masking if both used together. + */ + + const nsIContent* content = aFrame->GetContent(); + bool hasSVGLayout = (aFrame->GetStateBits() & NS_FRAME_SVG_LAYOUT); + if (hasSVGLayout) { + nsISVGChildFrame *svgChildFrame = do_QueryFrame(aFrame); + if (!svgChildFrame || !aFrame->GetContent()->IsSVG()) { + NS_ASSERTION(false, "why?"); + return; + } + if (!static_cast(content)->HasValidDimensions()) { + return; // The SVG spec says not to draw _anything_ + } + } + + float opacity = aFrame->StyleDisplay()->mOpacity; + if (opacity == 0.0f) { + return; + } + if (opacity != 1.0f && + hasSVGLayout && nsSVGUtils::CanOptimizeOpacity(aFrame)) { + opacity = 1.0f; + } + + /* 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(aFrame); + nsSVGEffects::EffectProperties effectProperties = + nsSVGEffects::GetEffectProperties(firstFrame); + + bool isOK = effectProperties.HasNoFilterOrHasValidFilter(); + nsSVGClipPathFrame *clipPathFrame = effectProperties.GetClipPathFrame(&isOK); + nsSVGMaskFrame *maskFrame = effectProperties.GetMaskFrame(&isOK); + if (!isOK) { + return; // Some resource is missing. We shouldn't paint anything. + } + + bool isTrivialClip = clipPathFrame ? clipPathFrame->IsTrivial() : true; + + gfxContext* gfx = aCtx->ThebesContext(); + gfxContextMatrixAutoSaveRestore matrixAutoSaveRestore(gfx); + + nsPoint firstFrameOffset = GetOffsetToBoundingBox(firstFrame); + nsPoint offsetToBoundingBox = aBuilder->ToReferenceFrame(firstFrame) - firstFrameOffset; + if (!firstFrame->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. */ + offsetToBoundingBox = nsPoint( + aFrame->PresContext()->RoundAppUnitsToNearestDevPixels(offsetToBoundingBox.x), + aFrame->PresContext()->RoundAppUnitsToNearestDevPixels(offsetToBoundingBox.y)); + } + + // After applying only "offsetToBoundingBox", aCtx would have its origin at + // the top left corner of aFrame'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(nsPresContext::CSSPixelsToAppUnits(float(toUserSpaceGfx.x)), + nsPresContext::CSSPixelsToAppUnits(float(toUserSpaceGfx.y))); + nsPoint offsetToUserSpace = offsetToBoundingBox - toUserSpace; + + NS_ASSERTION(hasSVGLayout || offsetToBoundingBox == offsetToUserSpace, + "For non-SVG frames there shouldn't be any additional offset"); + + aCtx->Translate(offsetToUserSpace); + + gfxMatrix cssPxToDevPxMatrix = GetCSSPxToDevPxMatrix(aFrame); + + bool complexEffects = false; + /* Check if we need to do additional operations on this child's + * rendering, which necessitates rendering into another surface. */ + if (opacity != 1.0f || maskFrame || (clipPathFrame && !isTrivialClip) + || aFrame->StyleDisplay()->mMixBlendMode != NS_STYLE_BLEND_NORMAL) { + complexEffects = true; + gfx->Save(); + aCtx->IntersectClip(aFrame->GetVisualOverflowRectRelativeToSelf() + + toUserSpace); + gfx->PushGroup(gfxContentType::COLOR_ALPHA); + } + + /* 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 (clipPathFrame && isTrivialClip) { + gfx->Save(); + clipPathFrame->ClipPaint(aCtx, aFrame, cssPxToDevPxMatrix); + } + + /* Paint the child */ + if (effectProperties.HasValidFilter()) { + RegularFramePaintCallback callback(aBuilder, aLayerManager, + offsetToUserSpace); + + nsRegion dirtyRegion = aDirtyRect - offsetToBoundingBox; + nsFilterInstance::PaintFilteredFrame(aCtx, aFrame, &callback, &dirtyRegion); + } else { + gfx->SetMatrix(matrixAutoSaveRestore.Matrix()); + aLayerManager->EndTransaction(FrameLayerBuilder::DrawThebesLayer, aBuilder); + aCtx->Translate(offsetToUserSpace); + } + + if (clipPathFrame && isTrivialClip) { + gfx->Restore(); + } + + /* No more effects, we're done. */ + if (!complexEffects) { + return; + } + + gfx->PopGroupToSource(); + + nsRefPtr maskSurface = + maskFrame ? maskFrame->ComputeMaskAlpha(aCtx, aFrame, + cssPxToDevPxMatrix, opacity) : nullptr; + + nsRefPtr clipMaskSurface; + if (clipPathFrame && !isTrivialClip) { + gfx->PushGroup(gfxContentType::COLOR_ALPHA); + + nsresult rv = clipPathFrame->ClipPaint(aCtx, aFrame, cssPxToDevPxMatrix); + clipMaskSurface = gfx->PopGroup(); + + if (NS_SUCCEEDED(rv) && clipMaskSurface) { + // Still more set after clipping, so clip to another surface + if (maskSurface || opacity != 1.0f) { + gfx->PushGroup(gfxContentType::COLOR_ALPHA); + gfx->Mask(clipMaskSurface); + gfx->PopGroupToSource(); + } else { + gfx->Mask(clipMaskSurface); + } + } + } + + if (maskSurface) { + gfx->Mask(maskSurface); + } else if (opacity != 1.0f) { + gfx->Paint(opacity); + } + + gfx->Restore(); +} + +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 gfxIntSize aRenderSize, + uint32_t aFlags) + : mFrame(aFrame) + , mPaintServerSize(aPaintServerSize) + , mRenderSize(aRenderSize) + , mFlags (aFlags) + {} + virtual bool operator()(gfxContext* aContext, + const gfxRect& aFillRect, + const GraphicsFilter& aFilter, + const gfxMatrix& aTransform) MOZ_OVERRIDE; +private: + nsIFrame* mFrame; + nsSize mPaintServerSize; + gfxIntSize mRenderSize; + uint32_t mFlags; +}; + +bool +PaintFrameCallback::operator()(gfxContext* aContext, + const gfxRect& aFillRect, + const GraphicsFilter& aFilter, + const gfxMatrix& aTransform) +{ + if (mFrame->GetStateBits() & NS_FRAME_DRAWING_AS_PAINTSERVER) + return false; + + mFrame->AddStateBits(NS_FRAME_DRAWING_AS_PAINTSERVER); + + nsRefPtr context(new nsRenderingContext()); + context->Init(mFrame->PresContext()->DeviceContext(), aContext); + aContext->Save(); + + // Clip to aFillRect so that we don't paint outside. + aContext->NewPath(); + aContext->Rectangle(aFillRect); + aContext->Clip(); + + aContext->Multiply(gfxMatrix(aTransform).Invert()); + + // 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 = GetOffsetToBoundingBox(mFrame); + gfxPoint devPxOffset = gfxPoint(offset.x, offset.y) / appUnitsPerDevPixel; + aContext->Multiply(gfxMatrix().Translate(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; + gfxMatrix scaleMatrix = gfxMatrix().Scale(scaleX, scaleY); + aContext->Multiply(scaleMatrix); + + // Draw. + nsRect dirty(-offset.x, -offset.y, + mPaintServerSize.width, mPaintServerSize.height); + + uint32_t flags = nsLayoutUtils::PAINT_IN_TRANSFORM | + nsLayoutUtils::PAINT_ALL_CONTINUATIONS; + if (mFlags & nsSVGIntegrationUtils::FLAG_SYNC_DECODE_IMAGES) { + flags |= nsLayoutUtils::PAINT_SYNC_DECODE_IMAGES; + } + nsLayoutUtils::PaintFrame(context, mFrame, + dirty, NS_RGBA(0, 0, 0, 0), + flags); + + aContext->Restore(); + + mFrame->RemoveStateBits(NS_FRAME_DRAWING_AS_PAINTSERVER); + + return true; +} + +/* static */ already_AddRefed +nsSVGIntegrationUtils::DrawableFromPaintServer(nsIFrame* aFrame, + nsIFrame* aTarget, + const nsSize& aPaintServerSize, + const gfxIntSize& aRenderSize, + 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()); + nsRefPtr pattern = + server->GetPaintServerPattern(aTarget, 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().Scale(scaleX, scaleY); + pattern->SetMatrix(scaleMatrix.Multiply(pattern->GetMatrix())); + nsRefPtr drawable = + new gfxPatternDrawable(pattern, aRenderSize); + return drawable.forget(); + } + + // We don't want to paint into a surface as long as we don't need to, so we + // set up a drawing callback. + nsRefPtr cb = + new PaintFrameCallback(aFrame, aPaintServerSize, aRenderSize, aFlags); + nsRefPtr drawable = new gfxCallbackDrawable(cb, aRenderSize); + return drawable.forget(); +}