michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: // Main header first: michael@0: #include "nsSVGIntegrationUtils.h" michael@0: michael@0: // Keep others in (case-insensitive) order: michael@0: #include "gfxDrawable.h" michael@0: #include "nsCSSAnonBoxes.h" michael@0: #include "nsDisplayList.h" michael@0: #include "nsFilterInstance.h" michael@0: #include "nsLayoutUtils.h" michael@0: #include "nsRenderingContext.h" michael@0: #include "nsSVGClipPathFrame.h" michael@0: #include "nsSVGEffects.h" michael@0: #include "nsSVGElement.h" michael@0: #include "nsSVGFilterPaintCallback.h" michael@0: #include "nsSVGMaskFrame.h" michael@0: #include "nsSVGPaintServerFrame.h" michael@0: #include "nsSVGUtils.h" michael@0: #include "FrameLayerBuilder.h" michael@0: #include "BasicLayers.h" michael@0: #include "mozilla/gfx/Point.h" michael@0: michael@0: using namespace mozilla; michael@0: using namespace mozilla::layers; michael@0: michael@0: // ---------------------------------------------------------------------- michael@0: michael@0: /** michael@0: * This class is used to get the pre-effects visual overflow rect of a frame, michael@0: * or, in the case of a frame with continuations, to collect the union of the michael@0: * pre-effects visual overflow rects of all the continuations. The result is michael@0: * relative to the origin (top left corner of the border box) of the frame, or, michael@0: * if the frame has continuations, the origin of the _first_ continuation. michael@0: */ michael@0: class PreEffectsVisualOverflowCollector : public nsLayoutUtils::BoxCallback michael@0: { michael@0: public: michael@0: /** michael@0: * If the pre-effects visual overflow rect of the frame being examined michael@0: * happens to be known, it can be passed in as aCurrentFrame and its michael@0: * pre-effects visual overflow rect can be passed in as michael@0: * aCurrentFrameOverflowArea. This is just an optimization to save a michael@0: * frame property lookup - these arguments are optional. michael@0: */ michael@0: PreEffectsVisualOverflowCollector(nsIFrame* aFirstContinuation, michael@0: nsIFrame* aCurrentFrame, michael@0: const nsRect& aCurrentFrameOverflowArea) michael@0: : mFirstContinuation(aFirstContinuation) michael@0: , mCurrentFrame(aCurrentFrame) michael@0: , mCurrentFrameOverflowArea(aCurrentFrameOverflowArea) michael@0: { michael@0: NS_ASSERTION(!mFirstContinuation->GetPrevContinuation(), michael@0: "We want the first continuation here"); michael@0: } michael@0: michael@0: virtual void AddBox(nsIFrame* aFrame) MOZ_OVERRIDE { michael@0: nsRect overflow = (aFrame == mCurrentFrame) ? michael@0: mCurrentFrameOverflowArea : GetPreEffectsVisualOverflowRect(aFrame); michael@0: mResult.UnionRect(mResult, overflow + aFrame->GetOffsetTo(mFirstContinuation)); michael@0: } michael@0: michael@0: nsRect GetResult() const { michael@0: return mResult; michael@0: } michael@0: michael@0: private: michael@0: michael@0: static nsRect GetPreEffectsVisualOverflowRect(nsIFrame* aFrame) { michael@0: nsRect* r = static_cast michael@0: (aFrame->Properties().Get(nsIFrame::PreEffectsBBoxProperty())); michael@0: if (r) { michael@0: return *r; michael@0: } michael@0: // Despite the fact that we're invoked for frames with SVG effects applied, michael@0: // we can actually get here. All continuations and IB split siblings of a michael@0: // frame with SVG effects applied will have the PreEffectsBBoxProperty michael@0: // property set on them. Therefore, the frames that are passed to us will michael@0: // always have that property set...well, with one exception. If the frames michael@0: // for an element with SVG effects applied have been subject to an "IB michael@0: // split", then the block frame(s) that caused the split will have been michael@0: // wrapped in anonymous, inline-block, nsBlockFrames of pseudo-type michael@0: // nsCSSAnonBoxes::mozAnonymousBlock. These "IB split sibling" anonymous michael@0: // blocks will have the PreEffectsBBoxProperty property set on them, but michael@0: // they will never be passed to us. Instead, we'll be passed the block michael@0: // children that they wrap, which don't have the PreEffectsBBoxProperty michael@0: // property set on them. This is actually okay. What we care about is michael@0: // collecting the _pre_ effects visual overflow rects of the frames to michael@0: // which the SVG effects have been applied. Since the IB split results in michael@0: // any overflow rect adjustments for transforms, effects, etc. taking michael@0: // place on the anonymous block wrappers, the wrapped children are left michael@0: // with their overflow rects unaffected. In other words, calling michael@0: // GetVisualOverflowRect() on the children will return their pre-effects michael@0: // visual overflow rects, just as we need. michael@0: // michael@0: // A couple of tests that demonstrate the IB split and cause us to get here michael@0: // are: michael@0: // michael@0: // * reftests/svg/svg-integration/clipPath-html-06.xhtml michael@0: // * reftests/svg/svg-integration/clipPath-html-06-extref.xhtml michael@0: // michael@0: // If we ever got passed a frame with the PreTransformOverflowAreasProperty michael@0: // property set, that would be bad, since then our GetVisualOverflowRect() michael@0: // call would give us the post-effects, and post-transform, overflow rect. michael@0: // michael@0: NS_ASSERTION(aFrame->GetParent()->StyleContext()->GetPseudo() == michael@0: nsCSSAnonBoxes::mozAnonymousBlock, michael@0: "How did we getting here, then?"); michael@0: NS_ASSERTION(!aFrame->Properties().Get( michael@0: aFrame->PreTransformOverflowAreasProperty()), michael@0: "GetVisualOverflowRect() won't return the pre-effects rect!"); michael@0: return aFrame->GetVisualOverflowRect(); michael@0: } michael@0: michael@0: nsIFrame* mFirstContinuation; michael@0: nsIFrame* mCurrentFrame; michael@0: const nsRect& mCurrentFrameOverflowArea; michael@0: nsRect mResult; michael@0: }; michael@0: michael@0: /** michael@0: * Gets the union of the pre-effects visual overflow rects of all of a frame's michael@0: * continuations, in "user space". michael@0: */ michael@0: static nsRect michael@0: GetPreEffectsVisualOverflowUnion(nsIFrame* aFirstContinuation, michael@0: nsIFrame* aCurrentFrame, michael@0: const nsRect& aCurrentFramePreEffectsOverflow, michael@0: const nsPoint& aFirstContinuationToUserSpace) michael@0: { michael@0: NS_ASSERTION(!aFirstContinuation->GetPrevContinuation(), michael@0: "Need first continuation here"); michael@0: PreEffectsVisualOverflowCollector collector(aFirstContinuation, michael@0: aCurrentFrame, michael@0: aCurrentFramePreEffectsOverflow); michael@0: // Compute union of all overflow areas relative to aFirstContinuation: michael@0: nsLayoutUtils::GetAllInFlowBoxes(aFirstContinuation, &collector); michael@0: // Return the result in user space: michael@0: return collector.GetResult() + aFirstContinuationToUserSpace; michael@0: } michael@0: michael@0: michael@0: bool michael@0: nsSVGIntegrationUtils::UsingEffectsForFrame(const nsIFrame* aFrame) michael@0: { michael@0: // Even when SVG display lists are disabled, returning true for SVG frames michael@0: // does not adversely affect any of our callers. Therefore we don't bother michael@0: // checking the SDL prefs here, since we don't know if we're being called for michael@0: // painting or hit-testing anyway. michael@0: const nsStyleSVGReset *style = aFrame->StyleSVGReset(); michael@0: return (style->HasFilters() || style->mClipPath || style->mMask); michael@0: } michael@0: michael@0: // For non-SVG frames, this gives the offset to the frame's "user space". michael@0: // For SVG frames, this returns a zero offset. michael@0: static nsPoint michael@0: GetOffsetToBoundingBox(nsIFrame* aFrame) michael@0: { michael@0: if ((aFrame->GetStateBits() & NS_FRAME_SVG_LAYOUT)) { michael@0: // Do NOT call GetAllInFlowRectsUnion for SVG - it will get the michael@0: // covered region relative to the nsSVGOuterSVGFrame, which is absolutely michael@0: // not what we want. SVG frames are always in user space, so they have michael@0: // no offset adjustment to make. michael@0: return nsPoint(); michael@0: } michael@0: // We could allow aFrame to be any continuation, but since that would require michael@0: // a GetPrevContinuation() virtual call and conditional returns, and since michael@0: // all our current consumers always pass in the first continuation, we don't michael@0: // currently bother. michael@0: NS_ASSERTION(!aFrame->GetPrevContinuation(), "Not first continuation"); michael@0: michael@0: // The GetAllInFlowRectsUnion() call gets the union of the frame border-box michael@0: // rects over all continuations, relative to the origin (top-left of the michael@0: // border box) of its second argument (here, aFrame, the first continuation). michael@0: return -nsLayoutUtils::GetAllInFlowRectsUnion(aFrame, aFrame).TopLeft(); michael@0: } michael@0: michael@0: /* static */ nsSize michael@0: nsSVGIntegrationUtils::GetContinuationUnionSize(nsIFrame* aNonSVGFrame) michael@0: { michael@0: NS_ASSERTION(!aNonSVGFrame->IsFrameOfType(nsIFrame::eSVG), michael@0: "SVG frames should not get here"); michael@0: nsIFrame* firstFrame = michael@0: nsLayoutUtils::FirstContinuationOrIBSplitSibling(aNonSVGFrame); michael@0: return nsLayoutUtils::GetAllInFlowRectsUnion(firstFrame, firstFrame).Size(); michael@0: } michael@0: michael@0: /* static */ gfx::Size michael@0: nsSVGIntegrationUtils::GetSVGCoordContextForNonSVGFrame(nsIFrame* aNonSVGFrame) michael@0: { michael@0: NS_ASSERTION(!aNonSVGFrame->IsFrameOfType(nsIFrame::eSVG), michael@0: "SVG frames should not get here"); michael@0: nsIFrame* firstFrame = michael@0: nsLayoutUtils::FirstContinuationOrIBSplitSibling(aNonSVGFrame); michael@0: nsRect r = nsLayoutUtils::GetAllInFlowRectsUnion(firstFrame, firstFrame); michael@0: nsPresContext* presContext = firstFrame->PresContext(); michael@0: return gfx::Size(presContext->AppUnitsToFloatCSSPixels(r.width), michael@0: presContext->AppUnitsToFloatCSSPixels(r.height)); michael@0: } michael@0: michael@0: gfxRect michael@0: nsSVGIntegrationUtils::GetSVGBBoxForNonSVGFrame(nsIFrame* aNonSVGFrame) michael@0: { michael@0: NS_ASSERTION(!aNonSVGFrame->IsFrameOfType(nsIFrame::eSVG), michael@0: "SVG frames should not get here"); michael@0: nsIFrame* firstFrame = michael@0: nsLayoutUtils::FirstContinuationOrIBSplitSibling(aNonSVGFrame); michael@0: // 'r' is in "user space": michael@0: nsRect r = GetPreEffectsVisualOverflowUnion(firstFrame, nullptr, nsRect(), michael@0: GetOffsetToBoundingBox(firstFrame)); michael@0: return nsLayoutUtils::RectToGfxRect(r, michael@0: aNonSVGFrame->PresContext()->AppUnitsPerCSSPixel()); michael@0: } michael@0: michael@0: // XXX Since we're called during reflow, this method is broken for frames with michael@0: // continuations. When we're called for a frame with continuations, we're michael@0: // called for each continuation in turn as it's reflowed. However, it isn't michael@0: // until the last continuation is reflowed that this method's michael@0: // GetOffsetToBoundingBox() and GetPreEffectsVisualOverflowUnion() calls will michael@0: // obtain valid border boxes for all the continuations. As a result, we'll michael@0: // end up returning bogus post-filter visual overflow rects for all the prior michael@0: // continuations. Unfortunately, by the time the last continuation is michael@0: // reflowed, it's too late to go back and set and propagate the overflow michael@0: // rects on the previous continuations. michael@0: // michael@0: // The reason that we need to pass an override bbox to michael@0: // GetPreEffectsVisualOverflowUnion rather than just letting it call into our michael@0: // GetSVGBBoxForNonSVGFrame method is because we get called by michael@0: // ComputeEffectsRect when it has been called with michael@0: // aStoreRectProperties set to false. In this case the pre-effects visual michael@0: // overflow rect that it has been passed may be different to that stored on michael@0: // aFrame, resulting in a different bbox. michael@0: // michael@0: // XXXjwatt The pre-effects visual overflow rect passed to michael@0: // ComputeEffectsRect won't include continuation overflows, so michael@0: // for frames with continuation the following filter analysis will likely end michael@0: // up being carried out with a bbox created as if the frame didn't have michael@0: // continuations. michael@0: // michael@0: // XXXjwatt Using aPreEffectsOverflowRect to create the bbox isn't really right michael@0: // for SVG frames, since for SVG frames the SVG spec defines the bbox to be michael@0: // something quite different to the pre-effects visual overflow rect. However, michael@0: // we're essentially calculating an invalidation area here, and using the michael@0: // pre-effects overflow rect will actually overestimate that area which, while michael@0: // being a bit wasteful, isn't otherwise a problem. michael@0: // michael@0: nsRect michael@0: nsSVGIntegrationUtils:: michael@0: ComputePostEffectsVisualOverflowRect(nsIFrame* aFrame, michael@0: const nsRect& aPreEffectsOverflowRect) michael@0: { michael@0: NS_ASSERTION(!(aFrame->GetStateBits() & NS_FRAME_SVG_LAYOUT), michael@0: "Don't call this on SVG child frames"); michael@0: michael@0: nsIFrame* firstFrame = michael@0: nsLayoutUtils::FirstContinuationOrIBSplitSibling(aFrame); michael@0: nsSVGEffects::EffectProperties effectProperties = michael@0: nsSVGEffects::GetEffectProperties(firstFrame); michael@0: if (!effectProperties.HasValidFilter()) { michael@0: return aPreEffectsOverflowRect; michael@0: } michael@0: michael@0: // Create an override bbox - see comment above: michael@0: nsPoint firstFrameToBoundingBox = GetOffsetToBoundingBox(firstFrame); michael@0: // overrideBBox is in "user space", in _CSS_ pixels: michael@0: // XXX Why are we rounding out to pixel boundaries? We don't do that in michael@0: // GetSVGBBoxForNonSVGFrame, and it doesn't appear to be necessary. michael@0: gfxRect overrideBBox = michael@0: nsLayoutUtils::RectToGfxRect( michael@0: GetPreEffectsVisualOverflowUnion(firstFrame, aFrame, michael@0: aPreEffectsOverflowRect, michael@0: firstFrameToBoundingBox), michael@0: aFrame->PresContext()->AppUnitsPerCSSPixel()); michael@0: overrideBBox.RoundOut(); michael@0: michael@0: nsRect overflowRect = michael@0: nsFilterInstance::GetPostFilterBounds(firstFrame, &overrideBBox); michael@0: michael@0: // Return overflowRect relative to aFrame, rather than "user space": michael@0: return overflowRect - (aFrame->GetOffsetTo(firstFrame) + firstFrameToBoundingBox); michael@0: } michael@0: michael@0: nsIntRegion michael@0: nsSVGIntegrationUtils::AdjustInvalidAreaForSVGEffects(nsIFrame* aFrame, michael@0: const nsPoint& aToReferenceFrame, michael@0: const nsIntRegion& aInvalidRegion) michael@0: { michael@0: if (aInvalidRegion.IsEmpty()) { michael@0: return nsIntRect(); michael@0: } michael@0: michael@0: // Don't bother calling GetEffectProperties; the filter property should michael@0: // already have been set up during reflow/ComputeFrameEffectsRect michael@0: nsIFrame* firstFrame = michael@0: nsLayoutUtils::FirstContinuationOrIBSplitSibling(aFrame); michael@0: nsSVGFilterProperty *prop = nsSVGEffects::GetFilterProperty(firstFrame); michael@0: if (!prop || !prop->IsInObserverLists()) { michael@0: return aInvalidRegion; michael@0: } michael@0: michael@0: int32_t appUnitsPerDevPixel = aFrame->PresContext()->AppUnitsPerDevPixel(); michael@0: michael@0: if (!prop || !prop->ReferencesValidResources()) { michael@0: // The frame is either not there or not currently available, michael@0: // perhaps because we're in the middle of tearing stuff down. michael@0: // Be conservative, return our visual overflow rect relative michael@0: // to the reference frame. michael@0: nsRect overflow = aFrame->GetVisualOverflowRect() + aToReferenceFrame; michael@0: return overflow.ToOutsidePixels(appUnitsPerDevPixel); michael@0: } michael@0: michael@0: // Convert aInvalidRegion into bounding box frame space in app units: michael@0: nsPoint toBoundingBox = michael@0: aFrame->GetOffsetTo(firstFrame) + GetOffsetToBoundingBox(firstFrame); michael@0: // The initial rect was relative to the reference frame, so we need to michael@0: // remove that offset to get a rect relative to the current frame. michael@0: toBoundingBox -= aToReferenceFrame; michael@0: nsRegion preEffectsRegion = aInvalidRegion.ToAppUnits(appUnitsPerDevPixel).MovedBy(toBoundingBox); michael@0: michael@0: // Adjust the dirty area for effects, and shift it back to being relative to michael@0: // the reference frame. michael@0: nsRegion result = nsFilterInstance::GetPostFilterDirtyArea(firstFrame, michael@0: preEffectsRegion).MovedBy(-toBoundingBox); michael@0: // Return the result, in pixels relative to the reference frame. michael@0: return result.ToOutsidePixels(appUnitsPerDevPixel); michael@0: } michael@0: michael@0: nsRect michael@0: nsSVGIntegrationUtils::GetRequiredSourceForInvalidArea(nsIFrame* aFrame, michael@0: const nsRect& aDirtyRect) michael@0: { michael@0: // Don't bother calling GetEffectProperties; the filter property should michael@0: // already have been set up during reflow/ComputeFrameEffectsRect michael@0: nsIFrame* firstFrame = michael@0: nsLayoutUtils::FirstContinuationOrIBSplitSibling(aFrame); michael@0: nsSVGFilterProperty *prop = nsSVGEffects::GetFilterProperty(firstFrame); michael@0: if (!prop || !prop->ReferencesValidResources()) { michael@0: return aDirtyRect; michael@0: } michael@0: michael@0: // Convert aDirtyRect into "user space" in app units: michael@0: nsPoint toUserSpace = michael@0: aFrame->GetOffsetTo(firstFrame) + GetOffsetToBoundingBox(firstFrame); michael@0: nsRect postEffectsRect = aDirtyRect + toUserSpace; michael@0: michael@0: // Return ther result, relative to aFrame, not in user space: michael@0: return nsFilterInstance::GetPreFilterNeededArea(firstFrame, postEffectsRect).GetBounds() michael@0: - toUserSpace; michael@0: } michael@0: michael@0: bool michael@0: nsSVGIntegrationUtils::HitTestFrameForEffects(nsIFrame* aFrame, const nsPoint& aPt) michael@0: { michael@0: nsIFrame* firstFrame = michael@0: nsLayoutUtils::FirstContinuationOrIBSplitSibling(aFrame); michael@0: // Convert aPt to user space: michael@0: nsPoint toUserSpace; michael@0: if (aFrame->GetStateBits() & NS_FRAME_SVG_LAYOUT) { michael@0: // XXXmstange Isn't this wrong for svg:use and innerSVG frames? michael@0: toUserSpace = aFrame->GetPosition(); michael@0: } else { michael@0: toUserSpace = michael@0: aFrame->GetOffsetTo(firstFrame) + GetOffsetToBoundingBox(firstFrame); michael@0: } michael@0: nsPoint pt = aPt + toUserSpace; michael@0: return nsSVGUtils::HitTestClip(firstFrame, pt); michael@0: } michael@0: michael@0: class RegularFramePaintCallback : public nsSVGFilterPaintCallback michael@0: { michael@0: public: michael@0: RegularFramePaintCallback(nsDisplayListBuilder* aBuilder, michael@0: LayerManager* aManager, michael@0: const nsPoint& aOffset) michael@0: : mBuilder(aBuilder), mLayerManager(aManager), michael@0: mOffset(aOffset) {} michael@0: michael@0: virtual void Paint(nsRenderingContext *aContext, nsIFrame *aTarget, michael@0: const nsIntRect* aDirtyRect, michael@0: nsIFrame* aTransformRoot) MOZ_OVERRIDE michael@0: { michael@0: BasicLayerManager* basic = static_cast(mLayerManager); michael@0: basic->SetTarget(aContext->ThebesContext()); michael@0: nsRenderingContext::AutoPushTranslation push(aContext, -mOffset); michael@0: mLayerManager->EndTransaction(FrameLayerBuilder::DrawThebesLayer, mBuilder); michael@0: } michael@0: michael@0: private: michael@0: nsDisplayListBuilder* mBuilder; michael@0: LayerManager* mLayerManager; michael@0: nsPoint mOffset; michael@0: }; michael@0: michael@0: void michael@0: nsSVGIntegrationUtils::PaintFramesWithEffects(nsRenderingContext* aCtx, michael@0: nsIFrame* aFrame, michael@0: const nsRect& aDirtyRect, michael@0: nsDisplayListBuilder* aBuilder, michael@0: LayerManager *aLayerManager) michael@0: { michael@0: #ifdef DEBUG michael@0: NS_ASSERTION(!(aFrame->GetStateBits() & NS_FRAME_SVG_LAYOUT) || michael@0: (NS_SVGDisplayListPaintingEnabled() && michael@0: !(aFrame->GetStateBits() & NS_FRAME_IS_NONDISPLAY)), michael@0: "Should not use nsSVGIntegrationUtils on this SVG frame"); michael@0: #endif michael@0: michael@0: /* SVG defines the following rendering model: michael@0: * michael@0: * 1. Render geometry michael@0: * 2. Apply filter michael@0: * 3. Apply clipping, masking, group opacity michael@0: * michael@0: * We follow this, but perform a couple of optimizations: michael@0: * michael@0: * + Use cairo's clipPath when representable natively (single object michael@0: * clip region). michael@0: * michael@0: * + Merge opacity and masking if both used together. michael@0: */ michael@0: michael@0: const nsIContent* content = aFrame->GetContent(); michael@0: bool hasSVGLayout = (aFrame->GetStateBits() & NS_FRAME_SVG_LAYOUT); michael@0: if (hasSVGLayout) { michael@0: nsISVGChildFrame *svgChildFrame = do_QueryFrame(aFrame); michael@0: if (!svgChildFrame || !aFrame->GetContent()->IsSVG()) { michael@0: NS_ASSERTION(false, "why?"); michael@0: return; michael@0: } michael@0: if (!static_cast(content)->HasValidDimensions()) { michael@0: return; // The SVG spec says not to draw _anything_ michael@0: } michael@0: } michael@0: michael@0: float opacity = aFrame->StyleDisplay()->mOpacity; michael@0: if (opacity == 0.0f) { michael@0: return; michael@0: } michael@0: if (opacity != 1.0f && michael@0: hasSVGLayout && nsSVGUtils::CanOptimizeOpacity(aFrame)) { michael@0: opacity = 1.0f; michael@0: } michael@0: michael@0: /* Properties are added lazily and may have been removed by a restyle, michael@0: so make sure all applicable ones are set again. */ michael@0: nsIFrame* firstFrame = michael@0: nsLayoutUtils::FirstContinuationOrIBSplitSibling(aFrame); michael@0: nsSVGEffects::EffectProperties effectProperties = michael@0: nsSVGEffects::GetEffectProperties(firstFrame); michael@0: michael@0: bool isOK = effectProperties.HasNoFilterOrHasValidFilter(); michael@0: nsSVGClipPathFrame *clipPathFrame = effectProperties.GetClipPathFrame(&isOK); michael@0: nsSVGMaskFrame *maskFrame = effectProperties.GetMaskFrame(&isOK); michael@0: if (!isOK) { michael@0: return; // Some resource is missing. We shouldn't paint anything. michael@0: } michael@0: michael@0: bool isTrivialClip = clipPathFrame ? clipPathFrame->IsTrivial() : true; michael@0: michael@0: gfxContext* gfx = aCtx->ThebesContext(); michael@0: gfxContextMatrixAutoSaveRestore matrixAutoSaveRestore(gfx); michael@0: michael@0: nsPoint firstFrameOffset = GetOffsetToBoundingBox(firstFrame); michael@0: nsPoint offsetToBoundingBox = aBuilder->ToReferenceFrame(firstFrame) - firstFrameOffset; michael@0: if (!firstFrame->IsFrameOfType(nsIFrame::eSVG)) { michael@0: /* Snap the offset if the reference frame is not a SVG frame, michael@0: * since other frames will be snapped to pixel when rendering. */ michael@0: offsetToBoundingBox = nsPoint( michael@0: aFrame->PresContext()->RoundAppUnitsToNearestDevPixels(offsetToBoundingBox.x), michael@0: aFrame->PresContext()->RoundAppUnitsToNearestDevPixels(offsetToBoundingBox.y)); michael@0: } michael@0: michael@0: // After applying only "offsetToBoundingBox", aCtx would have its origin at michael@0: // the top left corner of aFrame's bounding box (over all continuations). michael@0: // However, SVG painting needs the origin to be located at the origin of the michael@0: // SVG frame's "user space", i.e. the space in which, for example, the michael@0: // frame's BBox lives. michael@0: // SVG geometry frames and foreignObject frames apply their own offsets, so michael@0: // their position is relative to their user space. So for these frame types, michael@0: // if we want aCtx to be in user space, we first need to subtract the michael@0: // frame's position so that SVG painting can later add it again and the michael@0: // frame is painted in the right place. michael@0: michael@0: gfxPoint toUserSpaceGfx = nsSVGUtils::FrameSpaceInCSSPxToUserSpaceOffset(aFrame); michael@0: nsPoint toUserSpace(nsPresContext::CSSPixelsToAppUnits(float(toUserSpaceGfx.x)), michael@0: nsPresContext::CSSPixelsToAppUnits(float(toUserSpaceGfx.y))); michael@0: nsPoint offsetToUserSpace = offsetToBoundingBox - toUserSpace; michael@0: michael@0: NS_ASSERTION(hasSVGLayout || offsetToBoundingBox == offsetToUserSpace, michael@0: "For non-SVG frames there shouldn't be any additional offset"); michael@0: michael@0: aCtx->Translate(offsetToUserSpace); michael@0: michael@0: gfxMatrix cssPxToDevPxMatrix = GetCSSPxToDevPxMatrix(aFrame); michael@0: michael@0: bool complexEffects = false; michael@0: /* Check if we need to do additional operations on this child's michael@0: * rendering, which necessitates rendering into another surface. */ michael@0: if (opacity != 1.0f || maskFrame || (clipPathFrame && !isTrivialClip) michael@0: || aFrame->StyleDisplay()->mMixBlendMode != NS_STYLE_BLEND_NORMAL) { michael@0: complexEffects = true; michael@0: gfx->Save(); michael@0: aCtx->IntersectClip(aFrame->GetVisualOverflowRectRelativeToSelf() + michael@0: toUserSpace); michael@0: gfx->PushGroup(gfxContentType::COLOR_ALPHA); michael@0: } michael@0: michael@0: /* If this frame has only a trivial clipPath, set up cairo's clipping now so michael@0: * we can just do normal painting and get it clipped appropriately. michael@0: */ michael@0: if (clipPathFrame && isTrivialClip) { michael@0: gfx->Save(); michael@0: clipPathFrame->ClipPaint(aCtx, aFrame, cssPxToDevPxMatrix); michael@0: } michael@0: michael@0: /* Paint the child */ michael@0: if (effectProperties.HasValidFilter()) { michael@0: RegularFramePaintCallback callback(aBuilder, aLayerManager, michael@0: offsetToUserSpace); michael@0: michael@0: nsRegion dirtyRegion = aDirtyRect - offsetToBoundingBox; michael@0: nsFilterInstance::PaintFilteredFrame(aCtx, aFrame, &callback, &dirtyRegion); michael@0: } else { michael@0: gfx->SetMatrix(matrixAutoSaveRestore.Matrix()); michael@0: aLayerManager->EndTransaction(FrameLayerBuilder::DrawThebesLayer, aBuilder); michael@0: aCtx->Translate(offsetToUserSpace); michael@0: } michael@0: michael@0: if (clipPathFrame && isTrivialClip) { michael@0: gfx->Restore(); michael@0: } michael@0: michael@0: /* No more effects, we're done. */ michael@0: if (!complexEffects) { michael@0: return; michael@0: } michael@0: michael@0: gfx->PopGroupToSource(); michael@0: michael@0: nsRefPtr maskSurface = michael@0: maskFrame ? maskFrame->ComputeMaskAlpha(aCtx, aFrame, michael@0: cssPxToDevPxMatrix, opacity) : nullptr; michael@0: michael@0: nsRefPtr clipMaskSurface; michael@0: if (clipPathFrame && !isTrivialClip) { michael@0: gfx->PushGroup(gfxContentType::COLOR_ALPHA); michael@0: michael@0: nsresult rv = clipPathFrame->ClipPaint(aCtx, aFrame, cssPxToDevPxMatrix); michael@0: clipMaskSurface = gfx->PopGroup(); michael@0: michael@0: if (NS_SUCCEEDED(rv) && clipMaskSurface) { michael@0: // Still more set after clipping, so clip to another surface michael@0: if (maskSurface || opacity != 1.0f) { michael@0: gfx->PushGroup(gfxContentType::COLOR_ALPHA); michael@0: gfx->Mask(clipMaskSurface); michael@0: gfx->PopGroupToSource(); michael@0: } else { michael@0: gfx->Mask(clipMaskSurface); michael@0: } michael@0: } michael@0: } michael@0: michael@0: if (maskSurface) { michael@0: gfx->Mask(maskSurface); michael@0: } else if (opacity != 1.0f) { michael@0: gfx->Paint(opacity); michael@0: } michael@0: michael@0: gfx->Restore(); michael@0: } michael@0: michael@0: gfxMatrix michael@0: nsSVGIntegrationUtils::GetCSSPxToDevPxMatrix(nsIFrame* aNonSVGFrame) michael@0: { michael@0: int32_t appUnitsPerDevPixel = aNonSVGFrame->PresContext()->AppUnitsPerDevPixel(); michael@0: float devPxPerCSSPx = michael@0: 1 / nsPresContext::AppUnitsToFloatCSSPixels(appUnitsPerDevPixel); michael@0: michael@0: return gfxMatrix(devPxPerCSSPx, 0.0, michael@0: 0.0, devPxPerCSSPx, michael@0: 0.0, 0.0); michael@0: } michael@0: michael@0: class PaintFrameCallback : public gfxDrawingCallback { michael@0: public: michael@0: PaintFrameCallback(nsIFrame* aFrame, michael@0: const nsSize aPaintServerSize, michael@0: const gfxIntSize aRenderSize, michael@0: uint32_t aFlags) michael@0: : mFrame(aFrame) michael@0: , mPaintServerSize(aPaintServerSize) michael@0: , mRenderSize(aRenderSize) michael@0: , mFlags (aFlags) michael@0: {} michael@0: virtual bool operator()(gfxContext* aContext, michael@0: const gfxRect& aFillRect, michael@0: const GraphicsFilter& aFilter, michael@0: const gfxMatrix& aTransform) MOZ_OVERRIDE; michael@0: private: michael@0: nsIFrame* mFrame; michael@0: nsSize mPaintServerSize; michael@0: gfxIntSize mRenderSize; michael@0: uint32_t mFlags; michael@0: }; michael@0: michael@0: bool michael@0: PaintFrameCallback::operator()(gfxContext* aContext, michael@0: const gfxRect& aFillRect, michael@0: const GraphicsFilter& aFilter, michael@0: const gfxMatrix& aTransform) michael@0: { michael@0: if (mFrame->GetStateBits() & NS_FRAME_DRAWING_AS_PAINTSERVER) michael@0: return false; michael@0: michael@0: mFrame->AddStateBits(NS_FRAME_DRAWING_AS_PAINTSERVER); michael@0: michael@0: nsRefPtr context(new nsRenderingContext()); michael@0: context->Init(mFrame->PresContext()->DeviceContext(), aContext); michael@0: aContext->Save(); michael@0: michael@0: // Clip to aFillRect so that we don't paint outside. michael@0: aContext->NewPath(); michael@0: aContext->Rectangle(aFillRect); michael@0: aContext->Clip(); michael@0: michael@0: aContext->Multiply(gfxMatrix(aTransform).Invert()); michael@0: michael@0: // nsLayoutUtils::PaintFrame will anchor its painting at mFrame. But we want michael@0: // to have it anchored at the top left corner of the bounding box of all of michael@0: // mFrame's continuations. So we add a translation transform. michael@0: int32_t appUnitsPerDevPixel = mFrame->PresContext()->AppUnitsPerDevPixel(); michael@0: nsPoint offset = GetOffsetToBoundingBox(mFrame); michael@0: gfxPoint devPxOffset = gfxPoint(offset.x, offset.y) / appUnitsPerDevPixel; michael@0: aContext->Multiply(gfxMatrix().Translate(devPxOffset)); michael@0: michael@0: gfxSize paintServerSize = michael@0: gfxSize(mPaintServerSize.width, mPaintServerSize.height) / michael@0: mFrame->PresContext()->AppUnitsPerDevPixel(); michael@0: michael@0: // nsLayoutUtils::PaintFrame wants to render with paintServerSize, but we michael@0: // want it to render with mRenderSize, so we need to set up a scale transform. michael@0: gfxFloat scaleX = mRenderSize.width / paintServerSize.width; michael@0: gfxFloat scaleY = mRenderSize.height / paintServerSize.height; michael@0: gfxMatrix scaleMatrix = gfxMatrix().Scale(scaleX, scaleY); michael@0: aContext->Multiply(scaleMatrix); michael@0: michael@0: // Draw. michael@0: nsRect dirty(-offset.x, -offset.y, michael@0: mPaintServerSize.width, mPaintServerSize.height); michael@0: michael@0: uint32_t flags = nsLayoutUtils::PAINT_IN_TRANSFORM | michael@0: nsLayoutUtils::PAINT_ALL_CONTINUATIONS; michael@0: if (mFlags & nsSVGIntegrationUtils::FLAG_SYNC_DECODE_IMAGES) { michael@0: flags |= nsLayoutUtils::PAINT_SYNC_DECODE_IMAGES; michael@0: } michael@0: nsLayoutUtils::PaintFrame(context, mFrame, michael@0: dirty, NS_RGBA(0, 0, 0, 0), michael@0: flags); michael@0: michael@0: aContext->Restore(); michael@0: michael@0: mFrame->RemoveStateBits(NS_FRAME_DRAWING_AS_PAINTSERVER); michael@0: michael@0: return true; michael@0: } michael@0: michael@0: /* static */ already_AddRefed michael@0: nsSVGIntegrationUtils::DrawableFromPaintServer(nsIFrame* aFrame, michael@0: nsIFrame* aTarget, michael@0: const nsSize& aPaintServerSize, michael@0: const gfxIntSize& aRenderSize, michael@0: const gfxMatrix& aContextMatrix, michael@0: uint32_t aFlags) michael@0: { michael@0: // aPaintServerSize is the size that would be filled when using michael@0: // background-repeat:no-repeat and background-size:auto. For normal background michael@0: // images, this would be the intrinsic size of the image; for gradients and michael@0: // patterns this would be the whole target frame fill area. michael@0: // aRenderSize is what we will be actually filling after accounting for michael@0: // background-size. michael@0: if (aFrame->IsFrameOfType(nsIFrame::eSVGPaintServer)) { michael@0: // aFrame is either a pattern or a gradient. These fill the whole target michael@0: // frame by default, so aPaintServerSize is the whole target background fill michael@0: // area. michael@0: nsSVGPaintServerFrame* server = michael@0: static_cast(aFrame); michael@0: michael@0: gfxRect overrideBounds(0, 0, michael@0: aPaintServerSize.width, aPaintServerSize.height); michael@0: overrideBounds.ScaleInverse(aFrame->PresContext()->AppUnitsPerDevPixel()); michael@0: nsRefPtr pattern = michael@0: server->GetPaintServerPattern(aTarget, aContextMatrix, michael@0: &nsStyleSVG::mFill, 1.0, &overrideBounds); michael@0: michael@0: if (!pattern) michael@0: return nullptr; michael@0: michael@0: // pattern is now set up to fill aPaintServerSize. But we want it to michael@0: // fill aRenderSize, so we need to add a scaling transform. michael@0: // We couldn't just have set overrideBounds to aRenderSize - it would have michael@0: // worked for gradients, but for patterns it would result in a different michael@0: // pattern size. michael@0: gfxFloat scaleX = overrideBounds.Width() / aRenderSize.width; michael@0: gfxFloat scaleY = overrideBounds.Height() / aRenderSize.height; michael@0: gfxMatrix scaleMatrix = gfxMatrix().Scale(scaleX, scaleY); michael@0: pattern->SetMatrix(scaleMatrix.Multiply(pattern->GetMatrix())); michael@0: nsRefPtr drawable = michael@0: new gfxPatternDrawable(pattern, aRenderSize); michael@0: return drawable.forget(); michael@0: } michael@0: michael@0: // We don't want to paint into a surface as long as we don't need to, so we michael@0: // set up a drawing callback. michael@0: nsRefPtr cb = michael@0: new PaintFrameCallback(aFrame, aPaintServerSize, aRenderSize, aFlags); michael@0: nsRefPtr drawable = new gfxCallbackDrawable(cb, aRenderSize); michael@0: return drawable.forget(); michael@0: }