From d27090aa599aa5da28425b95bf560086f25a39f0 Mon Sep 17 00:00:00 2001 From: Michael Ventnor Date: Mon, 7 Jul 2008 19:57:47 -0500 Subject: [PATCH] Bug 212633 - "Add support for CSS3 box-shadow" (rendering support) [p=ventnor.bugzilla@gmail.com (Michael Ventnor) r+sr=roc] --- layout/base/nsCSSRendering.cpp | 234 +++++++++++++++++++-------------- layout/base/nsCSSRendering.h | 14 +- layout/base/nsDisplayList.cpp | 13 ++ layout/base/nsDisplayList.h | 20 +++ layout/generic/nsFrame.cpp | 61 +++++++-- layout/generic/nsIFrame.h | 6 + 6 files changed, 233 insertions(+), 115 deletions(-) diff --git a/layout/base/nsCSSRendering.cpp b/layout/base/nsCSSRendering.cpp index 4f16dfdc206..cd4cfb132c7 100644 --- a/layout/base/nsCSSRendering.cpp +++ b/layout/base/nsCSSRendering.cpp @@ -2679,8 +2679,7 @@ nsCSSRendering::PaintBorder(nsPresContext* aPresContext, PRBool aShouldIgnoreRounded) { nsMargin border; - nsStyleCoord bordStyleRadius[4]; - PRInt32 twipsRadii[4]; + nscoord twipsRadii[4]; float percent; nsCompatibility compatMode = aPresContext->CompatibilityMode(); @@ -2716,30 +2715,7 @@ nsCSSRendering::PaintBorder(nsPresContext* aPresContext, return; } - // get the radius for our border - bordStyleRadius[0] = aBorderStyle.mBorderRadius.GetTop(); //topleft - bordStyleRadius[1] = aBorderStyle.mBorderRadius.GetRight(); //topright - bordStyleRadius[2] = aBorderStyle.mBorderRadius.GetBottom(); //bottomright - bordStyleRadius[3] = aBorderStyle.mBorderRadius.GetLeft(); //bottomleft - - // convert percentage values - for(int i = 0; i < 4; i++) { - twipsRadii[i] = 0; - - switch (bordStyleRadius[i].GetUnit()) { - case eStyleUnit_Percent: - percent = bordStyleRadius[i].GetPercentValue(); - twipsRadii[i] = (nscoord)(percent * aForFrame->GetSize().width); - break; - - case eStyleUnit_Coord: - twipsRadii[i] = bordStyleRadius[i].GetCoordValue(); - break; - - default: - break; - } - } + GetBorderRadiusTwips(aBorderStyle.mBorderRadius, aForFrame->GetSize().width, twipsRadii); // Turn off rendering for all of the zero sized sides if (aSkipSides & SIDE_BIT_TOP) border.top = 0; @@ -2854,8 +2830,7 @@ nsCSSRendering::PaintOutline(nsPresContext* aPresContext, nsStyleContext* aStyleContext, nsRect* aGap) { - nsStyleCoord bordStyleRadius[4]; - PRInt32 twipsRadii[4]; + nscoord twipsRadii[4]; // Get our style context's color struct. const nsStyleColor* ourColor = aStyleContext->GetStyleColor(); @@ -2872,31 +2847,7 @@ nsCSSRendering::PaintOutline(nsPresContext* aPresContext, (aStyleContext, PR_FALSE); // get the radius for our outline - bordStyleRadius[0] = aOutlineStyle.mOutlineRadius.GetTop(); //topleft - bordStyleRadius[1] = aOutlineStyle.mOutlineRadius.GetRight(); //topright - bordStyleRadius[2] = aOutlineStyle.mOutlineRadius.GetBottom(); //bottomright - bordStyleRadius[3] = aOutlineStyle.mOutlineRadius.GetLeft(); //bottomleft - - // convert percentage values - for (int i = 0; i < 4; i++) { - twipsRadii[i] = 0; - - switch (bordStyleRadius[i].GetUnit()) { - case eStyleUnit_Percent: - { - float percent = bordStyleRadius[i].GetPercentValue(); - twipsRadii[i] = (nscoord)(percent * aBorderArea.width); - } - break; - - case eStyleUnit_Coord: - twipsRadii[i] = bordStyleRadius[i].GetCoordValue(); - break; - - default: - break; - } - } + GetBorderRadiusTwips(aOutlineStyle.mOutlineRadius, aBorderArea.width, twipsRadii); nscoord offset; aOutlineStyle.GetOutlineOffset(offset); @@ -3338,6 +3289,133 @@ nsCSSRendering::DidPaint() gInlineBGData->Reset(); } +/* static */ PRBool +nsCSSRendering::GetBorderRadiusTwips(const nsStyleSides& aBorderRadius, + const nscoord& aFrameWidth, + nscoord aTwipsRadii[4]) +{ + nsStyleCoord bordStyleRadius[4]; + PRBool result = PR_FALSE; + + bordStyleRadius[0] = aBorderRadius.GetTop(); //topleft + bordStyleRadius[1] = aBorderRadius.GetRight(); //topright + bordStyleRadius[2] = aBorderRadius.GetBottom(); //bottomright + bordStyleRadius[3] = aBorderRadius.GetLeft(); //bottomleft + + // Convert percentage values + for (int i = 0; i < 4; i++) { + aTwipsRadii[i] = 0; + float percent; + + switch (bordStyleRadius[i].GetUnit()) { + case eStyleUnit_Percent: + percent = bordStyleRadius[i].GetPercentValue(); + aTwipsRadii[i] = (nscoord)(percent * aFrameWidth); + break; + + case eStyleUnit_Coord: + aTwipsRadii[i] = bordStyleRadius[i].GetCoordValue(); + break; + + default: + break; + } + + if (aTwipsRadii[i]) + result = PR_TRUE; + } + return result; +} + +void +nsCSSRendering::PaintBoxShadow(nsPresContext* aPresContext, + nsIRenderingContext& aRenderingContext, + nsIFrame* aForFrame, + const nsPoint& aForFramePt) +{ + nsMargin borderValues; + gfxFloat borderRadii[4]; + PRIntn sidesToSkip; + nsRect frameRect; + + const nsStyleBorder* styleBorder = aForFrame->GetStyleBorder(); + borderValues = styleBorder->GetBorder(); + sidesToSkip = aForFrame->GetSkipSides(); + frameRect = nsRect(aForFramePt, aForFrame->GetSize()); + + // Get any border radius, since box-shadow must also have rounded corners if the frame does + nscoord twipsRadii[4]; + PRBool hasBorderRadius = GetBorderRadiusTwips(styleBorder->mBorderRadius, frameRect.width, twipsRadii); + nscoord twipsPerPixel = aPresContext->DevPixelsToAppUnits(1); + ComputePixelRadii(twipsRadii, frameRect, borderValues, sidesToSkip, twipsPerPixel, borderRadii); + + gfxRect frameGfxRect = RectToGfxRect(frameRect, twipsPerPixel); + for (PRUint32 i = styleBorder->mBoxShadow->Length(); i > 0; --i) { + nsCSSShadowItem* shadowItem = styleBorder->mBoxShadow->ShadowAt(i - 1); + gfxRect shadowRect(frameRect.x, frameRect.y, frameRect.width, frameRect.height); + shadowRect.MoveBy(gfxPoint(shadowItem->mXOffset.GetCoordValue(), + shadowItem->mYOffset.GetCoordValue())); + shadowRect.Outset(shadowItem->mSpread.GetCoordValue()); + + gfxRect shadowRectPlusBlur = shadowRect; + shadowRect.ScaleInverse(twipsPerPixel); + shadowRect.RoundOut(); + + // shadowRect won't include the blur, so make an extra rect here that includes the blur + // for use in the even-odd rule below. + nscoord blurRadius = shadowItem->mRadius.GetCoordValue(); + shadowRectPlusBlur.Outset(blurRadius); + shadowRectPlusBlur.ScaleInverse(twipsPerPixel); + shadowRectPlusBlur.RoundOut(); + + gfxContext* renderContext = aRenderingContext.ThebesContext(); + nsRefPtr shadowContext; + nsContextBoxBlur blurringArea; + + // shadowRect has already been converted to device pixels, pass 1 as the appunits/pixel value + blurRadius /= twipsPerPixel; + shadowContext = blurringArea.Init(shadowRect, blurRadius, 1, renderContext); + if (!shadowContext) + return; + + // Set the shadow color; if not specified, use the foreground color + nscolor shadowColor; + if (shadowItem->mHasColor) + shadowColor = shadowItem->mColor; + else + shadowColor = aForFrame->GetStyleColor()->mColor; + + renderContext->Save(); + renderContext->SetColor(gfxRGBA(shadowColor)); + + // Clip out the area of the actual frame so the shadow is not shown within + // the frame + renderContext->NewPath(); + renderContext->Rectangle(shadowRectPlusBlur); + if (hasBorderRadius) + DoRoundedRectCWSubPath(renderContext, frameGfxRect, borderRadii); + else + renderContext->Rectangle(frameGfxRect); + renderContext->SetFillRule(gfxContext::FILL_RULE_EVEN_ODD); + renderContext->Clip(); + + // Draw the shape of the frame so it can be blurred. Recall how nsContextBoxBlur + // doesn't make any temporary surfaces if blur is 0 and it just returns the original + // surface? If we have no blur, we're painting this fill on the actual content surface + // (renderContext == shadowContext) which is why we set up the color and clip + // before doing this. + shadowContext->NewPath(); + if (hasBorderRadius) + DoRoundedRectCWSubPath(shadowContext, shadowRect, borderRadii); + else + shadowContext->Rectangle(shadowRect); + shadowContext->Fill(); + + blurringArea.DoPaint(); + renderContext->Restore(); + } +} + void nsCSSRendering::PaintBackground(nsPresContext* aPresContext, nsIRenderingContext& aRenderingContext, @@ -3803,34 +3881,8 @@ nsCSSRendering::PaintBackgroundWithSC(nsPresContext* aPresContext, ctx->Rectangle(RectToGfxRect(dirtyRect, appUnitsPerPixel), PR_TRUE); ctx->Clip(); - nsStyleCoord bordStyleRadius[4]; nscoord borderRadii[4]; - - // get the radius for our border - bordStyleRadius[NS_SIDE_TOP] = aBorder.mBorderRadius.GetTop(); // topleft - bordStyleRadius[NS_SIDE_RIGHT] = aBorder.mBorderRadius.GetRight(); // topright - bordStyleRadius[NS_SIDE_BOTTOM] = aBorder.mBorderRadius.GetBottom(); // bottomright - bordStyleRadius[NS_SIDE_LEFT] = aBorder.mBorderRadius.GetLeft(); // bottomleft - - PRBool haveRadius = PR_FALSE; - PRUint8 side = 0; - for (; side < 4; ++side) { - borderRadii[side] = 0; - switch (bordStyleRadius[side].GetUnit()) { - case eStyleUnit_Percent: - borderRadii[side] = nscoord(bordStyleRadius[side].GetPercentValue() * - aForFrame->GetSize().width); - break; - case eStyleUnit_Coord: - borderRadii[side] = bordStyleRadius[side].GetCoordValue(); - break; - default: - break; - } - - if (borderRadii[side] != 0) - haveRadius = PR_TRUE; - } + PRBool haveRadius = GetBorderRadiusTwips(aBorder.mBorderRadius, aForFrame->GetSize().width, borderRadii); if (haveRadius) { gfxFloat radii[4]; @@ -4029,32 +4081,12 @@ nsCSSRendering::PaintBackgroundColor(nsPresContext* aPresContext, return; } - nsStyleCoord bordStyleRadius[4]; nscoord borderRadii[4]; nsRect bgClipArea(aBgClipArea); - // get the radius for our border - bordStyleRadius[NS_SIDE_TOP] = aBorder.mBorderRadius.GetTop(); // topleft - bordStyleRadius[NS_SIDE_RIGHT] = aBorder.mBorderRadius.GetRight(); // topright - bordStyleRadius[NS_SIDE_BOTTOM] = aBorder.mBorderRadius.GetBottom(); // bottomright - bordStyleRadius[NS_SIDE_LEFT] = aBorder.mBorderRadius.GetLeft(); // bottomleft + GetBorderRadiusTwips(aBorder.mBorderRadius, aForFrame->GetSize().width, borderRadii); PRUint8 side = 0; - for (; side < 4; ++side) { - borderRadii[side] = 0; - switch (bordStyleRadius[side].GetUnit()) { - case eStyleUnit_Percent: - borderRadii[side] = nscoord(bordStyleRadius[side].GetPercentValue() * - aForFrame->GetSize().width); - break; - case eStyleUnit_Coord: - borderRadii[side] = bordStyleRadius[side].GetCoordValue(); - break; - default: - break; - } - } - // Rounded version of the border for (side = 0; side < 4; ++side) { if (borderRadii[side] > 0) { diff --git a/layout/base/nsCSSRendering.h b/layout/base/nsCSSRendering.h index 761e45073ad..cad7450ecc2 100644 --- a/layout/base/nsCSSRendering.h +++ b/layout/base/nsCSSRendering.h @@ -60,6 +60,11 @@ public: */ static void Shutdown(); + static void PaintBoxShadow(nsPresContext* aPresContext, + nsIRenderingContext& aRenderingContext, + nsIFrame* aForFrame, + const nsPoint& aForFramePt); + /** * Render the border for an element using css rendering rules * for borders. aSkipSides is a bitmask of the sides to skip @@ -305,6 +310,11 @@ protected: const gfxFloat aOffset, const PRUint8 aDecoration, const PRUint8 aStyle); + + /* Returns FALSE iff all returned aTwipsRadii == 0, TRUE otherwise */ + static PRBool GetBorderRadiusTwips(const nsStyleSides& aBorderRadius, + const nscoord& aFrameWidth, + PRInt32 aTwipsRadii[4]); }; /* @@ -349,7 +359,9 @@ public: * at aRect and you don't need to worry about translating any coordinates to draw * on this temporary surface. * - * If aBlurRadius is 0, the returned context is aDestinationCtx, because no blurring is required. + * If aBlurRadius is 0, the returned context is aDestinationCtx and DoPaint() does nothing, + * because no blurring is required. Therefore, you should prepare the destination context as + * if you were going to draw directly on it instead of any temporary surface created in this class. */ gfxContext* Init(const gfxRect& aRect, nscoord aBlurRadius, PRInt32 aAppUnitsPerDevPixel, gfxContext* aDestinationCtx); diff --git a/layout/base/nsDisplayList.cpp b/layout/base/nsDisplayList.cpp index beb506ac8b3..90e836a5b17 100644 --- a/layout/base/nsDisplayList.cpp +++ b/layout/base/nsDisplayList.cpp @@ -629,6 +629,19 @@ nsDisplayBorder::Paint(nsDisplayListBuilder* aBuilder, mFrame->GetStyleContext(), mFrame->GetSkipSides()); } +void +nsDisplayBoxShadow::Paint(nsDisplayListBuilder* aBuilder, + nsIRenderingContext* aCtx, const nsRect& aDirtyRect) { + nsPoint offset = aBuilder->ToReferenceFrame(mFrame); + nsCSSRendering::PaintBoxShadow(mFrame->PresContext(), *aCtx, + mFrame, offset); +} + +nsRect +nsDisplayBoxShadow::GetBounds(nsDisplayListBuilder* aBuilder) { + return mFrame->GetOverflowRect() + aBuilder->ToReferenceFrame(mFrame); +} + nsDisplayWrapList::nsDisplayWrapList(nsIFrame* aFrame, nsDisplayList* aList) : nsDisplayItem(aFrame) { mList.AppendToTop(aList); diff --git a/layout/base/nsDisplayList.h b/layout/base/nsDisplayList.h index fb5bf8b0f04..20ff25282c0 100644 --- a/layout/base/nsDisplayList.h +++ b/layout/base/nsDisplayList.h @@ -1025,6 +1025,26 @@ private: PRPackedBool mIsThemed; }; +/** + * The standard display item to paint the CSS box-shadow of a frame. + */ +class nsDisplayBoxShadow : public nsDisplayItem { +public: + nsDisplayBoxShadow(nsIFrame* aFrame) : nsDisplayItem(aFrame) { + MOZ_COUNT_CTOR(nsDisplayBoxShadow); + } +#ifdef NS_BUILD_REFCNT_LOGGING + virtual ~nsDisplayBoxShadow() { + MOZ_COUNT_DTOR(nsDisplayBoxShadow); + } +#endif + + virtual void Paint(nsDisplayListBuilder* aBuilder, nsIRenderingContext* aCtx, + const nsRect& aDirtyRect); + virtual nsRect GetBounds(nsDisplayListBuilder* aBuilder); + NS_DISPLAY_DECL_NAME("BoxShadow") +}; + /** * The standard display item to paint the CSS outline of a frame. */ diff --git a/layout/generic/nsFrame.cpp b/layout/generic/nsFrame.cpp index 00eb0739f72..f89130296ed 100644 --- a/layout/generic/nsFrame.cpp +++ b/layout/generic/nsFrame.cpp @@ -914,6 +914,12 @@ nsFrame::DisplayBorderBackgroundOutline(nsDisplayListBuilder* aBuilder, if (!IsVisibleForPainting(aBuilder)) return NS_OK; + if (GetStyleBorder()->mBoxShadow) { + nsresult rv = aLists.BorderBackground()->AppendNewToTop(new (aBuilder) + nsDisplayBoxShadow(this)); + NS_ENSURE_SUCCESS(rv, rv); + } + // Here we don't try to detect background propagation. Frames that might // receive a propagated background should just set aForceBackground to // PR_TRUE. @@ -5382,14 +5388,51 @@ IsInlineFrame(nsIFrame *aFrame) type == nsGkAtoms::positionedInlineFrame; } +nsRect +nsIFrame::GetAdditionalOverflow(const nsRect& aOverflowArea, + const nsSize& aNewSize) +{ + nsRect overflowRect; + + // outline + PRBool hasOutline; + overflowRect = ComputeOutlineRect(this, &hasOutline, aOverflowArea); + + // box-shadow + nsCSSShadowArray* boxShadows = GetStyleBorder()->mBoxShadow; + if (boxShadows) { + for (PRUint32 i = 0; i < boxShadows->Length(); ++i) { + nsRect tmpRect(nsPoint(0, 0), aNewSize); + nsCSSShadowItem* shadow = boxShadows->ShadowAt(i); + nscoord xOffset = shadow->mXOffset.GetCoordValue(); + nscoord yOffset = shadow->mYOffset.GetCoordValue(); + nscoord outsetRadius = shadow->mRadius.GetCoordValue() + + shadow->mSpread.GetCoordValue(); + + tmpRect.MoveBy(nsPoint(xOffset, yOffset)); + tmpRect.Inflate(outsetRadius, outsetRadius); + + overflowRect.UnionRect(overflowRect, tmpRect); + } + } + + // Absolute position clipping + PRBool hasAbsPosClip; + nsRect absPosClipRect; + hasAbsPosClip = GetAbsPosClipRect(GetStyleDisplay(), &absPosClipRect, aNewSize); + if (hasAbsPosClip) { + overflowRect.IntersectRect(overflowRect, absPosClipRect); + } + + return overflowRect; +} + void nsIFrame::FinishAndStoreOverflow(nsRect* aOverflowArea, nsSize aNewSize) { // This is now called FinishAndStoreOverflow() instead of // StoreOverflow() because frame-generic ways of adding overflow // can happen here, e.g. CSS2 outline and native theme. - // If we find more things other than outline that need to be added, - // we should think about starting a new method like GetAdditionalOverflow() NS_ASSERTION(aNewSize.width == 0 || aNewSize.height == 0 || aOverflowArea->Contains(nsRect(nsPoint(0, 0), aNewSize)), "Computed overflow area must contain frame bounds"); @@ -5426,21 +5469,13 @@ nsIFrame::FinishAndStoreOverflow(nsRect* aOverflowArea, nsSize aNewSize) geometricOverflow = PR_FALSE; } - PRBool hasOutline; - nsRect outlineRect(ComputeOutlineRect(this, &hasOutline, *aOverflowArea)); + nsRect overflowRect = GetAdditionalOverflow(*aOverflowArea, aNewSize); - PRBool hasAbsPosClip; - nsRect absPosClipRect; - hasAbsPosClip = GetAbsPosClipRect(disp, &absPosClipRect, aNewSize); - if (hasAbsPosClip) { - outlineRect.IntersectRect(outlineRect, absPosClipRect); - } - - if (outlineRect != nsRect(nsPoint(0, 0), aNewSize)) { + if (overflowRect != nsRect(nsPoint(0, 0), aNewSize)) { mState |= NS_FRAME_OUTSIDE_CHILDREN; nsRect* overflowArea = GetOverflowAreaProperty(PR_TRUE); NS_ASSERTION(overflowArea, "should have created rect"); - *aOverflowArea = *overflowArea = outlineRect; + *aOverflowArea = *overflowArea = overflowRect; } else { if (mState & NS_FRAME_OUTSIDE_CHILDREN) { diff --git a/layout/generic/nsIFrame.h b/layout/generic/nsIFrame.h index 2ee057c4c63..6750fc73bb9 100644 --- a/layout/generic/nsIFrame.h +++ b/layout/generic/nsIFrame.h @@ -2150,6 +2150,12 @@ protected: nscoord aOffsetX, nscoord aOffsetY, PRBool aImmediate); + /** + * Gets the overflow area for any properties that are common to all types of frames + * e.g. outlines. + */ + nsRect GetAdditionalOverflow(const nsRect& aOverflowArea, const nsSize& aNewSize); + /** * Can we stop inside this frame when we're skipping non-rendered whitespace? * @param aForward [in] Are we moving forward (or backward) in content order.