Implement text-shadow for the text-overflow marker text (ellipsis) r=roc
authorMichael Ventnor <mventnor@mozilla.com>
Fri, 01 Jul 2011 16:43:11 +1000
changeset 72117 2fba916e056cdb24bd569a6b8ab9ea69b90c7c25
parent 72116 592403064072fe729609a03d4d4eed7c47e16fff
child 72118 e8023ac96fb770f73baeb3a985b64388f7a2c184
push idunknown
push userunknown
push dateunknown
reviewersroc
milestone7.0a1
Implement text-shadow for the text-overflow marker text (ellipsis) r=roc
layout/base/nsLayoutUtils.cpp
layout/base/nsLayoutUtils.h
layout/generic/TextOverflow.cpp
layout/reftests/text-overflow/marker-shadow-notref.html
layout/reftests/text-overflow/marker-shadow.html
layout/reftests/text-overflow/reftest.list
layout/xul/base/src/nsTextBoxFrame.cpp
layout/xul/base/src/nsTextBoxFrame.h
--- a/layout/base/nsLayoutUtils.cpp
+++ b/layout/base/nsLayoutUtils.cpp
@@ -2884,16 +2884,71 @@ nsLayoutUtils::GetStringWidth(const nsIF
                                          direction, presContext, *aContext);
     }
   }
 #endif // IBMBIDI
   aContext->SetTextRunRTL(PR_FALSE);
   return aContext->GetWidth(aString, aLength);
 }
 
+/* static */ void
+nsLayoutUtils::PaintTextShadow(const nsIFrame* aFrame,
+                               nsRenderingContext* aContext,
+                               const nsRect& aTextRect,
+                               const nsRect& aDirtyRect,
+                               const nscolor& aForegroundColor,
+                               TextShadowCallback aCallback,
+                               void* aCallbackData)
+{
+  const nsStyleText* textStyle = aFrame->GetStyleText();
+  if (!textStyle->mTextShadow)
+    return;
+
+  // Text shadow happens with the last value being painted at the back,
+  // ie. it is painted first.
+  gfxContext* aDestCtx = aContext->ThebesContext();
+  for (PRUint32 i = textStyle->mTextShadow->Length(); i > 0; --i) {
+    nsCSSShadowItem* shadowDetails = textStyle->mTextShadow->ShadowAt(i - 1);
+    nsPoint shadowOffset(shadowDetails->mXOffset,
+                         shadowDetails->mYOffset);
+    nscoord blurRadius = NS_MAX(shadowDetails->mRadius, 0);
+
+    nsRect shadowRect(aTextRect);
+    shadowRect.MoveBy(shadowOffset);
+
+    nsPresContext* presCtx = aFrame->PresContext();
+    nsContextBoxBlur contextBoxBlur;
+    gfxContext* shadowContext = contextBoxBlur.Init(shadowRect, 0, blurRadius,
+                                                    presCtx->AppUnitsPerDevPixel(),
+                                                    aDestCtx, aDirtyRect, nsnull);
+    if (!shadowContext)
+      continue;
+
+    nscolor shadowColor;
+    if (shadowDetails->mHasColor)
+      shadowColor = shadowDetails->mColor;
+    else
+      shadowColor = aForegroundColor;
+
+    // Conjure an nsRenderingContext from a gfxContext for drawing the text
+    nsRefPtr<nsRenderingContext> renderingContext = new nsRenderingContext();
+    renderingContext->Init(presCtx->DeviceContext(), shadowContext);
+
+    aDestCtx->Save();
+    aDestCtx->NewPath();
+    aDestCtx->SetColor(gfxRGBA(shadowColor));
+
+    // The callback will draw whatever we want to blur as a shadow.
+    aCallback(renderingContext, shadowOffset, shadowColor, aCallbackData);
+
+    contextBoxBlur.DoPaint();
+    aDestCtx->Restore();
+  }
+}
+
 /* static */ nscoord
 nsLayoutUtils::GetCenteredFontBaseline(nsFontMetrics* aFontMetrics,
                                        nscoord         aLineHeight)
 {
   nscoord fontAscent = aFontMetrics->MaxAscent();
   nscoord fontHeight = aFontMetrics->MaxHeight();
 
   nscoord leading = aLineHeight - fontHeight;
--- a/layout/base/nsLayoutUtils.h
+++ b/layout/base/nsLayoutUtils.h
@@ -942,16 +942,33 @@ public:
                          PRUint8              aDirection = NS_STYLE_DIRECTION_INHERIT);
 
   static nscoord GetStringWidth(const nsIFrame*      aFrame,
                                 nsRenderingContext* aContext,
                                 const PRUnichar*     aString,
                                 PRInt32              aLength);
 
   /**
+   * Helper function for drawing text-shadow. The callback's job
+   * is to draw whatever needs to be blurred onto the given context.
+   */
+  typedef void (* TextShadowCallback)(nsRenderingContext* aCtx,
+                                      nsPoint aShadowOffset,
+                                      const nscolor& aShadowColor,
+                                      void* aData);
+
+  static void PaintTextShadow(const nsIFrame*     aFrame,
+                              nsRenderingContext* aContext,
+                              const nsRect&       aTextRect,
+                              const nsRect&       aDirtyRect,
+                              const nscolor&      aForegroundColor,
+                              TextShadowCallback  aCallback,
+                              void*               aCallbackData);
+
+  /**
    * Gets the baseline to vertically center text from a font within a
    * line of specified height.
    *
    * Returns the baseline position relative to the top of the line.
    */
   static nscoord GetCenteredFontBaseline(nsFontMetrics* aFontMetrics,
                                          nscoord         aLineHeight);
 
--- a/layout/generic/TextOverflow.cpp
+++ b/layout/generic/TextOverflow.cpp
@@ -16,16 +16,17 @@
  * The Original Code is an implementation of CSS3 text-overflow.
  *
  * The Initial Developer of the Original Code is the Mozilla Foundation.
  * Portions created by the Initial Developer are Copyright (C) 2011
  * the Initial Developer. All Rights Reserved.
  *
  * Contributor(s):
  *   Mats Palmgren <matspal@gmail.com> (original author)
+ *   Michael Ventnor <m.ventnor@gmail.com>
  *
  * Alternatively, the contents of this file may be used under the terms of
  * either the GNU General Public License Version 2 or later (the "GPL"), or
  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  * in which case the provisions of the GPL or the LGPL are applicable instead
  * of those above. If you wish to allow use of your version of this file only
  * under the terms of either the GPL or the LGPL, and not to allow others to
  * use your version of this file under the terms of the MPL, indicate your
@@ -199,38 +200,68 @@ public:
     MOZ_COUNT_CTOR(nsDisplayTextOverflowMarker);
   }
 #ifdef NS_BUILD_REFCNT_LOGGING
   virtual ~nsDisplayTextOverflowMarker() {
     MOZ_COUNT_DTOR(nsDisplayTextOverflowMarker);
   }
 #endif
   virtual nsRect GetBounds(nsDisplayListBuilder* aBuilder) {
-    return mRect;
+    nsRect shadowRect =
+      nsLayoutUtils::GetTextShadowRectsUnion(mRect, mFrame);
+    return mRect.Union(shadowRect);
   }
   virtual void Paint(nsDisplayListBuilder* aBuilder,
                      nsRenderingContext* aCtx);
+  void PaintTextToContext(nsRenderingContext* aCtx,
+                          nsPoint aOffsetFromRect);
   NS_DISPLAY_DECL_NAME("TextOverflow", TYPE_TEXT_OVERFLOW)
 private:
   nsRect          mRect;   // in reference frame coordinates
   const nsString  mString; // the marker text
   nscoord         mAscent; // baseline for the marker text in mRect
 };
 
+static void
+PaintTextShadowCallback(nsRenderingContext* aCtx,
+                        nsPoint aShadowOffset,
+                        const nscolor& aShadowColor,
+                        void* aData)
+{
+  reinterpret_cast<nsDisplayTextOverflowMarker*>(aData)->
+           PaintTextToContext(aCtx, aShadowOffset);
+}
+
 void
 nsDisplayTextOverflowMarker::Paint(nsDisplayListBuilder* aBuilder,
                                    nsRenderingContext*   aCtx)
 {
+  nscolor foregroundColor = nsLayoutUtils::GetTextColor(mFrame);
+
+  // Paint the text-shadows for the overflow marker
+  nsLayoutUtils::PaintTextShadow(mFrame, aCtx, mRect, mVisibleRect,
+                                 foregroundColor, PaintTextShadowCallback,
+                                 (void*)this);
+
+  aCtx->SetColor(foregroundColor);
+  PaintTextToContext(aCtx, nsPoint(0, 0));
+}
+
+void
+nsDisplayTextOverflowMarker::PaintTextToContext(nsRenderingContext* aCtx,
+                                                nsPoint aOffsetFromRect)
+{
   nsStyleContext* sc = mFrame->GetStyleContext();
   nsLayoutUtils::SetFontFromStyle(aCtx, sc);
-  aCtx->SetColor(nsLayoutUtils::GetTextColor(mFrame));
+
   nsPoint baselinePt = mRect.TopLeft();
   baselinePt.y += mAscent;
-  nsLayoutUtils::DrawString(mFrame, aCtx, mString.get(), mString.Length(),
-                            baselinePt);
+
+  nsLayoutUtils::DrawString(mFrame, aCtx, mString.get(),
+                            mString.Length(), baselinePt + aOffsetFromRect);
 }
 
 /* static */ TextOverflow*
 TextOverflow::WillProcessLines(nsDisplayListBuilder*   aBuilder,
                                const nsDisplayListSet& aLists,
                                nsIFrame*               aBlockFrame)
 {
   if (!CanHaveTextOverflow(aBuilder, aBlockFrame)) {
new file mode 100644
--- /dev/null
+++ b/layout/reftests/text-overflow/marker-shadow-notref.html
@@ -0,0 +1,30 @@
+<!DOCTYPE HTML>
+<!--
+    Any copyright is dedicated to the Public Domain.
+    http://creativecommons.org/licenses/publicdomain/
+-->
+<html>
+<head>
+<meta http-equiv="content-type" content="text/html; charset=UTF-8">
+<style type="text/css">
+@font-face {
+  font-family: DejaVuSansMono;
+  src: url(../fonts/DejaVuSansMono.woff);
+}
+
+div {
+  font-family: DejaVuSansMono;
+  width: 60px;
+  overflow: hidden;
+  white-space: nowrap;
+  font-size: 14px;
+  text-overflow: "...";
+}
+</style>
+</head>
+<body>
+<div>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;a</div>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/text-overflow/marker-shadow.html
@@ -0,0 +1,33 @@
+<!DOCTYPE HTML>
+<!--
+    Any copyright is dedicated to the Public Domain.
+    http://creativecommons.org/licenses/publicdomain/
+
+    Test: Marker should have text-shadow applied to it
+-->
+<html>
+<head>
+<meta http-equiv="content-type" content="text/html; charset=UTF-8">
+<style type="text/css">
+@font-face {
+  font-family: DejaVuSansMono;
+  src: url(../fonts/DejaVuSansMono.woff);
+}
+
+div {
+  font-family: DejaVuSansMono;
+  width: 60px;
+  overflow: hidden;
+  white-space: nowrap;
+  font-size: 14px;
+  text-overflow: "...";
+  text-shadow: 0px 2px 2px red;
+}
+</style>
+</head>
+<body>
+<div>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
+&nbsp;&nbsp;&nbsp;&nbsp;a</div>
+</body>
+</html>
--- a/layout/reftests/text-overflow/reftest.list
+++ b/layout/reftests/text-overflow/reftest.list
@@ -8,8 +8,9 @@ HTTP(..) == anonymous-block.html anonymo
 HTTP(..) == false-marker-overlap.html false-marker-overlap-ref.html
 HTTP(..) == visibility-hidden.html visibility-hidden-ref.html
 HTTP(..) == block-padding.html block-padding-ref.html
 HTTP(..) == quirks-decorations.html quirks-decorations-ref.html
 HTTP(..) == quirks-line-height.html quirks-line-height-ref.html
 HTTP(..) == standards-decorations.html standards-decorations-ref.html
 HTTP(..) == standards-line-height.html standards-line-height-ref.html
 HTTP(..) == selection.html selection-ref.html
+HTTP(..) != marker-shadow.html marker-shadow-notref.html
--- a/layout/xul/base/src/nsTextBoxFrame.cpp
+++ b/layout/xul/base/src/nsTextBoxFrame.cpp
@@ -342,27 +342,56 @@ public:
                      nsRenderingContext* aCtx);
   virtual nsRect GetBounds(nsDisplayListBuilder* aBuilder);
   NS_DISPLAY_DECL_NAME("XULTextBox", TYPE_XUL_TEXT_BOX)
 
   virtual nsRect GetComponentAlphaBounds(nsDisplayListBuilder* aBuilder);
 
   virtual void DisableComponentAlpha() { mDisableSubpixelAA = PR_TRUE; }
 
+  void PaintTextWithOffset(nsRenderingContext* aCtx,
+                           nsPoint aOffset);
+
   PRPackedBool mDisableSubpixelAA;
 };
 
+static void
+PaintTextShadowCallback(nsRenderingContext* aCtx,
+                        nsPoint aShadowOffset,
+                        const nscolor& aShadowColor,
+                        void* aData)
+{
+  reinterpret_cast<nsDisplayXULTextBox*>(aData)->
+           PaintTextWithOffset(aCtx, aShadowOffset);
+}
+
 void
 nsDisplayXULTextBox::Paint(nsDisplayListBuilder* aBuilder,
                            nsRenderingContext* aCtx)
 {
   gfxContextAutoDisableSubpixelAntialiasing disable(aCtx->ThebesContext(),
                                                     mDisableSubpixelAA);
+
+  // Paint the text shadow before doing any foreground stuff
+  nsRect drawRect = static_cast<nsTextBoxFrame*>(mFrame)->mTextDrawRect;
+  nsLayoutUtils::PaintTextShadow(mFrame, aCtx,
+                                 drawRect, mVisibleRect,
+                                 mFrame->GetStyleColor()->mColor,
+                                 PaintTextShadowCallback,
+                                 (void*)this);
+
+  PaintTextWithOffset(aCtx, nsPoint(0, 0));
+}
+
+void
+nsDisplayXULTextBox::PaintTextWithOffset(nsRenderingContext* aCtx,
+                                         nsPoint aOffset)
+{
   static_cast<nsTextBoxFrame*>(mFrame)->
-    PaintTitle(*aCtx, mVisibleRect, ToReferenceFrame());
+    PaintTitle(*aCtx, mVisibleRect, ToReferenceFrame() + aOffset);
 }
 
 nsRect
 nsDisplayXULTextBox::GetBounds(nsDisplayListBuilder* aBuilder) {
   return mFrame->GetVisualOverflowRect() + ToReferenceFrame();
 }
 
 nsRect
@@ -390,33 +419,17 @@ nsTextBoxFrame::BuildDisplayList(nsDispl
 void
 nsTextBoxFrame::PaintTitle(nsRenderingContext& aRenderingContext,
                            const nsRect&        aDirtyRect,
                            nsPoint              aPt)
 {
     if (mTitle.IsEmpty())
         return;
 
-    nsRect textRect = mTextDrawRect + aPt;
-
-    // Paint the text shadow before doing any foreground stuff
-    const nsStyleText* textStyle = GetStyleText();
-    if (textStyle->mTextShadow) {
-      // Text shadow happens with the last value being painted at the back,
-      // ie. it is painted first.
-      for (PRUint32 i = textStyle->mTextShadow->Length(); i > 0; --i) {
-        PaintOneShadow(aRenderingContext.ThebesContext(),
-                       textRect,
-                       textStyle->mTextShadow->ShadowAt(i - 1),
-                       GetStyleColor()->mColor,
-                       aDirtyRect);
-      }
-    }
-
-    DrawText(aRenderingContext, textRect, nsnull);
+    DrawText(aRenderingContext, mTextDrawRect + aPt, nsnull);
 }
 
 void
 nsTextBoxFrame::DrawText(nsRenderingContext& aRenderingContext,
                          const nsRect&        aTextRect,
                          const nscolor*       aOverrideColor)
 {
     nsPresContext* presContext = PresContext();
@@ -604,59 +617,16 @@ nsTextBoxFrame::DrawText(nsRenderingCont
       gfxFloat sizePixel = presContext->AppUnitsToGfxUnits(size);
       nsCSSRendering::PaintDecorationLine(ctx, strikeColor,
                         pt, gfxSize(width, sizePixel), ascentPixel, offsetPixel,
                         NS_STYLE_TEXT_DECORATION_LINE_LINE_THROUGH,
                         strikeStyle);
     }
 }
 
-void nsTextBoxFrame::PaintOneShadow(gfxContext*      aCtx,
-                                    const nsRect&    aTextRect,
-                                    nsCSSShadowItem* aShadowDetails,
-                                    const nscolor&   aForegroundColor,
-                                    const nsRect&    aDirtyRect) {
-  nsPoint shadowOffset(aShadowDetails->mXOffset,
-                       aShadowDetails->mYOffset);
-  nscoord blurRadius = NS_MAX(aShadowDetails->mRadius, 0);
-
-  nsRect shadowRect(aTextRect);
-  shadowRect.MoveBy(shadowOffset);
-
-  nsContextBoxBlur contextBoxBlur;
-  gfxContext* shadowContext = contextBoxBlur.Init(shadowRect, 0, blurRadius,
-                                                  PresContext()->AppUnitsPerDevPixel(),
-                                                  aCtx, aDirtyRect, nsnull);
-
-  if (!shadowContext)
-    return;
-
-  nscolor shadowColor;
-  if (aShadowDetails->mHasColor)
-    shadowColor = aShadowDetails->mColor;
-  else
-    shadowColor = aForegroundColor;
-
-  // Conjure an nsRenderingContext from a gfxContext for DrawText
-  nsRefPtr<nsRenderingContext> renderingContext = new nsRenderingContext();
-  renderingContext->Init(PresContext()->DeviceContext(), shadowContext);
-
-  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(*renderingContext, shadowRect, &shadowColor);
-  contextBoxBlur.DoPaint();
-  aCtx->Restore();
-}
-
 void
 nsTextBoxFrame::CalculateUnderline(nsRenderingContext& aRenderingContext)
 {
     if (mAccessKeyInfo && mAccessKeyInfo->mAccesskeyIndex != kNotFound) {
          // Calculate all fields of mAccessKeyInfo which
          // are the same for both BiDi and non-BiDi frames.
          const PRUnichar *titleString = mCroppedTitle.get();
          aRenderingContext.SetTextRunRTL(PR_FALSE);
--- a/layout/xul/base/src/nsTextBoxFrame.h
+++ b/layout/xul/base/src/nsTextBoxFrame.h
@@ -89,16 +89,17 @@ public:
                   nsPoint              aPt);
 
   nsRect GetComponentAlphaBounds();
 
   virtual PRBool ComputesOwnOverflowArea();
 
 protected:
   friend class nsAsyncAccesskeyUpdate;
+  friend class nsDisplayXULTextBox;
   // Should be called only by nsAsyncAccesskeyUpdate.
   // Returns PR_TRUE if accesskey was updated.
   PRBool UpdateAccesskey(nsWeakFrame& aWeakThis);
   void UpdateAccessTitle();
   void UpdateAccessIndex();
 
   // REVIEW: SORRY! Couldn't resist devirtualizing these
   void LayoutTitle(nsPresContext*      aPresContext,
@@ -129,22 +130,16 @@ private:
 
   PRBool AlwaysAppendAccessKey();
   PRBool InsertSeparatorBeforeAccessKey();
 
   void DrawText(nsRenderingContext& aRenderingContext,
                          const nsRect&        aTextRect,
                          const nscolor*       aOverrideColor);
 
-  void PaintOneShadow(gfxContext *     aCtx,
-                      const nsRect&    aTextRect,
-                      nsCSSShadowItem* aShadowDetails,
-                      const nscolor&   aForegroundColor,
-                      const nsRect&    aDirtyRect);
-
   nsString mTitle;
   nsString mCroppedTitle;
   nsString mAccessKey;
   nsSize mTextSize;
   nsRect mTextDrawRect;
   nsAccessKeyInfo* mAccessKeyInfo;
 
   CroppingStyle mCropType;