Bug 1440014: Part 1: Implemented rendering for text-decoration-width CSS property r=jfkthame
authorCharlie Marlow <cmarlow@mozilla.com>
Wed, 26 Jun 2019 21:21:57 +0000
changeset 543116 d82e7b564cecad0fb4c0c0ad15b648fca5b06ab0
parent 543115 2150f683f8a80e76c1da36440249f3cc423cccbe
child 543117 40986c1d78aa9a4cd6c9cf418f02428e5909690e
push id2131
push userffxbld-merge
push dateMon, 26 Aug 2019 18:30:20 +0000
treeherdermozilla-release@b19ffb3ca153 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjfkthame
bugs1440014
milestone69.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 1440014: Part 1: Implemented rendering for text-decoration-width CSS property r=jfkthame reftests will be added later Differential Revision: https://phabricator.services.mozilla.com/D34238
layout/generic/nsTextFrame.cpp
layout/generic/nsTextFrame.h
layout/painting/nsCSSRendering.cpp
layout/painting/nsCSSRendering.h
--- a/layout/generic/nsTextFrame.cpp
+++ b/layout/generic/nsTextFrame.cpp
@@ -5113,25 +5113,28 @@ void nsTextFrame::GetTextDecorations(
       const auto kOverline = swapUnderlineAndOverline
                                  ? StyleTextDecorationLine_UNDERLINE
                                  : StyleTextDecorationLine_OVERLINE;
 
       const nsStyleText* const styleText = context->StyleText();
 
       if (textDecorations & kUnderline) {
         aDecorations.mUnderlines.AppendElement(nsTextFrame::LineDecoration(
-            f, baselineOffset, styleText->mTextUnderlineOffset, color, style));
+            f, baselineOffset, styleText->mTextUnderlineOffset,
+            styleTextReset->mTextDecorationWidth, color, style));
       }
       if (textDecorations & kOverline) {
         aDecorations.mOverlines.AppendElement(nsTextFrame::LineDecoration(
-            f, baselineOffset, styleText->mTextUnderlineOffset, color, style));
+            f, baselineOffset, styleText->mTextUnderlineOffset,
+            styleTextReset->mTextDecorationWidth, color, style));
       }
       if (textDecorations & StyleTextDecorationLine_LINE_THROUGH) {
         aDecorations.mStrikes.AppendElement(nsTextFrame::LineDecoration(
-            f, baselineOffset, styleText->mTextUnderlineOffset, color, style));
+            f, baselineOffset, styleText->mTextUnderlineOffset,
+            styleTextReset->mTextDecorationWidth, color, style));
       }
     }
 
     // In all modes, if we're on an inline-block or inline-table (or
     // inline-stack, inline-box, inline-grid), we're done.
     // If we're on a ruby frame other than ruby text container, we
     // should continue.
     mozilla::StyleDisplay display = f->GetDisplay();
@@ -5269,21 +5272,20 @@ nsRect nsTextFrame::UpdateTextEmphasis(W
     nscoord gap = (baseFontMetrics->MaxHeight() - frameSize.BSize(aWM)) / 2;
     overflowRect.BStart(aWM) += gap * (side == eLogicalSideBStart ? -1 : 1);
   }
 
   SetProperty(EmphasisMarkProperty(), info);
   return overflowRect.GetPhysicalRect(aWM, frameSize.GetPhysicalSize(aWM));
 }
 
-static void SetParamIfLength(const LengthOrAuto& aTextOffset,
-                             Float* aParamOffset,
+static void SetParamIfLength(const LengthOrAuto& aLengthOrAuto, Float* aParam,
                              const gfxFloat aAppUnitsPerDevPixel) {
-  if (aTextOffset.IsLength()) {
-    *aParamOffset = aTextOffset.AsLength().ToAppUnits() / aAppUnitsPerDevPixel;
+  if (aLengthOrAuto.IsLength()) {
+    *aParam = aLengthOrAuto.AsLength().ToAppUnits() / aAppUnitsPerDevPixel;
   }
 }
 
 void nsTextFrame::UnionAdditionalOverflow(nsPresContext* aPresContext,
                                           nsIFrame* aBlock,
                                           PropertyProvider& aProvider,
                                           nsRect* aVisualOverflowRect,
                                           bool aIncludeTextDecorations,
@@ -5300,31 +5302,39 @@ void nsTextFrame::UnionAdditionalOverflo
     uint8_t decorationStyle =
         aBlock->Style()->StyleTextReset()->mTextDecorationStyle;
     // If the style is none, let's include decoration line rect as solid style
     // since changing the style from none to solid/dotted/dashed doesn't cause
     // reflow.
     if (decorationStyle == NS_STYLE_TEXT_DECORATION_STYLE_NONE) {
       decorationStyle = NS_STYLE_TEXT_DECORATION_STYLE_SOLID;
     }
+    nsCSSRendering::DecorationRectParams params;
     nsFontMetrics* fontMetrics = aProvider.GetFontMetrics();
     nscoord underlineOffset, underlineSize;
     fontMetrics->GetUnderline(underlineOffset, underlineSize);
 
     const LengthOrAuto& textUnderlineOffset =
         aBlock->Style()->StyleText()->mTextUnderlineOffset;
 
+    const LengthOrAuto& textDecorationWidth =
+        aBlock->Style()->StyleTextReset()->mTextDecorationWidth;
+
     if (textUnderlineOffset.IsLength()) {
       underlineOffset = textUnderlineOffset.AsLength().ToAppUnits();
     }
 
+    params.defaultLineThickness = underlineSize;
+    if (textDecorationWidth.IsLength()) {
+      underlineSize = textDecorationWidth.AsLength().ToAppUnits();
+    }
+
     nscoord maxAscent =
         inverted ? fontMetrics->MaxDescent() : fontMetrics->MaxAscent();
 
-    nsCSSRendering::DecorationRectParams params;
     Float gfxWidth = (verticalRun ? aVisualOverflowRect->height
                                   : aVisualOverflowRect->width) /
                      appUnitsPerDevUnit;
     params.lineSize = Size(gfxWidth, underlineSize / appUnitsPerDevUnit);
     params.ascent = gfxFloat(mAscent) / appUnitsPerDevUnit;
     params.style = decorationStyle;
     params.vertical = verticalRun;
     params.sidewaysLeft = mTextRun->IsSidewaysLeft();
@@ -5401,19 +5411,23 @@ void nsTextFrame::UnionAdditionalOverflo
                                     useVerticalMetrics);
 
             params.lineSize.height = metrics.*lineSize;
 
             params.offset = metrics.*lineOffset;
 
             if (lineType == StyleTextDecorationLine_UNDERLINE) {
               SetParamIfLength(dec.mTextUnderlineOffset, &params.offset,
-                               aPresContext->AppUnitsPerDevPixel());
+                               appUnitsPerDevUnit);
             }
 
+            params.defaultLineThickness = params.lineSize.height;
+            SetParamIfLength(dec.mTextDecorationWidth, &params.lineSize.height,
+                             appUnitsPerDevUnit);
+
             const nsRect decorationRect =
                 nsCSSRendering::GetTextDecorationRect(aPresContext, params) +
                 (verticalDec ? nsPoint(frameBStart - dec.mBaselineOffset, 0)
                              : nsPoint(0, -dec.mBaselineOffset));
 
             if (verticalDec) {
               topOrLeft = std::min(decorationRect.x, topOrLeft);
               bottomOrRight = std::max(decorationRect.XMost(), bottomOrRight);
@@ -5584,44 +5598,50 @@ void nsTextFrame::DrawSelectionDecoratio
   params.dirtyRect = aDirtyRect;
   params.pt = aPt;
   params.lineSize.width = aWidth;
   params.ascent = aAscent;
 
   if (aDecoration == StyleTextDecorationLine_UNDERLINE) {
     params.offset = aFontMetrics.underlineOffset;
     SetParamIfLength(StyleText()->mTextUnderlineOffset, &params.offset,
-                     PresContext()->AppUnitsPerDevPixel());
+                     aTextPaintStyle.PresContext()->AppUnitsPerDevPixel());
   } else {
     params.offset = aFontMetrics.maxAscent;
   }
 
   params.decoration = aDecoration;
   params.decorationType = DecorationType::Selection;
   params.callbacks = aCallbacks;
   params.vertical = aVertical;
   params.sidewaysLeft = mTextRun->IsSidewaysLeft();
   params.descentLimit = ComputeDescentLimitForSelectionUnderline(
       aTextPaintStyle.PresContext(), aFontMetrics);
 
   float relativeSize;
+  const LengthOrAuto& decWidth = StyleTextReset()->mTextDecorationWidth;
+  const gfxFloat appUnitsPerDevPixel =
+      aTextPaintStyle.PresContext()->AppUnitsPerDevPixel();
 
   switch (aSelectionType) {
     case SelectionType::eIMERawClause:
     case SelectionType::eIMESelectedRawClause:
     case SelectionType::eIMEConvertedClause:
     case SelectionType::eIMESelectedClause:
     case SelectionType::eSpellCheck: {
       int32_t index = nsTextPaintStyle::GetUnderlineStyleIndexForSelectionType(
           aSelectionType);
       bool weDefineSelectionUnderline =
           aTextPaintStyle.GetSelectionUnderlineForPaint(
               index, &params.color, &relativeSize, &params.style);
-      params.lineSize.height = ComputeSelectionUnderlineHeight(
+      params.defaultLineThickness = ComputeSelectionUnderlineHeight(
           aTextPaintStyle.PresContext(), aFontMetrics, aSelectionType);
+      params.lineSize.height = params.defaultLineThickness;
+      SetParamIfLength(decWidth, &params.lineSize.height, appUnitsPerDevPixel);
+
       bool isIMEType = aSelectionType != SelectionType::eSpellCheck;
 
       if (isIMEType) {
         // IME decoration lines should not be drawn on the both ends, i.e., we
         // need to cut both edges of the decoration lines.  Because same style
         // IME selections can adjoin, but the users need to be able to know
         // where are the boundaries of the selections.
         //
@@ -5683,25 +5703,28 @@ void nsTextFrame::DrawSelectionDecoratio
           GetInflationForTextDecorations(this, inflationMinFontSize);
       const gfxFont::Metrics metrics =
           GetFirstFontMetrics(GetFontGroupForFrame(this, inflation), aVertical);
 
       relativeSize = 2.0f;
       aTextPaintStyle.GetURLSecondaryColor(&params.color);
       params.style = NS_STYLE_TEXT_DECORATION_STYLE_SOLID;
       params.lineSize.height = metrics.strikeoutSize;
+      params.defaultLineThickness = params.lineSize.height;
+      SetParamIfLength(decWidth, &params.lineSize.height, appUnitsPerDevPixel);
       params.offset = metrics.strikeoutOffset + 0.5;
       params.decoration = StyleTextDecorationLine_LINE_THROUGH;
       break;
     }
     default:
       NS_WARNING("Requested selection decorations when there aren't any");
       return;
   }
   params.lineSize.height *= relativeSize;
+  params.defaultLineThickness *= relativeSize;
   params.icoordInFrame =
       (aVertical ? params.pt.y - aPt.y : params.pt.x - aPt.x) + aICoordInFrame;
   PaintDecorationLine(params);
 }
 
 /* static */
 bool nsTextFrame::GetSelectionTextColors(SelectionType aSelectionType,
                                          nsTextPaintStyle& aTextPaintStyle,
@@ -6876,17 +6899,19 @@ void nsTextFrame::DrawTextRunAndDecorati
 
     params.color = dec.mColor;
 
     params.offset = metrics.*lineOffset;
     if (lineType == StyleTextDecorationLine_UNDERLINE) {
       SetParamIfLength(dec.mTextUnderlineOffset, &params.offset,
                        PresContext()->AppUnitsPerDevPixel());
     }
-
+    params.defaultLineThickness = params.lineSize.height;
+    SetParamIfLength(dec.mTextDecorationWidth, &params.lineSize.height,
+                     PresContext()->AppUnitsPerDevPixel());
     params.style = dec.mStyle;
     PaintDecorationLine(params);
   };
 
   // We create a clip region in order to draw the decoration lines only in the
   // range of the text. Restricting the draw area prevents the decoration lines
   // to be drawn multiple times when a part of the text is selected.
 
@@ -7220,21 +7245,26 @@ bool nsTextFrame::CombineSelectionUnderl
       } else if (!nsTextPaintStyle::GetSelectionUnderline(
                      aPresContext, index, nullptr, &relativeSize,
                      &params.style)) {
         continue;
       }
     }
     nsRect decorationArea;
 
-    params.lineSize = Size(aPresContext->AppUnitsToGfxUnits(aRect.width),
-                           ComputeSelectionUnderlineHeight(
-                               aPresContext, metrics, sd->mSelectionType));
+    const LengthOrAuto& decWidth = StyleTextReset()->mTextDecorationWidth;
+    params.lineSize.width = aPresContext->AppUnitsToGfxUnits(aRect.width);
+    params.defaultLineThickness = ComputeSelectionUnderlineHeight(
+        aPresContext, metrics, sd->mSelectionType);
+    SetParamIfLength(decWidth, &params.lineSize.height,
+                     aPresContext->AppUnitsPerDevPixel());
+
     relativeSize = std::max(relativeSize, 1.0f);
     params.lineSize.height *= relativeSize;
+    params.defaultLineThickness *= relativeSize;
     decorationArea =
         nsCSSRendering::GetTextDecorationRect(aPresContext, params);
     aRect.UnionRect(aRect, decorationArea);
   }
 
   return !aRect.IsEmpty() && !givenRect.Contains(aRect);
 }
 
--- a/layout/generic/nsTextFrame.h
+++ b/layout/generic/nsTextFrame.h
@@ -694,40 +694,47 @@ class nsTextFrame : public nsFrame {
 
     // This is represents the offset from our baseline to mFrame's baseline;
     // positive offsets are *above* the baseline and negative offsets below
     nscoord mBaselineOffset;
 
     // This represents the offset from the initial position of the underline
     const mozilla::LengthOrAuto mTextUnderlineOffset;
 
+    // for CSS property text-decoration-width, the width refers to the thickness
+    // of the decoration line
+    const mozilla::LengthOrAuto mTextDecorationWidth;
     nscolor mColor;
     uint8_t mStyle;
 
     LineDecoration(nsIFrame* const aFrame, const nscoord aOff,
                    const mozilla::LengthOrAuto& aUnderline,
-                   const nscolor aColor, const uint8_t aStyle)
+                   const mozilla::LengthOrAuto& aDecWidth, const nscolor aColor,
+                   const uint8_t aStyle)
         : mFrame(aFrame),
           mBaselineOffset(aOff),
           mTextUnderlineOffset(aUnderline),
+          mTextDecorationWidth(aDecWidth),
           mColor(aColor),
           mStyle(aStyle) {}
 
     LineDecoration(const LineDecoration& aOther)
         : mFrame(aOther.mFrame),
           mBaselineOffset(aOther.mBaselineOffset),
           mTextUnderlineOffset(aOther.mTextUnderlineOffset),
+          mTextDecorationWidth(aOther.mTextDecorationWidth),
           mColor(aOther.mColor),
           mStyle(aOther.mStyle) {}
 
     bool operator==(const LineDecoration& aOther) const {
       return mFrame == aOther.mFrame && mStyle == aOther.mStyle &&
              mColor == aOther.mColor &&
              mBaselineOffset == aOther.mBaselineOffset &&
-             mTextUnderlineOffset == aOther.mTextUnderlineOffset;
+             mTextUnderlineOffset == aOther.mTextUnderlineOffset &&
+             mTextDecorationWidth == aOther.mTextDecorationWidth;
     }
 
     bool operator!=(const LineDecoration& aOther) const {
       return !(*this == aOther);
     }
   };
   struct TextDecorations {
     AutoTArray<LineDecoration, 1> mOverlines, mUnderlines, mStrikes;
--- a/layout/painting/nsCSSRendering.cpp
+++ b/layout/painting/nsCSSRendering.cpp
@@ -4057,16 +4057,18 @@ gfxRect nsCSSRendering::GetTextDecoratio
                  right = floor(iCoord + aParams.lineSize.width + 0.5);
 
   // We compute |r| as if for a horizontal text run, and then swap vertical
   // and horizontal coordinates at the end if vertical was requested.
   gfxRect r(left, 0, right - left, 0);
 
   gfxFloat lineThickness = NS_round(aParams.lineSize.height);
   lineThickness = std::max(lineThickness, 1.0);
+  gfxFloat defaultLineThickness = NS_round(aParams.defaultLineThickness);
+  defaultLineThickness = std::max(defaultLineThickness, 1.0);
 
   gfxFloat ascent = NS_round(aParams.ascent);
   gfxFloat descentLimit = floor(aParams.descentLimit);
 
   gfxFloat suggestedMaxRectHeight =
       std::max(std::min(ascent, descentLimit), 1.0);
   r.height = lineThickness;
   if (aParams.style == NS_STYLE_TEXT_DECORATION_STYLE_DOUBLE) {
@@ -4149,28 +4151,32 @@ gfxRect nsCSSRendering::GetTextDecoratio
         // possible.  Otherwise, we should lift up the top edge of the rect as
         // far as possible.
         gfxFloat offsetBottomAligned = -descentLimit + r.Height();
         gfxFloat offsetTopAligned = 0.0;
         offset = std::min(offsetBottomAligned, offsetTopAligned);
       }
     }
   } else if (aParams.decoration == StyleTextDecorationLine_OVERLINE) {
-    // For overline, we adjust the offset by lineThickness (the thickness of
-    // a single decoration line) because empirically it looks better to draw
-    // the overline just inside rather than outside the font's ascent, which
-    // is what nsTextFrame passes as aParams.offset (as fonts don't provide
-    // an explicit overline-offset).
-    offset = aParams.offset - lineThickness + r.Height();
+    // For overline, we adjust the offset by defaultlineThickness (the default
+    // thickness of a single decoration line) because empirically it looks
+    // better to draw the overline just inside rather than outside the font's
+    // ascent, which is what nsTextFrame passes as aParams.offset (as fonts
+    // don't provide an explicit overline-offset).
+    offset = aParams.offset - defaultLineThickness + r.Height();
   } else if (aParams.decoration == StyleTextDecorationLine_LINE_THROUGH) {
     // To maintain a consistent mid-point for line-through decorations,
     // we adjust the offset by half of the decoration rect's height.
     gfxFloat extra = floor(r.Height() / 2.0 + 0.5);
     extra = std::max(extra, lineThickness);
-    offset = aParams.offset - lineThickness + extra;
+    // computes offset for when user specifies a decoration width since
+    // aParams.offset is derived from the font metric's line height
+    gfxFloat decorationWidthOffset =
+        (lineThickness - defaultLineThickness) / 2.0;
+    offset = aParams.offset - lineThickness + extra + decorationWidthOffset;
   } else {
     MOZ_ASSERT_UNREACHABLE("Invalid text decoration value");
   }
 
   // Convert line-relative coordinate system (x = line-right, y = line-up)
   // to physical coords, and move the decoration rect to the calculated
   // offset from baseline.
   if (aParams.vertical) {
--- a/layout/painting/nsCSSRendering.h
+++ b/layout/painting/nsCSSRendering.h
@@ -554,16 +554,22 @@ struct nsCSSRendering {
   // NOTE: pt, dirtyRect, lineSize, ascent, offset in the following
   //       structs are non-rounded device pixels, not app units.
   struct DecorationRectParams {
     // The width [length] and the height [thickness] of the decoration
     // line. This is a "logical" size in textRun orientation, so that
     // for a vertical textrun, width will actually be a physical height;
     // and conversely, height will be a physical width.
     Size lineSize;
+    // The default height [thickness] of the line given by the font metrics.
+    // This is used for obtaining the correct offset for the decoration line
+    // when CSS specifies a unique thickness for a text-decoration,
+    // since the offset given by the font is derived from the font metric's
+    // assumed line height
+    Float defaultLineThickness = 0.0f;
     // The ascent of the text.
     Float ascent = 0.0f;
     // The offset of the decoration line from the baseline of the text
     // (if the value is positive, the line is lifted up).
     Float offset = 0.0f;
     // If descentLimit is zero or larger and the underline overflows
     // from the descent space, the underline should be lifted up as far
     // as possible.  Note that this does not mean the underline never