Bug 889235 - Make text-shadow work on SVG text. r=cam
authorRobert Longson <longsonr@gmail.com>
Mon, 13 Jan 2014 12:21:42 +0000
changeset 179182 1ce60609ad03037a848339ab3fa41b4f64f489df
parent 179181 e6c2155d6e511d9938d7553399d4f4f20b7d0304
child 179183 a68d50181ebbec95e8cee964b5b29670ad24cd76
push id3343
push userffxbld
push dateMon, 17 Mar 2014 21:55:32 +0000
treeherdermozilla-beta@2f7d3415f79f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerscam
bugs889235
milestone29.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 889235 - Make text-shadow work on SVG text. r=cam
layout/base/nsLayoutUtils.cpp
layout/generic/nsTextFrame.cpp
layout/reftests/svg/text/dynamic-text-shadow.svg
layout/reftests/svg/text/ignore-text-shadow.svg
layout/reftests/svg/text/reftest.list
layout/reftests/svg/text/text-shadow-ref.svg
layout/reftests/svg/text/text-shadow.svg
layout/style/nsStyleStruct.h
layout/style/nsStyleStructInlines.h
layout/svg/SVGTextFrame.cpp
--- a/layout/base/nsLayoutUtils.cpp
+++ b/layout/base/nsLayoutUtils.cpp
@@ -2594,17 +2594,17 @@ nsLayoutUtils::GetAllInFlowRectsUnion(ns
 }
 
 nsRect
 nsLayoutUtils::GetTextShadowRectsUnion(const nsRect& aTextAndDecorationsRect,
                                        nsIFrame* aFrame,
                                        uint32_t aFlags)
 {
   const nsStyleText* textStyle = aFrame->StyleText();
-  if (!textStyle->HasTextShadow(aFrame))
+  if (!textStyle->HasTextShadow())
     return aTextAndDecorationsRect;
 
   nsRect resultRect = aTextAndDecorationsRect;
   int32_t A2D = aFrame->PresContext()->AppUnitsPerDevPixel();
   for (uint32_t i = 0; i < textStyle->mTextShadow->Length(); ++i) {
     nsCSSShadowItem* shadow = textStyle->mTextShadow->ShadowAt(i);
     nsMargin blur = nsContextBoxBlur::GetBlurRadiusMargin(shadow->mRadius, A2D);
     if ((aFlags & EXCLUDE_BLUR_SHADOWS) && blur != nsMargin(0, 0, 0, 0))
@@ -3809,17 +3809,17 @@ nsLayoutUtils::PaintTextShadow(const nsI
                                nsRenderingContext* aContext,
                                const nsRect& aTextRect,
                                const nsRect& aDirtyRect,
                                const nscolor& aForegroundColor,
                                TextShadowCallback aCallback,
                                void* aCallbackData)
 {
   const nsStyleText* textStyle = aFrame->StyleText();
-  if (!textStyle->HasTextShadow(aFrame))
+  if (!textStyle->HasTextShadow())
     return;
 
   // Text shadow happens with the last value being painted at the back,
   // ie. it is painted first.
   gfxContext* aDestCtx = aContext->ThebesContext();
   for (uint32_t i = textStyle->mTextShadow->Length(); i > 0; --i) {
     nsCSSShadowItem* shadowDetails = textStyle->mTextShadow->ShadowAt(i - 1);
     nsPoint shadowOffset(shadowDetails->mXOffset,
--- a/layout/generic/nsTextFrame.cpp
+++ b/layout/generic/nsTextFrame.cpp
@@ -5206,19 +5206,16 @@ static bool GetSelectionTextColors(Selec
  * If text-shadow was not specified, *aShadow is left untouched
  * (NOT reset to null), and the function returns false.
  */
 static bool GetSelectionTextShadow(nsIFrame* aFrame,
                                    SelectionType aType,
                                    nsTextPaintStyle& aTextPaintStyle,
                                    nsCSSShadowArray** aShadow)
 {
-  if (aFrame->IsSVGText()) {
-    return false;
-  }
   switch (aType) {
     case nsISelectionController::SELECTION_NORMAL:
       return aTextPaintStyle.GetSelectionShadow(aShadow);
     default:
       return false;
   }
 }
 
@@ -5506,17 +5503,17 @@ nsTextFrame::PaintTextWithSelectionColor
                                  &type, &rangeStyle)) {
     nscolor foreground, background;
     GetSelectionTextColors(type, aTextPaintStyle, rangeStyle,
                            &foreground, &background);
     gfxPoint textBaselinePt(aFramePt.x + xOffset, aTextBaselinePt.y);
 
     // Determine what shadow, if any, to draw - either from textStyle
     // or from the ::-moz-selection pseudo-class if specified there
-    nsCSSShadowArray *shadow = textStyle->GetTextShadow(this);
+    nsCSSShadowArray* shadow = textStyle->GetTextShadow();
     GetSelectionTextShadow(this, type, aTextPaintStyle, &shadow);
 
     // Draw shadows, if any
     if (shadow) {
       gfxTextRun::Metrics shadowMetrics =
         mTextRun->MeasureText(offset, length, gfxFont::LOOSE_INK_EXTENTS,
                               nullptr, &aProvider);
       if (GetStateBits() & TEXT_HYPHEN_BREAK) {
@@ -5892,17 +5889,17 @@ nsTextFrame::PaintText(nsRenderingContex
                                aCallbacks)) {
       return;
     }
   }
 
   nscolor foregroundColor = textPaintStyle.GetTextColor();
   if (!aCallbacks) {
     const nsStyleText* textStyle = StyleText();
-    if (textStyle->HasTextShadow(this)) {
+    if (textStyle->HasTextShadow()) {
       // Text shadow happens with the last value being painted at the back,
       // ie. it is painted first.
       gfxTextRun::Metrics shadowMetrics = 
         mTextRun->MeasureText(startOffset, maxLength, gfxFont::LOOSE_INK_EXTENTS,
                               nullptr, &provider);
       for (uint32_t i = textStyle->mTextShadow->Length(); i > 0; --i) {
         PaintOneShadow(startOffset, maxLength,
                        textStyle->mTextShadow->ShadowAt(i - 1), &provider,
new file mode 100644
--- /dev/null
+++ b/layout/reftests/svg/text/dynamic-text-shadow.svg
@@ -0,0 +1,15 @@
+<!--
+     Any copyright is dedicated to the Public Domain.
+     http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<svg xmlns="http://www.w3.org/2000/svg" class="reftest-wait">
+  <text x="30" y="30" fill="green" style="text-shadow: #cc9900 20px 12px 2px">Hello</text>
+  <script>
+    function f() {
+      document.getElementsByTagName("text")[0].style.textShadow = "grey 3px 3px";
+      document.documentElement.removeAttribute("class");
+    }
+
+    window.addEventListener("MozReftestInvalidate", f, false);
+  </script>
+</svg>
deleted file mode 100644
--- a/layout/reftests/svg/text/ignore-text-shadow.svg
+++ /dev/null
@@ -1,7 +0,0 @@
-<!--
-     Any copyright is dedicated to the Public Domain.
-     http://creativecommons.org/publicdomain/zero/1.0/
--->
-<svg xmlns="http://www.w3.org/2000/svg" width="700" height="200">
-  <text x="100" y="100" style="font: 16px sans-serif">hello <tspan style="text-shadow: 2px 2px 3px #000">there</tspan> everyone</text>
-</svg>
--- a/layout/reftests/svg/text/reftest.list
+++ b/layout/reftests/svg/text/reftest.list
@@ -122,25 +122,27 @@ HTTP(../..) == simple-transform-rotate.s
 
 == textLength.svg textLength-ref.svg
 == textLength-2.svg textLength-2-ref.svg
 fuzzy-if(/^Windows\x20NT\x206\.[12]/.test(http.oscpu),4,15) == textLength-3.svg textLength-3-ref.svg
 == textLength-4.svg textLength-4-ref.svg
 == textLength-5.svg textLength-5-ref.svg
 == textLength-6.svg textLength-6-ref.svg
 
+# text-shadow
+== text-shadow.svg text-shadow-ref.svg
+
 # tests for ignoring various properties
 == ignore-border.svg ignore-prop-ref.svg
 == ignore-display.svg ignore-display-ref.svg
 == ignore-float.svg ignore-prop-ref.svg
 == ignore-float-first-letter.svg ignore-prop-ref.svg
 == ignore-position.svg ignore-position-ref.svg
 == ignore-margin.svg ignore-prop-ref.svg
 == ignore-padding.svg ignore-prop-ref.svg
-== ignore-text-shadow.svg ignore-prop-ref.svg
 == ignore-vertical-align.svg ignore-vertical-align-ref.svg
 == ignore-overflow-scroll.svg ignore-prop-ref.svg
 == ignore-text-align.svg ignore-prop-ref.svg
 == ignore-text-align-2.svg ignore-text-align-2-ref.svg
 
 # pseudo-elements
 == pseudo-first-line.svg pseudo-first-line-ref.svg
 == pseudo-first-line-2.svg pseudo-first-line-2-ref.svg
@@ -158,16 +160,17 @@ fuzzy-if(/^Windows\x20NT\x206\.[12]/.tes
 # dynamic document changes
 == dynamic-font-size.svg dynamic-font-size-ref.svg
 == dynamic-font-size-2.svg dynamic-font-size-2-ref.svg
 == dynamic-font-size-3.svg dynamic-font-size-3-ref.svg
 == dynamic-font-size-4.svg dynamic-font-size-4-ref.svg
 == dynamic-dominant-baseline.svg dynamic-dominant-baseline-ref.svg
 == dynamic-multiple-x.svg dynamic-multiple-x-ref.svg
 fuzzy-if(!d2d,14,2) fuzzy-if(azureQuartz,1,6) == dynamic-non-scaling-stroke.svg dynamic-non-scaling-stroke-ref.svg #Bug 885316
+== dynamic-text-shadow.svg text-shadow-ref.svg
 
 # text and masks
 HTTP(../..) == mask-applied.svg mask-applied-ref.svg
 HTTP(../..) == mask-content.svg mask-content-ref.svg
 HTTP(../..) == mask-content-2.svg mask-content-2-ref.svg
 
 # text and clipPaths
 HTTP(../..) == clipPath-applied.svg clipPath-applied-ref.svg
new file mode 100644
--- /dev/null
+++ b/layout/reftests/svg/text/text-shadow-ref.svg
@@ -0,0 +1,8 @@
+<!--
+     Any copyright is dedicated to the Public Domain.
+     http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<svg xmlns="http://www.w3.org/2000/svg">
+  <text x="33" y="33" fill="grey">Hello</text>
+  <text x="30" y="30" fill="green">Hello</text>
+</svg>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/svg/text/text-shadow.svg
@@ -0,0 +1,7 @@
+<!--
+     Any copyright is dedicated to the Public Domain.
+     http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<svg xmlns="http://www.w3.org/2000/svg">
+  <text x="30" y="30" fill="green" style="text-shadow: grey 3px 3px;">Hello</text>
+</svg>
--- a/layout/style/nsStyleStruct.h
+++ b/layout/style/nsStyleStruct.h
@@ -1426,23 +1426,23 @@ struct nsStyleText {
   }
 
   bool WordCanWrapStyle() const {
     return WhiteSpaceCanWrapStyle() &&
            mWordWrap == NS_STYLE_WORDWRAP_BREAK_WORD;
   }
 
   // These are defined in nsStyleStructInlines.h.
+  inline bool HasTextShadow() const;
+  inline nsCSSShadowArray* GetTextShadow() const;
 
   // The aContextFrame argument on each of these is the frame this
   // style struct is for.  If the frame is for SVG text, the return
   // value will be massaged to be something that makes sense for
   // SVG text.
-  inline bool HasTextShadow(const nsIFrame* aContextFrame) const;
-  inline nsCSSShadowArray* GetTextShadow(const nsIFrame* aContextFrame) const;
   inline bool WhiteSpaceCanWrap(const nsIFrame* aContextFrame) const;
   inline bool WordCanWrap(const nsIFrame* aContextFrame) const;
 };
 
 struct nsStyleImageOrientation {
   static nsStyleImageOrientation CreateAsAngleAndFlip(double aRadians,
                                                       bool aFlip) {
     uint8_t orientation(0);
--- a/layout/style/nsStyleStructInlines.h
+++ b/layout/style/nsStyleStructInlines.h
@@ -52,29 +52,24 @@ nsStyleBorder::GetSubImage(uint8_t aInde
 {
   imgIContainer* subImage = nullptr;
   if (aIndex < mSubImages.Count())
     subImage = mSubImages[aIndex];
   return subImage;
 }
 
 bool
-nsStyleText::HasTextShadow(const nsIFrame* aContextFrame) const
+nsStyleText::HasTextShadow() const
 {
-  NS_ASSERTION(aContextFrame->StyleText() == this, "unexpected aContextFrame");
-  return mTextShadow && !aContextFrame->IsSVGText();
+  return mTextShadow;
 }
 
 nsCSSShadowArray*
-nsStyleText::GetTextShadow(const nsIFrame* aContextFrame) const
+nsStyleText::GetTextShadow() const
 {
-  NS_ASSERTION(aContextFrame->StyleText() == this, "unexpected aContextFrame");
-  if (aContextFrame->IsSVGText()) {
-    return nullptr;
-  }
   return mTextShadow;
 }
 
 bool
 nsStyleText::WhiteSpaceCanWrap(const nsIFrame* aContextFrame) const
 {
   NS_ASSERTION(aContextFrame->StyleText() == this, "unexpected aContextFrame");
   return WhiteSpaceCanWrapStyle() && !aContextFrame->IsSVGText();
--- a/layout/svg/SVGTextFrame.cpp
+++ b/layout/svg/SVGTextFrame.cpp
@@ -627,18 +627,20 @@ struct TextRenderedRun
    * Flag values used for the aFlags arguments of GetRunUserSpaceRect,
    * GetFrameUserSpaceRect and GetUserSpaceRect.
    */
   enum {
     // Includes the fill geometry of the text in the returned rectangle.
     eIncludeFill = 1,
     // Includes the stroke geometry of the text in the returned rectangle.
     eIncludeStroke = 2,
+    // Includes any text shadow in the returned rectangle.
+    eIncludeTextShadow = 4,
     // Don't include any horizontal glyph overflow in the returned rectangle.
-    eNoHorizontalOverflow = 4
+    eNoHorizontalOverflow = 8
   };
 
   /**
    * Returns a rectangle that bounds the fill and/or stroke of the rendered run
    * in run user space.
    *
    * @param aContext The context to use for unit conversions.
    * @param aFlags A combination of the flags above (eIncludeFill and
@@ -905,21 +907,31 @@ TextRenderedRun::GetRunUserSpaceRect(nsP
   gfxFloat x, width;
   if (aFlags & eNoHorizontalOverflow) {
     x = 0.0;
     width = textRun->GetAdvanceWidth(offset, length, nullptr);
   } else {
     x = metrics.mBoundingBox.x;
     width = metrics.mBoundingBox.width;
   }
-  gfxRect fillInAppUnits(x, baseline - above,
-                         width, metrics.mBoundingBox.height + above + below);
+  nsRect fillInAppUnits(x, baseline - above,
+                        width, metrics.mBoundingBox.height + above + below);
+
+  // Account for text-shadow.
+  if (aFlags & eIncludeTextShadow) {
+    fillInAppUnits =
+      nsLayoutUtils::GetTextShadowRectsUnion(fillInAppUnits, mFrame);
+  }
 
   // Convert the app units rectangle to user units.
-  gfxRect fill = AppUnitsToFloatCSSPixels(fillInAppUnits, aContext);
+  gfxRect fill = AppUnitsToFloatCSSPixels(gfxRect(fillInAppUnits.x,
+                                                  fillInAppUnits.y,
+                                                  fillInAppUnits.width,
+                                                  fillInAppUnits.height),
+                                          aContext);
 
   // Scale the rectangle up due to any mFontSizeScaleFactor.  We scale
   // it around the text's origin.
   ScaleAround(fill,
               gfxPoint(0.0, aContext->AppUnitsToFloatCSSPixels(baseline)),
               1.0 / mFontSizeScaleFactor);
 
   // Include the fill if requested.
@@ -3733,24 +3745,35 @@ SVGTextFrame::ReflowSVG()
   UpdateGlyphPositioning();
 
   nsPresContext* presContext = PresContext();
 
   SVGBBox r;
   TextRenderedRunIterator it(this, TextRenderedRunIterator::eAllFrames);
   for (TextRenderedRun run = it.Current(); run.mFrame; run = it.Next()) {
     uint32_t runFlags = 0;
+    if (run.mFrame->StyleSVG()->mFill.mType != eStyleSVGPaintType_None) {
+      runFlags |= TextRenderedRun::eIncludeFill |
+                  TextRenderedRun::eIncludeTextShadow;
+    }
+    if (nsSVGUtils::HasStroke(run.mFrame)) {
+      runFlags |= TextRenderedRun::eIncludeFill |
+                  TextRenderedRun::eIncludeTextShadow;
+    }
+    // Our "visual" overflow rect needs to be valid for building display lists
+    // for hit testing, which means that for certain values of 'pointer-events'
+    // it needs to include the geometry of the fill or stroke even when the fill/
+    // stroke don't actually render (e.g. when stroke="none" or
+    // stroke-opacity="0"). GetGeometryHitTestFlags accounts for 'pointer-events'.
+    // The text-shadow is not part of the hit-test area.
     uint16_t hitTestFlags = nsSVGUtils::GetGeometryHitTestFlags(run.mFrame);
-
-    if ((hitTestFlags & SVG_HIT_TEST_FILL) ||
-        run.mFrame->StyleSVG()->mFill.mType != eStyleSVGPaintType_None) {
+    if (hitTestFlags & SVG_HIT_TEST_FILL) {
       runFlags |= TextRenderedRun::eIncludeFill;
     }
-    if ((hitTestFlags & SVG_HIT_TEST_STROKE) ||
-        nsSVGUtils::HasStroke(run.mFrame)) {
+    if (hitTestFlags & SVG_HIT_TEST_STROKE) {
       runFlags |= TextRenderedRun::eIncludeStroke;
     }
 
     if (runFlags) {
       r.UnionEdges(run.GetUserSpaceRect(presContext, runFlags));
     }
   }