From 7e868a56e1123a20d4379b79f23c8c3ab2b2656b Mon Sep 17 00:00:00 2001 From: Michael Ventnor Date: Fri, 13 Jun 2008 10:02:32 +1200 Subject: [PATCH] Implement text-shadow rendering (bug 10713). r+sr=roc. Relanding with fixes to make tests pass on Mac --- layout/base/nsCSSRendering.cpp | 153 +++++++++++++++++ layout/base/nsCSSRendering.h | 81 +++++++++ layout/base/nsLayoutUtils.cpp | 24 +++ layout/base/nsLayoutUtils.h | 8 + layout/generic/nsBlockFrame.cpp | 15 +- layout/generic/nsBlockFrame.h | 2 +- layout/generic/nsHTMLContainerFrame.cpp | 129 ++++++++++++++- layout/generic/nsHTMLContainerFrame.h | 5 +- layout/generic/nsLineLayout.cpp | 8 + layout/generic/nsTextFrame.h | 24 ++- layout/generic/nsTextFrameThebes.cpp | 156 ++++++++++++++---- .../base/src/nsMathMLContainerFrame.cpp | 6 + layout/reftests/reftest.list | 2 +- .../decorations-multiple-zorder-ref.html | 24 ++- 14 files changed, 587 insertions(+), 50 deletions(-) diff --git a/layout/base/nsCSSRendering.cpp b/layout/base/nsCSSRendering.cpp index 43ca9875bbc..4f16dfdc206 100644 --- a/layout/base/nsCSSRendering.cpp +++ b/layout/base/nsCSSRendering.cpp @@ -25,6 +25,7 @@ * Takeshi Ichimaru * Masayuki Nakano * L. David Baron , Mozilla Corporation + * Michael Ventnor * * Alternatively, the contents of this file may be used under the terms of * either of the GNU General Public License Version 2 or later (the "GPL"), @@ -4707,3 +4708,155 @@ nsCSSRendering::GetTextDecorationRectInternal(const gfxPoint& aPt, r.pos.y = baseline - NS_floor(offset + 0.5); return r; } + +// ----- +// nsContextBoxBlur +// ----- +void +nsContextBoxBlur::BoxBlurHorizontal(unsigned char* aInput, + unsigned char* aOutput, + PRUint32 aLeftLobe, + PRUint32 aRightLobe) +{ + // Box blur involves looking at one pixel, and setting its value to the average of + // its neighbouring pixels. leftLobe is how many pixels to the left to include + // in the average, rightLobe is to the right. + // boxSize is how many pixels total will be averaged when looking at each pixel. + PRUint32 boxSize = aLeftLobe + aRightLobe + 1; + + long stride = mImageSurface->Stride(); + PRUint32 rows = mRect.Height(); + + for (PRUint32 y = 0; y < rows; y++) { + PRUint32 alphaSum = 0; + for (PRUint32 i = 0; i < boxSize; i++) { + PRInt32 pos = i - aLeftLobe; + pos = PR_MAX(pos, 0); + pos = PR_MIN(pos, stride - 1); + alphaSum += aInput[stride * y + pos]; + } + for (PRInt32 x = 0; x < stride; x++) { + PRInt32 tmp = x - aLeftLobe; + PRInt32 last = PR_MAX(tmp, 0); + PRInt32 next = PR_MIN(tmp + boxSize, stride - 1); + + aOutput[stride * y + x] = alphaSum/boxSize; + + alphaSum += aInput[stride * y + next] - + aInput[stride * y + last]; + } + } +} + +void +nsContextBoxBlur::BoxBlurVertical(unsigned char* aInput, + unsigned char* aOutput, + PRUint32 aTopLobe, + PRUint32 aBottomLobe) +{ + PRUint32 boxSize = aTopLobe + aBottomLobe + 1; + + long stride = mImageSurface->Stride(); + PRUint32 rows = mRect.Height(); + + for (PRInt32 x = 0; x < stride; x++) { + PRUint32 alphaSum = 0; + for (PRUint32 i = 0; i < boxSize; i++) { + PRInt32 pos = i - aTopLobe; + pos = PR_MAX(pos, 0); + pos = PR_MIN(pos, stride - 1); + alphaSum += aInput[stride * pos + x]; + } + for (PRUint32 y = 0; y < rows; y++) { + PRInt32 tmp = y - aTopLobe; + PRInt32 last = PR_MAX(tmp, 0); + PRInt32 next = PR_MIN(tmp + boxSize, rows - 1); + + aOutput[stride * y + x] = alphaSum/boxSize; + + alphaSum += aInput[stride * next + x] - + aInput[stride * last + x]; + } + } +} + +gfxContext* +nsContextBoxBlur::Init(const gfxRect& aRect, nscoord aBlurRadius, + PRInt32 aAppUnitsPerDevPixel, + gfxContext* aDestinationCtx) +{ + mBlurRadius = aBlurRadius / aAppUnitsPerDevPixel; + + if (mBlurRadius <= 0) { + mContext = aDestinationCtx; + return mContext; + } + + mDestinationCtx = aDestinationCtx; + + // Convert from app units to device pixels + mRect = aRect; + mRect.Outset(aBlurRadius); + mRect.ScaleInverse(aAppUnitsPerDevPixel); + mRect.RoundOut(); + + // Make an alpha-only surface to draw on. We will play with the data after everything is drawn + // to create a blur effect. + mImageSurface = new gfxImageSurface(gfxIntSize(mRect.Width(), mRect.Height()), + gfxASurface::ImageFormatA8); + if (!mImageSurface) + return nsnull; + + // Use a device offset so callers don't need to worry about translating coordinates, + // they can draw as if this was part of the destination context at the coordinates + // of mRect. + mImageSurface->SetDeviceOffset(-mRect.TopLeft()); + + mContext = new gfxContext(mImageSurface); + return mContext; +} + +void +nsContextBoxBlur::DoPaint() +{ + if (mBlurRadius <= 0) + return; + + unsigned char* boxData = mImageSurface->Data(); + + // A blur radius of 1 achieves nothing (1/2 = 0 in int terms), + // but we still want a blur! + mBlurRadius = PR_MAX(mBlurRadius, 2); + + nsTArray tempAlphaDataBuf; + if (!tempAlphaDataBuf.SetLength(mImageSurface->GetDataSize())) + return; // OOM + + // Here we do like what the SVG gaussian blur filter does in calculating + // the lobes. + if (mBlurRadius & 1) { + // blur radius is odd + BoxBlurHorizontal(boxData, tempAlphaDataBuf.Elements(), mBlurRadius/2, mBlurRadius/2); + BoxBlurHorizontal(tempAlphaDataBuf.Elements(), boxData, mBlurRadius/2, mBlurRadius/2); + BoxBlurHorizontal(boxData, tempAlphaDataBuf.Elements(), mBlurRadius/2, mBlurRadius/2); + BoxBlurVertical(tempAlphaDataBuf.Elements(), boxData, mBlurRadius/2, mBlurRadius/2); + BoxBlurVertical(boxData, tempAlphaDataBuf.Elements(), mBlurRadius/2, mBlurRadius/2); + BoxBlurVertical(tempAlphaDataBuf.Elements(), boxData, mBlurRadius/2, mBlurRadius/2); + } else { + // blur radius is even + BoxBlurHorizontal(boxData, tempAlphaDataBuf.Elements(), mBlurRadius/2, mBlurRadius/2 - 1); + BoxBlurHorizontal(tempAlphaDataBuf.Elements(), boxData, mBlurRadius/2 - 1, mBlurRadius/2); + BoxBlurHorizontal(boxData, tempAlphaDataBuf.Elements(), mBlurRadius/2, mBlurRadius/2); + BoxBlurVertical(tempAlphaDataBuf.Elements(), boxData, mBlurRadius/2, mBlurRadius/2 - 1); + BoxBlurVertical(boxData, tempAlphaDataBuf.Elements(), mBlurRadius/2 - 1, mBlurRadius/2); + BoxBlurVertical(tempAlphaDataBuf.Elements(), boxData, mBlurRadius/2, mBlurRadius/2); + } + + mDestinationCtx->Mask(mImageSurface); +} + +gfxContext* +nsContextBoxBlur::GetContext() +{ + return mContext; +} diff --git a/layout/base/nsCSSRendering.h b/layout/base/nsCSSRendering.h index 7acaf63ca84..761e45073ad 100644 --- a/layout/base/nsCSSRendering.h +++ b/layout/base/nsCSSRendering.h @@ -43,6 +43,7 @@ #include "nsIRenderingContext.h" #include "nsStyleConsts.h" #include "gfxContext.h" +#include "gfxImageSurface.h" struct nsPoint; class nsStyleContext; class nsPresContext; @@ -306,5 +307,85 @@ protected: const PRUint8 aStyle); }; +/* + * nsContextBoxBlur + * Creates an 8-bit alpha channel context for callers to draw in, blurs the + * contents of that context and applies it as a 1-color mask on a + * different existing context. + * + * You must call Init() first to create a suitable temporary surface to draw on. + * You must then draw any desired content onto the given context, then call DoPaint() + * to apply the blurred content as a single-color mask. You can only call Init() once, + * so objects cannot be reused. + * + * This is very useful for creating drop shadows or silhouettes. + */ +class nsContextBoxBlur { +public: + /** + * Prepares a gfxContext to draw on. Do not call this twice; if you want to + * get the gfxContext again use GetContext(). + * + * @param aRect The coordinates of the surface to create. + * All coordinates must be in app units. + * This must not include the blur radius, pass it as the + * second parameter and everything is taken care of. + * + * @param aBlurRadius The blur radius in app units. + * + * @param aAppUnitsPerDevPixel The number of app units in a device pixel, for conversion. + * Most of the time you'll pass this from the current + * PresContext if available. + * + * @param aDestinationCtx The graphics context to apply the blurred mask to + * when you call DoPaint(). Make sure it is not destroyed + * before you call DoPaint(). To set the color of the resulting + * blurred graphic mask, you must set the color on this + * context before calling Init(). + * + * @return A blank 8-bit alpha-channel-only graphics context to draw on, or null on + * error. Must not be freed. The context has a device offset applied to it given + * by aRect. This means you can use coordinates as if it were at the desired position + * 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. + */ + gfxContext* Init(const gfxRect& aRect, nscoord aBlurRadius, PRInt32 aAppUnitsPerDevPixel, + gfxContext* aDestinationCtx); + + /** + * Does the actual blurring and mask applying. Users of this object *must* + * have called Init() first, then have drawn whatever they want to be + * blurred onto the internal gfxContext before calling this. + */ + void DoPaint(); + + /** + * Gets the internal gfxContext at any time. Must not be freed. Avoid calling + * this before calling Init() since the context would not be constructed at that + * point. + */ + gfxContext* GetContext(); + +protected: + void BoxBlurHorizontal(unsigned char* aInput, + unsigned char* aOutput, + PRUint32 aLeftLobe, + PRUint32 aRightLobe); + void BoxBlurVertical(unsigned char* aInput, + unsigned char* aOutput, + PRUint32 aTopLobe, + PRUint32 aBottomLobe); + + nsRefPtr mContext; + nsRefPtr mImageSurface; + gfxContext* mDestinationCtx; + + // Contrary to what is passed as parameters, these are in device pixels + gfxRect mRect; + PRInt32 mBlurRadius; + +}; #endif /* nsCSSRendering_h___ */ diff --git a/layout/base/nsLayoutUtils.cpp b/layout/base/nsLayoutUtils.cpp index 807fa87219c..70b06930e05 100644 --- a/layout/base/nsLayoutUtils.cpp +++ b/layout/base/nsLayoutUtils.cpp @@ -1286,6 +1286,30 @@ nsLayoutUtils::GetAllInFlowRectsUnion(nsIFrame* aFrame, nsIFrame* aRelativeTo) { : accumulator.mResultRect; } +nsRect +nsLayoutUtils::GetTextShadowRectsUnion(const nsRect& aTextAndDecorationsRect, + nsIFrame* aFrame) +{ + const nsStyleText* textStyle = aFrame->GetStyleText(); + if (!textStyle->mShadowArray) + return aTextAndDecorationsRect; + + nsRect resultRect = aTextAndDecorationsRect; + for (PRUint32 i = 0; i < textStyle->mShadowArray->Length(); ++i) { + nsRect tmpRect(aTextAndDecorationsRect); + nsTextShadowItem* shadow = textStyle->mShadowArray->ShadowAt(i); + nscoord xOffset = shadow->mXOffset.GetCoordValue(); + nscoord yOffset = shadow->mYOffset.GetCoordValue(); + nscoord blurRadius = shadow->mRadius.GetCoordValue(); + + tmpRect.MoveBy(nsPoint(xOffset, yOffset)); + tmpRect.Inflate(blurRadius, blurRadius); + + resultRect.UnionRect(resultRect, tmpRect); + } + return resultRect; +} + nsresult nsLayoutUtils::GetFontMetricsForFrame(nsIFrame* aFrame, nsIFontMetrics** aFontMetrics) diff --git a/layout/base/nsLayoutUtils.h b/layout/base/nsLayoutUtils.h index 2c09ad334a5..38f1cdf2a84 100644 --- a/layout/base/nsLayoutUtils.h +++ b/layout/base/nsLayoutUtils.h @@ -502,6 +502,14 @@ public: */ static nsRect GetAllInFlowRectsUnion(nsIFrame* aFrame, nsIFrame* aRelativeTo); + /** + * Takes a text-shadow array from the style properties of a given nsIFrame and + * computes the union of those shadows along with the given initial rect. + * If there are no shadows, the initial rect is returned. + */ + static nsRect GetTextShadowRectsUnion(const nsRect& aTextAndDecorationsRect, + nsIFrame* aFrame); + /** * Get the font metrics corresponding to the frame's style data. * @param aFrame the frame diff --git a/layout/generic/nsBlockFrame.cpp b/layout/generic/nsBlockFrame.cpp index 17ca5ccce47..e757cfa5309 100644 --- a/layout/generic/nsBlockFrame.cpp +++ b/layout/generic/nsBlockFrame.cpp @@ -1439,10 +1439,20 @@ nsBlockFrame::ComputeCombinedArea(const nsHTMLReflowState& aReflowState, // XXX_perf: This can be done incrementally. It is currently one of // the things that makes incremental reflow O(N^2). nsRect area(0, 0, aMetrics.width, aMetrics.height); + if (NS_STYLE_OVERFLOW_CLIP != aReflowState.mStyleDisplay->mOverflowX) { + PRBool inQuirks = (PresContext()->CompatibilityMode() == eCompatibility_NavQuirks); for (line_iterator line = begin_lines(), line_end = end_lines(); line != line_end; ++line) { + + // Text-shadow overflows + if (!inQuirks && line->IsInline()) { + nsRect shadowRect = nsLayoutUtils::GetTextShadowRectsUnion(line->GetCombinedArea(), + this); + area.UnionRect(area, shadowRect); + } + area.UnionRect(area, line->GetCombinedArea()); } @@ -5904,7 +5914,7 @@ nsBlockFrame::IsVisibleInSelection(nsISelection* aSelection) } /* virtual */ void -nsBlockFrame::PaintTextDecorationLine(nsIRenderingContext& aRenderingContext, +nsBlockFrame::PaintTextDecorationLine(gfxContext* aCtx, const nsPoint& aPt, nsLineBox* aLine, nscolor aColor, @@ -5945,12 +5955,11 @@ nsBlockFrame::PaintTextDecorationLine(nsIRenderingContext& aRenderingContext, // Only paint if we have a positive width if (width > 0) { - gfxContext *ctx = aRenderingContext.ThebesContext(); gfxPoint pt(PresContext()->AppUnitsToGfxUnits(start + aPt.x), PresContext()->AppUnitsToGfxUnits(aLine->mBounds.y + aPt.y)); gfxSize size(PresContext()->AppUnitsToGfxUnits(width), aSize); nsCSSRendering::PaintDecorationLine( - ctx, aColor, pt, size, + aCtx, aColor, pt, size, PresContext()->AppUnitsToGfxUnits(aLine->GetAscent()), aOffset, aDecoration, NS_STYLE_BORDER_STYLE_SOLID); } diff --git a/layout/generic/nsBlockFrame.h b/layout/generic/nsBlockFrame.h index f4bbcb9cc81..e2c9cf59d65 100644 --- a/layout/generic/nsBlockFrame.h +++ b/layout/generic/nsBlockFrame.h @@ -341,7 +341,7 @@ protected: * Overides member function of nsHTMLContainerFrame. Needed to handle the * lines in a nsBlockFrame properly. */ - virtual void PaintTextDecorationLine(nsIRenderingContext& aRenderingContext, + virtual void PaintTextDecorationLine(gfxContext* aCtx, const nsPoint& aPt, nsLineBox* aLine, nscolor aColor, diff --git a/layout/generic/nsHTMLContainerFrame.cpp b/layout/generic/nsHTMLContainerFrame.cpp index 66c11f554cf..5593b79fec0 100644 --- a/layout/generic/nsHTMLContainerFrame.cpp +++ b/layout/generic/nsHTMLContainerFrame.cpp @@ -20,6 +20,7 @@ * the Initial Developer. All Rights Reserved. * * Contributor(s): + * Michael Ventnor * * Alternatively, the contents of this file may be used under the terms of * either of the GNU General Public License Version 2 or later (the "GPL"), @@ -131,15 +132,15 @@ nsDisplayTextDecoration::Paint(nsDisplayListBuilder* aBuilder, nsHTMLContainerFrame* f = static_cast(mFrame); if (mDecoration == NS_STYLE_TEXT_DECORATION_UNDERLINE) { gfxFloat underlineOffset = fontGroup->GetUnderlineOffset(); - f->PaintTextDecorationLine(*aCtx, pt, mLine, mColor, + f->PaintTextDecorationLine(aCtx->ThebesContext(), pt, mLine, mColor, underlineOffset, ascent, metrics.underlineSize, mDecoration); } else if (mDecoration == NS_STYLE_TEXT_DECORATION_OVERLINE) { - f->PaintTextDecorationLine(*aCtx, pt, mLine, mColor, + f->PaintTextDecorationLine(aCtx->ThebesContext(), pt, mLine, mColor, metrics.maxAscent, ascent, metrics.underlineSize, mDecoration); } else { - f->PaintTextDecorationLine(*aCtx, pt, mLine, mColor, + f->PaintTextDecorationLine(aCtx->ThebesContext(), pt, mLine, mColor, metrics.strikeoutOffset, ascent, metrics.strikeoutSize, mDecoration); } @@ -151,6 +152,95 @@ nsDisplayTextDecoration::GetBounds(nsDisplayListBuilder* aBuilder) return mFrame->GetOverflowRect() + aBuilder->ToReferenceFrame(mFrame); } +class nsDisplayTextShadow : public nsDisplayItem { +public: + nsDisplayTextShadow(nsHTMLContainerFrame* aFrame, const PRUint8 aDecoration, + const nscolor& aColor, nsLineBox* aLine, + const nscoord& aBlurRadius, const gfxPoint& aOffset) + : nsDisplayItem(aFrame), mLine(aLine), mColor(aColor), + mDecorationFlags(aDecoration), + mBlurRadius(aBlurRadius), mOffset(aOffset) { + MOZ_COUNT_CTOR(nsDisplayTextShadow); + } + virtual ~nsDisplayTextShadow() { + MOZ_COUNT_DTOR(nsDisplayTextShadow); + } + + virtual void Paint(nsDisplayListBuilder* aBuilder, nsIRenderingContext* aCtx, + const nsRect& aDirtyRect); + virtual nsRect GetBounds(nsDisplayListBuilder* aBuilder); + NS_DISPLAY_DECL_NAME("TextShadow") +private: + nsLineBox* mLine; + nscolor mColor; + PRUint8 mDecorationFlags; + nscoord mBlurRadius; // App units + gfxPoint mOffset; // App units +}; + +void +nsDisplayTextShadow::Paint(nsDisplayListBuilder* aBuilder, + nsIRenderingContext* aCtx, + const nsRect& aDirtyRect) +{ + mBlurRadius = PR_MAX(mBlurRadius, 0); + + nsCOMPtr fm; + nsLayoutUtils::GetFontMetricsForFrame(mFrame, getter_AddRefs(fm)); + nsIThebesFontMetrics* tfm = static_cast(fm.get()); + gfxFontGroup* fontGroup = tfm->GetThebesFontGroup(); + gfxFont* firstFont = fontGroup->GetFontAt(0); + if (!firstFont) + return; // OOM + const gfxFont::Metrics& metrics = firstFont->GetMetrics(); + nsPoint pt = aBuilder->ToReferenceFrame(mFrame) + nsPoint(mOffset.x, mOffset.y); + + nsHTMLContainerFrame* f = static_cast(mFrame); + nsMargin bp = f->GetUsedBorderAndPadding(); + nscoord innerWidthInAppUnits = (mFrame->GetSize().width - bp.LeftRight()); + + gfxRect shadowRect = gfxRect(pt.x, pt.y, innerWidthInAppUnits, mFrame->GetSize().height); + gfxContext* thebesCtx = aCtx->ThebesContext(); + + nsContextBoxBlur contextBoxBlur; + gfxContext* shadowCtx = contextBoxBlur.Init(shadowRect, mBlurRadius, + mFrame->PresContext()->AppUnitsPerDevPixel(), + thebesCtx); + if (!shadowCtx) + return; + + thebesCtx->Save(); + thebesCtx->NewPath(); + thebesCtx->SetColor(gfxRGBA(mColor)); + + if (mDecorationFlags & NS_STYLE_TEXT_DECORATION_UNDERLINE) { + gfxFloat underlineOffset = fontGroup->GetUnderlineOffset(); + f->PaintTextDecorationLine(shadowCtx, pt, mLine, mColor, + underlineOffset, metrics.maxAscent, + metrics.underlineSize, NS_STYLE_TEXT_DECORATION_UNDERLINE); + } + if (mDecorationFlags & NS_STYLE_TEXT_DECORATION_OVERLINE) { + f->PaintTextDecorationLine(shadowCtx, pt, mLine, mColor, + metrics.maxAscent, metrics.maxAscent, + metrics.underlineSize, NS_STYLE_TEXT_DECORATION_OVERLINE); + } + if (mDecorationFlags & NS_STYLE_TEXT_DECORATION_LINE_THROUGH) { + f->PaintTextDecorationLine(shadowCtx, pt, mLine, mColor, + metrics.strikeoutOffset, metrics.maxAscent, + metrics.strikeoutSize, NS_STYLE_TEXT_DECORATION_LINE_THROUGH); + } + + contextBoxBlur.DoPaint(); + thebesCtx->Restore(); +} + +nsRect +nsDisplayTextShadow::GetBounds(nsDisplayListBuilder* aBuilder) +{ + // Shadows are always painted in the overflow rect + return mFrame->GetOverflowRect() + aBuilder->ToReferenceFrame(mFrame); +} + nsresult nsHTMLContainerFrame::DisplayTextDecorations(nsDisplayListBuilder* aBuilder, nsDisplayList* aBelowTextDecorations, @@ -170,6 +260,34 @@ nsHTMLContainerFrame::DisplayTextDecorations(nsDisplayListBuilder* aBuilder, GetTextDecorations(PresContext(), aLine != nsnull, decorations, underColor, overColor, strikeColor); + if (decorations == NS_STYLE_TEXT_DECORATION_NONE) + return NS_OK; + + // The text-shadow spec says that any text decorations must also have a shadow applied to + // it. So draw the shadows as part of the display list. + const nsStyleText* textStyle = GetStyleText(); + + if (textStyle->mShadowArray) { + for (PRUint32 i = textStyle->mShadowArray->Length(); i > 0; --i) { + nsTextShadowItem* shadow = textStyle->mShadowArray->ShadowAt(i - 1); + nscoord blurRadius = shadow->mRadius.GetCoordValue(); + nscolor shadowColor; + + if (shadow->mHasColor) + shadowColor = shadow->mColor; + else + shadowColor = GetStyleColor()->mColor; + + gfxPoint offset = gfxPoint(shadow->mXOffset.GetCoordValue(), + shadow->mYOffset.GetCoordValue()); + + // Add it to the display list so it is painted underneath the text and all decorations + nsresult rv = aBelowTextDecorations->AppendNewToTop(new (aBuilder) + nsDisplayTextShadow(this, decorations, shadowColor, aLine, blurRadius, offset)); + NS_ENSURE_SUCCESS(rv, rv); + } + } + if (decorations & NS_STYLE_TEXT_DECORATION_UNDERLINE) { nsresult rv = aBelowTextDecorations->AppendNewToTop(new (aBuilder) nsDisplayTextDecoration(this, NS_STYLE_TEXT_DECORATION_UNDERLINE, underColor, aLine)); @@ -221,7 +339,7 @@ HasTextFrameDescendantOrInFlow(nsIFrame* aFrame); /*virtual*/ void nsHTMLContainerFrame::PaintTextDecorationLine( - nsIRenderingContext& aRenderingContext, + gfxContext* aCtx, const nsPoint& aPt, nsLineBox* aLine, nscolor aColor, @@ -239,11 +357,10 @@ nsHTMLContainerFrame::PaintTextDecorationLine( } } nscoord innerWidth = mRect.width - bp.left - bp.right; - gfxContext *ctx = aRenderingContext.ThebesContext(); gfxPoint pt(PresContext()->AppUnitsToGfxUnits(bp.left + aPt.x), PresContext()->AppUnitsToGfxUnits(bp.top + aPt.y)); gfxSize size(PresContext()->AppUnitsToGfxUnits(innerWidth), aSize); - nsCSSRendering::PaintDecorationLine(ctx, aColor, pt, size, aAscent, aOffset, + nsCSSRendering::PaintDecorationLine(aCtx, aColor, pt, size, aAscent, aOffset, aDecoration, NS_STYLE_BORDER_STYLE_SOLID); } diff --git a/layout/generic/nsHTMLContainerFrame.h b/layout/generic/nsHTMLContainerFrame.h index 825686251ec..b084fdb80bd 100644 --- a/layout/generic/nsHTMLContainerFrame.h +++ b/layout/generic/nsHTMLContainerFrame.h @@ -161,7 +161,7 @@ protected: /** * Function that does the actual drawing of the textdecoration. * input: - * @param aRenderingContext + * @param aCtx the Thebes graphics context to draw on * @param aLine the line, or nsnull if this is an inline frame * @param aColor the color of the text-decoration * @param aAscent ascent of the font from which the @@ -176,7 +176,7 @@ protected: * NS_STYLE_TEXT_DECORATION_OVERLINE or * NS_STYLE_TEXT_DECORATION_LINE_THROUGH. */ - virtual void PaintTextDecorationLine(nsIRenderingContext& aRenderingContext, + virtual void PaintTextDecorationLine(gfxContext* aCtx, const nsPoint& aPt, nsLineBox* aLine, nscolor aColor, @@ -186,6 +186,7 @@ protected: const PRUint8 aDecoration); friend class nsDisplayTextDecoration; + friend class nsDisplayTextShadow; }; #endif /* nsHTMLContainerFrame_h___ */ diff --git a/layout/generic/nsLineLayout.cpp b/layout/generic/nsLineLayout.cpp index 95cc4516f42..d4cf11d95da 100644 --- a/layout/generic/nsLineLayout.cpp +++ b/layout/generic/nsLineLayout.cpp @@ -2556,6 +2556,14 @@ nsLineLayout::RelativePositionFrames(PerSpanData* psd, nsRect& aCombinedArea) // bidi reordering can move and resize the frames. So use the frame's // rect instead of mBounds. nsRect adjustedBounds(nsPoint(0, 0), psd->mFrame->mFrame->GetSize()); + + // Text-shadow overflow + if (mPresContext->CompatibilityMode() != eCompatibility_NavQuirks) { + nsRect shadowRect = nsLayoutUtils::GetTextShadowRectsUnion(adjustedBounds, + psd->mFrame->mFrame); + adjustedBounds.UnionRect(adjustedBounds, shadowRect); + } + combinedAreaResult.UnionRect(psd->mFrame->mCombinedArea, adjustedBounds); } else { diff --git a/layout/generic/nsTextFrame.h b/layout/generic/nsTextFrame.h index 542e4d188c9..39a7798f20a 100644 --- a/layout/generic/nsTextFrame.h +++ b/layout/generic/nsTextFrame.h @@ -54,6 +54,7 @@ #include "nsLineBox.h" #include "gfxFont.h" #include "gfxSkipChars.h" +#include "gfxContext.h" class nsTextPaintStyle; class PropertyProvider; @@ -253,6 +254,7 @@ public: gfxFloat GetSnappedBaselineY(gfxContext* aContext, gfxFloat aY); // primary frame paint method called from nsDisplayText + // The private DrawText() is what applies the text to a graphics context void PaintText(nsIRenderingContext* aRenderingContext, nsPoint aPt, const nsRect& aDirtyRect); // helper: paint quirks-mode CSS text decorations @@ -260,7 +262,8 @@ public: const gfxPoint& aFramePt, const gfxPoint& aTextBaselinePt, nsTextPaintStyle& aTextStyle, - PropertyProvider& aProvider); + PropertyProvider& aProvider, + const nscolor& aOverrideColor = 0); // helper: paint text frame when we're impacted by at least one selection. // Return PR_FALSE if the text was not painted and we should continue with // the fast path. @@ -382,6 +385,25 @@ protected: PropertyProvider& aProvider, nsRect* aOverflowRect); + void DrawText(gfxContext* aCtx, + const gfxPoint& aTextBaselinePt, + PRUint32 aOffset, + PRUint32 aLength, + const gfxRect* aDirtyRect, + PropertyProvider* aProvider, + gfxFloat& aAdvanceWidth, + PRBool aDrawSoftHyphen); + + void PaintOneShadow(PRUint32 aOffset, + PRUint32 aLength, + nsTextShadowItem* aShadowDetails, + PropertyProvider* aProvider, + const gfxRect& aDirtyRect, + const gfxPoint& aFramePt, + const gfxPoint& aTextBaselinePt, + gfxContext* aCtx, + const nscolor& aForegroundColor); + struct TextDecorations { PRUint8 mDecorations; nscolor mOverColor; diff --git a/layout/generic/nsTextFrameThebes.cpp b/layout/generic/nsTextFrameThebes.cpp index 65b51eb8b12..a6af68c8b1f 100644 --- a/layout/generic/nsTextFrameThebes.cpp +++ b/layout/generic/nsTextFrameThebes.cpp @@ -32,6 +32,7 @@ * Mats Palmgren * Uri Bernstein * Stephen Blackheath + * Michael Ventnor * * Alternatively, the contents of this file may be used under the terms of * either of the GNU General Public License Version 2 or later (the "GPL"), @@ -115,6 +116,7 @@ #include "gfxFont.h" #include "gfxContext.h" #include "gfxTextRunWordCache.h" +#include "gfxImageSurface.h" #ifdef NS_DEBUG #undef NOISY_BLINK @@ -3675,6 +3677,10 @@ nsTextFrame::UnionTextDecorationOverflow(nsPresContext* aPresContext, PropertyProvider& aProvider, nsRect* aOverflowRect) { + // Text-shadow overflows + nsRect shadowRect = nsLayoutUtils::GetTextShadowRectsUnion(*aOverflowRect, this); + aOverflowRect->UnionRect(*aOverflowRect, shadowRect); + if (IsFloatingFirstLetterChild()) { // The underline/overline drawable area must be contained in the overflow // rect when this is in floating first letter frame at *both* modes. @@ -3704,7 +3710,8 @@ nsTextFrame::PaintTextDecorations(gfxContext* aCtx, const gfxRect& aDirtyRect, const gfxPoint& aFramePt, const gfxPoint& aTextBaselinePt, nsTextPaintStyle& aTextPaintStyle, - PropertyProvider& aProvider) + PropertyProvider& aProvider, + const nscolor& aOverrideColor) { TextDecorations decorations = GetTextDecorations(aTextPaintStyle.PresContext()); @@ -3722,24 +3729,28 @@ nsTextFrame::PaintTextDecorations(gfxContext* aCtx, const gfxRect& aDirtyRect, gfxSize size(GetRect().width / app, 0); gfxFloat ascent = gfxFloat(mAscent) / app; + nscolor lineColor; if (decorations.HasOverline()) { + lineColor = aOverrideColor ? aOverrideColor : decorations.mOverColor; size.height = fontMetrics.underlineSize; nsCSSRendering::PaintDecorationLine( - aCtx, decorations.mOverColor, pt, size, ascent, fontMetrics.maxAscent, + aCtx, lineColor, pt, size, ascent, fontMetrics.maxAscent, NS_STYLE_TEXT_DECORATION_OVERLINE, NS_STYLE_BORDER_STYLE_SOLID); } if (decorations.HasUnderline()) { + lineColor = aOverrideColor ? aOverrideColor : decorations.mUnderColor; size.height = fontMetrics.underlineSize; gfxFloat offset = aProvider.GetFontGroup()->GetUnderlineOffset(); nsCSSRendering::PaintDecorationLine( - aCtx, decorations.mUnderColor, pt, size, ascent, offset, + aCtx, lineColor, pt, size, ascent, offset, NS_STYLE_TEXT_DECORATION_UNDERLINE, NS_STYLE_BORDER_STYLE_SOLID); } if (decorations.HasStrikeout()) { + lineColor = aOverrideColor ? aOverrideColor : decorations.mStrikeColor; size.height = fontMetrics.strikeoutSize; gfxFloat offset = fontMetrics.strikeoutOffset; nsCSSRendering::PaintDecorationLine( - aCtx, decorations.mStrikeColor, pt, size, ascent, offset, + aCtx, lineColor, pt, size, ascent, offset, NS_STYLE_TEXT_DECORATION_LINE_THROUGH, NS_STYLE_BORDER_STYLE_SOLID); } } @@ -3948,6 +3959,72 @@ PRBool SelectionIterator::GetNextSegment(gfxFloat* aXOffset, return PR_TRUE; } +void +nsTextFrame::PaintOneShadow(PRUint32 aOffset, PRUint32 aLength, + nsTextShadowItem* aShadowDetails, + PropertyProvider* aProvider, const gfxRect& aDirtyRect, + const gfxPoint& aFramePt, const gfxPoint& aTextBaselinePt, + gfxContext* aCtx, const nscolor& aForegroundColor) +{ + nscoord xOffset = aShadowDetails->mXOffset.GetCoordValue(); + nscoord yOffset = aShadowDetails->mYOffset.GetCoordValue(); + nscoord blurRadius = PR_MAX(aShadowDetails->mRadius.GetCoordValue(), 0); + + nsTextPaintStyle textPaintStyle(this); + gfxFloat advanceWidth; + + gfxTextRun::Metrics shadowMetrics = + mTextRun->MeasureText(aOffset, aLength, PR_FALSE, + nsnull, aProvider); + + // This rect is the box which is equivalent to where the shadow will be painted. + // X and Y are significant because they can affect the rounding. + // The origin of mBoundingBox is the text baseline point, so we must translate it by + // that much in order to make the origin the top-left corner of the text bounding box. + gfxRect shadowRect = shadowMetrics.mBoundingBox + aTextBaselinePt; + shadowRect.MoveBy(gfxPoint(xOffset, yOffset)); + + if (GetStateBits() & TEXT_HYPHEN_BREAK) { + // Add the width of the soft hyphen so it isn't cut off + shadowRect.size.width += aProvider->GetHyphenWidth(); + } + + nsContextBoxBlur contextBoxBlur; + gfxContext* shadowContext = contextBoxBlur.Init(shadowRect, blurRadius, + PresContext()->AppUnitsPerDevPixel(), + aCtx); + if (!shadowContext) + return; + + nscolor shadowColor; + if (aShadowDetails->mHasColor) + shadowColor = aShadowDetails->mColor; + else + shadowColor = aForegroundColor; + + aCtx->Save(); + aCtx->NewPath(); + aCtx->SetColor(gfxRGBA(shadowColor)); + + // Draw the text onto our alpha-only surface to capture the alpha values. + // Remember that the box blur context has a device offset on it, so we don't need to + // translate any coordinates to fit on the surface. + DrawText(shadowContext, + aTextBaselinePt + gfxPoint(xOffset, yOffset), + aOffset, aLength, &aDirtyRect, aProvider, advanceWidth, + (GetStateBits() & TEXT_HYPHEN_BREAK) != 0); + + // This will only have an effect in quirks mode. Standards mode text-decoration shadow painting + // is handled in nsHTMLContainerFrame.cpp, so you must remember to consider that if you change + // any code behaviour here. + PaintTextDecorations(shadowContext, aDirtyRect, aFramePt + gfxPoint(xOffset, yOffset), + aTextBaselinePt + gfxPoint(xOffset, yOffset), + textPaintStyle, *aProvider, shadowColor); + + contextBoxBlur.DoPaint(); + aCtx->Restore(); +} + // Paints selection backgrounds and text in the correct colors. Also computes // aAllTypes, the union of all selection types that are applying to this text. void @@ -4031,18 +4108,11 @@ nsTextFrame::PaintTextWithSelectionColors(gfxContext* aCtx, // Draw text segment aCtx->SetColor(gfxRGBA(foreground)); gfxFloat advance; - mTextRun->Draw(aCtx, gfxPoint(aFramePt.x + xOffset, aTextBaselinePt.y), offset, length, - &aDirtyRect, &aProvider, &advance); + + DrawText(aCtx, gfxPoint(aFramePt.x + xOffset, aTextBaselinePt.y), + offset, length, &aDirtyRect, &aProvider, + advance, hyphenWidth > 0); if (hyphenWidth) { - // Draw the hyphen - gfxFloat hyphenBaselineX = aFramePt.x + xOffset + mTextRun->GetDirection()*advance; - // Get a reference rendering context because aCtx might not have the - // reference matrix currently set - gfxTextRunCache::AutoTextRun hyphenTextRun(GetHyphenTextRun(mTextRun, nsnull, this)); - if (hyphenTextRun.get()) { - hyphenTextRun->Draw(aCtx, gfxPoint(hyphenBaselineX, aTextBaselinePt.y), - 0, hyphenTextRun->GetLength(), &aDirtyRect, nsnull, nsnull); - } advance += hyphenWidth; } iterator.UpdateWithAdvance(advance); @@ -4193,6 +4263,23 @@ nsTextFrame::PaintText(nsIRenderingContext* aRenderingContext, nsPoint aPt, gfxRect dirtyRect(aDirtyRect.x, aDirtyRect.y, aDirtyRect.width, aDirtyRect.height); + gfxFloat advanceWidth; + gfxRGBA foregroundColor = gfxRGBA(textPaintStyle.GetTextColor()); + + // Paint the text shadow before doing any foreground stuff + const nsStyleText* textStyle = GetStyleText(); + if (textStyle->mShadowArray) { + // Text shadow happens with the last value being painted at the back, + // ie. it is painted first. + for (PRUint32 i = textStyle->mShadowArray->Length(); i > 0; --i) { + PaintOneShadow(provider.GetStart().GetSkippedOffset(), + ComputeTransformedLength(provider), + textStyle->mShadowArray->ShadowAt(i - 1), &provider, + dirtyRect, framePt, textBaselinePt, ctx, + textPaintStyle.GetTextColor()); + } + } + // Fork off to the (slower) paint-with-selection path if necessary. if (GetNonGeneratedAncestor(this)->GetStateBits() & NS_FRAME_SELECTED_CONTENT) { if (PaintTextWithSelection(ctx, framePt, textBaselinePt, @@ -4200,27 +4287,36 @@ nsTextFrame::PaintText(nsIRenderingContext* aRenderingContext, nsPoint aPt, return; } - gfxFloat advanceWidth; - gfxFloat* needAdvanceWidth = - (GetStateBits() & TEXT_HYPHEN_BREAK) ? &advanceWidth : nsnull; - ctx->SetColor(gfxRGBA(textPaintStyle.GetTextColor())); - - mTextRun->Draw(ctx, textBaselinePt, - provider.GetStart().GetSkippedOffset(), - ComputeTransformedLength(provider), - &dirtyRect, &provider, needAdvanceWidth); - if (GetStateBits() & TEXT_HYPHEN_BREAK) { - gfxFloat hyphenBaselineX = textBaselinePt.x + mTextRun->GetDirection()*advanceWidth; + ctx->SetColor(foregroundColor); + + DrawText(ctx, textBaselinePt, provider.GetStart().GetSkippedOffset(), + ComputeTransformedLength(provider), &dirtyRect, + &provider, advanceWidth, + (GetStateBits() & TEXT_HYPHEN_BREAK) != 0); + PaintTextDecorations(ctx, dirtyRect, framePt, textBaselinePt, + textPaintStyle, provider); +} + +void +nsTextFrame::DrawText(gfxContext* aCtx, const gfxPoint& aTextBaselinePt, + PRUint32 aOffset, PRUint32 aLength, + const gfxRect* aDirtyRect, PropertyProvider* aProvider, + gfxFloat& aAdvanceWidth, PRBool aDrawSoftHyphen) +{ + // Paint the text and soft-hyphen (if any) onto the given graphics context + mTextRun->Draw(aCtx, aTextBaselinePt, aOffset, aLength, + aDirtyRect, aProvider, &aAdvanceWidth); + + if (aDrawSoftHyphen) { + gfxFloat hyphenBaselineX = aTextBaselinePt.x + mTextRun->GetDirection() * aAdvanceWidth; // Don't use ctx as the context, because we need a reference context here, // ctx may be transformed. gfxTextRunCache::AutoTextRun hyphenTextRun(GetHyphenTextRun(mTextRun, nsnull, this)); if (hyphenTextRun.get()) { - hyphenTextRun->Draw(ctx, gfxPoint(hyphenBaselineX, textBaselinePt.y), - 0, hyphenTextRun->GetLength(), &dirtyRect, nsnull, nsnull); + hyphenTextRun->Draw(aCtx, gfxPoint(hyphenBaselineX, aTextBaselinePt.y), + 0, hyphenTextRun->GetLength(), aDirtyRect, nsnull, nsnull); } } - PaintTextDecorations(ctx, dirtyRect, framePt, textBaselinePt, - textPaintStyle, provider); } PRInt16 diff --git a/layout/mathml/base/src/nsMathMLContainerFrame.cpp b/layout/mathml/base/src/nsMathMLContainerFrame.cpp index cb1aabd6063..86ce5713b86 100644 --- a/layout/mathml/base/src/nsMathMLContainerFrame.cpp +++ b/layout/mathml/base/src/nsMathMLContainerFrame.cpp @@ -856,6 +856,12 @@ nsMathMLContainerFrame::GatherAndStoreOverflow(nsHTMLReflowMetrics* aMetrics) // frame rectangle. nsRect frameRect(0, 0, aMetrics->width, aMetrics->height); + // Text-shadow overflows. + if (PresContext()->CompatibilityMode() != eCompatibility_NavQuirks) { + nsRect shadowRect = nsLayoutUtils::GetTextShadowRectsUnion(frameRect, this); + frameRect.UnionRect(frameRect, shadowRect); + } + // All non-child-frame content such as nsMathMLChars (and most child-frame // content) is included in mBoundingMetrics. nsRect boundingBox(mBoundingMetrics.leftBearing, diff --git a/layout/reftests/reftest.list b/layout/reftests/reftest.list index c9a53a94a6b..282f7653597 100644 --- a/layout/reftests/reftest.list +++ b/layout/reftests/reftest.list @@ -55,7 +55,7 @@ include text/reftest.list include text-decoration/reftest.list # text-shadow/ -# include text-shadow/reftest.list +include text-shadow/reftest.list # text-indent/ include text-indent/reftest.list diff --git a/layout/reftests/text-shadow/decorations-multiple-zorder-ref.html b/layout/reftests/text-shadow/decorations-multiple-zorder-ref.html index 3fa0f87708e..000c19e825f 100644 --- a/layout/reftests/text-shadow/decorations-multiple-zorder-ref.html +++ b/layout/reftests/text-shadow/decorations-multiple-zorder-ref.html @@ -1,13 +1,25 @@ - -
testforquirks
-
testforquirks
+ +
testforquirks
+
testforquirks
+ + +
test
+
test
testforquirks
testforquirks
- -
testforquirks
-
testforquirks
+ +
testfor
+
testfor
+ + +
testforquirks
+
testforquirks
+ + +
testforquirks
+
testforquirks