Bug 594933 - Part 2: Add support for calc() to line-height. r=dbaron
authorMorris Tseng <mtseng@mozilla.com>
Tue, 15 Mar 2016 10:37:37 +0800
changeset 340397 2f3cc8818587cc3b25d196cb61a2063f37227ab9
parent 340396 8e35ad3b586f7aabdfce8050893a45a8c47f518c
child 340398 df2429673c945e1fe476c8056547cca787e727d7
push id12961
push userbmo:mh+mozilla@glandium.org
push dateTue, 15 Mar 2016 07:44:03 +0000
reviewersdbaron
bugs594933
milestone48.0a1
Bug 594933 - Part 2: Add support for calc() to line-height. r=dbaron
layout/reftests/css-calc/line-height-1-ref.html
layout/reftests/css-calc/line-height-1.html
layout/reftests/css-calc/reftest.list
layout/style/nsCSSParser.cpp
layout/style/nsCSSPropList.h
layout/style/nsRuleNode.cpp
layout/style/test/property_database.js
new file mode 100644
--- /dev/null
+++ b/layout/reftests/css-calc/line-height-1-ref.html
@@ -0,0 +1,62 @@
+<!DOCTYPE html>
+<style>
+div {
+ width:100px;
+ height:600px;
+ margin:5px 0 0 5px;
+ font-size: 30px;
+ float:left;
+}
+div#one {
+ line-height: 300%;
+}
+div#two {
+ line-height: 100px;
+}
+div#three {
+ line-height: 110px;
+}
+div#four {
+ line-height: 20px;
+}
+div#five {
+ line-height: 12px;
+}
+div#six {
+ line-height: 12px;
+}
+div#seven {
+ line-height: 12px;
+}
+div#eight {
+ line-height: 12px;
+}
+div#nine {
+ line-height: 12px;
+}
+div#nine {
+ line-height: 12px;
+}
+div#ten {
+ line-height: 12px;
+}
+div#div-11 {
+ line-height: 15px;
+}
+div#div-12 {
+ line-height: 195px;
+}
+</style>
+<div id="one">line height is 300%</div>
+<div id="two">line height is 100px</div>
+<div id="three">line height is 50px</div>
+<div id="four">line height is 10px * 2</div>
+<div id="five">line height is 50% - 3px</div>
+<div id="six">line height is 25% - 3px + 25%</div>
+<div id="seven">line height is 25% - 3px + 12.5% * 2</div>
+<div id="eight">line height is 25% - 3px + 12.5%*2</div>
+<div id="nine">line height is 25% - 3px + 2*12.5%</div>
+<div id="ten">line height is 25% - 3px + 2 * 12.5%</div>
+<div id="div-11">line height is 30% + 20%</div>
+<div id="div-12">line height is 3 * 2 + 3 / 6</div>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/css-calc/line-height-1.html
@@ -0,0 +1,62 @@
+<!DOCTYPE html>
+<style>
+div {
+ width:100px;
+ height:600px;
+ margin:5px 0 0 5px;
+ font-size: 30px;
+ float:left;
+}
+div#one {
+ line-height: calc(300%);
+}
+div#two {
+ line-height: calc(100px);
+}
+div#three {
+ line-height: calc(20px + 300%); 
+}
+div#four {
+ line-height: calc(10px * 2);
+}
+div#five {
+ line-height: calc(50% - 3px);
+}
+div#six {
+ line-height: calc(25% - 3px + 25%);
+}
+div#seven {
+ line-height: calc(25% - 3px + 12.5% * 2);
+}
+div#eight {
+ line-height: calc(25% - 3px + 12.5%*2);
+}
+div#nine {
+ line-height: calc(25% - 3px + 2 * 12.5%);
+}
+div#nine {
+ line-height: calc(25% - 3px + 2*12.5%);
+}
+div#ten {
+ line-height: calc(25% - 3px + 2 * 12.5%);
+}
+div#div-11 {
+ line-height: calc(30% + 20%);
+}
+div#div-12 {
+ line-height: calc(3 * 2 + 3 / 6);
+}
+</style>
+<div id="one">line height is 300%</div>
+<div id="two">line height is 100px</div>
+<div id="three">line height is 50px</div>
+<div id="four">line height is 10px * 2</div>
+<div id="five">line height is 50% - 3px</div>
+<div id="six">line height is 25% - 3px + 25%</div>
+<div id="seven">line height is 25% - 3px + 12.5% * 2</div>
+<div id="eight">line height is 25% - 3px + 12.5%*2</div>
+<div id="nine">line height is 25% - 3px + 2*12.5%</div>
+<div id="ten">line height is 25% - 3px + 2 * 12.5%</div>
+<div id="div-11">line height is 30% + 20%</div>
+<div id="div-12">line height is 3 * 2 + 3 / 6</div>
+</html>
--- a/layout/reftests/css-calc/reftest.list
+++ b/layout/reftests/css-calc/reftest.list
@@ -1,1 +1,2 @@
 == background-image-gradient-1.html background-image-gradient-1-ref.html
+== line-height-1.html line-height-1-ref.html
--- a/layout/style/nsCSSParser.cpp
+++ b/layout/style/nsCSSParser.cpp
@@ -13554,20 +13554,20 @@ CSSParserImpl::ParseFont()
   if (!ParseSingleTokenNonNegativeVariant(size, VARIANT_KEYWORD | VARIANT_LP,
                                           nsCSSProps::kFontSizeKTable)) {
     return false;
   }
 
   // Get optional "/" line-height
   nsCSSValue  lineHeight;
   if (ExpectSymbol('/', true)) {
-    if (!ParseSingleTokenNonNegativeVariant(lineHeight,
-                                            VARIANT_NUMBER | VARIANT_LP |
-                                              VARIANT_NORMAL,
-                                            nullptr)) {
+    if (ParseNonNegativeVariant(lineHeight,
+                                VARIANT_NUMBER | VARIANT_LP |
+                                  VARIANT_NORMAL | VARIANT_CALC,
+                                nullptr) != CSSParseResult::Ok) {
       return false;
     }
   }
   else {
     lineHeight.SetNormalValue();
   }
 
   // Get final mandatory font-family
--- a/layout/style/nsCSSPropList.h
+++ b/layout/style/nsCSSPropList.h
@@ -2267,17 +2267,17 @@ CSS_PROP_TEXT(
     line_height,
     LineHeight,
     CSS_PROPERTY_PARSE_VALUE |
         CSS_PROPERTY_VALUE_NONNEGATIVE |
         CSS_PROPERTY_APPLIES_TO_FIRST_LETTER_AND_FIRST_LINE |
         CSS_PROPERTY_APPLIES_TO_PLACEHOLDER |
         CSS_PROPERTY_GETCS_NEEDS_LAYOUT_FLUSH,
     "",
-    VARIANT_HLPN | VARIANT_KEYWORD | VARIANT_NORMAL | VARIANT_SYSFONT,
+    VARIANT_HLPN | VARIANT_KEYWORD | VARIANT_NORMAL | VARIANT_SYSFONT | VARIANT_CALC,
     kLineHeightKTable,
     offsetof(nsStyleText, mLineHeight),
     eStyleAnimType_Coord)
 CSS_PROP_SHORTHAND(
     list-style,
     list_style,
     ListStyle,
     CSS_PROPERTY_PARSE_FUNCTION,
--- a/layout/style/nsRuleNode.cpp
+++ b/layout/style/nsRuleNode.cpp
@@ -4267,16 +4267,111 @@ TruncateStringToSingleGrapheme(nsAString
       // Not mutating the string for common cases helps memory use
       // since we share the buffer from the specified style into the
       // computed style.
       aStr.Truncate(iter - aStr.Data());
     }
   }
 }
 
+struct LineHeightCalcObj
+{
+  float mLineHeight;
+  bool mIsNumber;
+};
+
+struct SetLineHeightCalcOps : public css::NumbersAlreadyNormalizedOps
+{
+  typedef LineHeightCalcObj result_type;
+  nsStyleContext* const mStyleContext;
+  nsPresContext* const mPresContext;
+  RuleNodeCacheConditions& mConditions;
+
+  SetLineHeightCalcOps(nsStyleContext* aStyleContext,
+                       nsPresContext* aPresContext,
+                       RuleNodeCacheConditions& aConditions)
+    : mStyleContext(aStyleContext),
+      mPresContext(aPresContext),
+      mConditions(aConditions)
+  {
+  }
+
+  result_type
+  MergeAdditive(nsCSSUnit aCalcFunction,
+                result_type aValue1, result_type aValue2)
+  {
+    MOZ_ASSERT(aValue1.mIsNumber == aValue2.mIsNumber);
+
+    LineHeightCalcObj result;
+    result.mIsNumber = aValue1.mIsNumber;
+    if (aCalcFunction == eCSSUnit_Calc_Plus) {
+      result.mLineHeight = aValue1.mLineHeight + aValue2.mLineHeight;
+      return result;
+    }
+    MOZ_ASSERT(aCalcFunction == eCSSUnit_Calc_Minus,
+               "unexpected unit");
+    result.mLineHeight = aValue1.mLineHeight - aValue2.mLineHeight;
+    return result;
+  }
+
+  result_type
+  MergeMultiplicativeL(nsCSSUnit aCalcFunction,
+                       float aValue1, result_type aValue2)
+  {
+    MOZ_ASSERT(aCalcFunction == eCSSUnit_Calc_Times_L,
+               "unexpected unit");
+    LineHeightCalcObj result;
+    result.mIsNumber = aValue2.mIsNumber;
+    result.mLineHeight = aValue1 * aValue2.mLineHeight;
+    return result;
+  }
+
+  result_type
+  MergeMultiplicativeR(nsCSSUnit aCalcFunction,
+                       result_type aValue1, float aValue2)
+  {
+    LineHeightCalcObj result;
+    result.mIsNumber = aValue1.mIsNumber;
+    if (aCalcFunction == eCSSUnit_Calc_Times_R) {
+      result.mLineHeight = aValue1.mLineHeight * aValue2;
+      return result;
+    }
+    MOZ_ASSERT(aCalcFunction == eCSSUnit_Calc_Divided,
+               "unexpected unit");
+    result.mLineHeight = aValue1.mLineHeight / aValue2;
+    return result;
+  }
+
+  result_type ComputeLeafValue(const nsCSSValue& aValue)
+  {
+    LineHeightCalcObj result;
+    if (aValue.IsLengthUnit()) {
+      result.mIsNumber = false;
+      result.mLineHeight = CalcLength(aValue, mStyleContext,
+                                      mPresContext, mConditions);
+    }
+    else if (eCSSUnit_Percent == aValue.GetUnit()) {
+      mConditions.SetUncacheable();
+      result.mIsNumber = false;
+      nscoord fontSize = mStyleContext->StyleFont()->mFont.size;
+      result.mLineHeight = fontSize * aValue.GetPercentValue();
+    }
+    else if (eCSSUnit_Number == aValue.GetUnit()) {
+      result.mIsNumber = true;
+      result.mLineHeight = aValue.GetFloatValue();
+    } else {
+      MOZ_ASSERT(false, "unexpected value");
+      result.mIsNumber = true;
+      result.mLineHeight = 1.0f;
+    }
+
+    return result;
+  }
+};
+
 const void*
 nsRuleNode::ComputeTextData(void* aStartStruct,
                             const nsRuleData* aRuleData,
                             nsStyleContext* aContext,
                             nsRuleNode* aHighestNode,
                             const RuleDetail aRuleDetail,
                             const RuleNodeCacheConditions aConditions)
 {
@@ -4309,29 +4404,39 @@ nsRuleNode::ComputeTextData(void* aStart
     } else if (textShadowValue->GetUnit() == eCSSUnit_List ||
                textShadowValue->GetUnit() == eCSSUnit_ListDep) {
       // List of arrays
       text->mTextShadow = GetShadowData(textShadowValue->GetListValue(),
                                         aContext, false, conditions);
     }
   }
 
-  // line-height: normal, number, length, percent, inherit
+  // line-height: normal, number, length, percent, calc, inherit
   const nsCSSValue* lineHeightValue = aRuleData->ValueForLineHeight();
   if (eCSSUnit_Percent == lineHeightValue->GetUnit()) {
     conditions.SetUncacheable();
     // Use |mFont.size| to pick up minimum font size.
     text->mLineHeight.SetCoordValue(
         NSToCoordRound(float(aContext->StyleFont()->mFont.size) *
                        lineHeightValue->GetPercentValue()));
   }
   else if (eCSSUnit_Initial == lineHeightValue->GetUnit() ||
            eCSSUnit_System_Font == lineHeightValue->GetUnit()) {
     text->mLineHeight.SetNormalValue();
   }
+  else if (eCSSUnit_Calc == lineHeightValue->GetUnit()) {
+    SetLineHeightCalcOps ops(aContext, mPresContext, conditions);
+    LineHeightCalcObj obj = css::ComputeCalc(*lineHeightValue, ops);
+    if (obj.mIsNumber) {
+      text->mLineHeight.SetFactorValue(obj.mLineHeight);
+    } else {
+      text->mLineHeight.SetCoordValue(
+        NSToCoordRoundWithClamp(obj.mLineHeight));
+    }
+  }
   else {
     SetCoord(*lineHeightValue, text->mLineHeight, parentText->mLineHeight,
              SETCOORD_LEH | SETCOORD_FACTOR | SETCOORD_NORMAL |
                SETCOORD_UNSET_INHERIT,
              aContext, mPresContext, conditions);
     if (lineHeightValue->IsLengthUnit() &&
         !lineHeightValue->IsRelativeLengthUnit()) {
       nscoord lh = nsStyleFont::ZoomText(mPresContext,
--- a/layout/style/test/property_database.js
+++ b/layout/style/test/property_database.js
@@ -2792,18 +2792,29 @@ var gCSSProperties = {
                      "font-kerning", "font-synthesis", "font-variant-alternates", "font-variant-caps", "font-variant-east-asian",
                      "font-variant-ligatures", "font-variant-numeric", "font-variant-position" ],
     initial_values: [ (gInitialFontFamilyIsSansSerif ? "medium sans-serif" : "medium serif") ],
     other_values: [ "large serif", "9px fantasy", "condensed bold italic small-caps 24px/1.4 Times New Roman, serif", "small inherit roman", "small roman inherit",
       // system fonts
       "caption", "icon", "menu", "message-box", "small-caption", "status-bar",
       // Gecko-specific system fonts
       "-moz-window", "-moz-document", "-moz-desktop", "-moz-info", "-moz-dialog", "-moz-button", "-moz-pull-down-menu", "-moz-list", "-moz-field", "-moz-workspace",
+      // line-height with calc()
+      "condensed bold italic small-caps 24px/calc(2px) Times New Roman, serif",
+      "condensed bold italic small-caps 24px/calc(50%) Times New Roman, serif",
+      "condensed bold italic small-caps 24px/calc(3*25px) Times New Roman, serif",
+      "condensed bold italic small-caps 24px/calc(25px*3) Times New Roman, serif",
+      "condensed bold italic small-caps 24px/calc(3*25px + 50%) Times New Roman, serif",
+      "condensed bold italic small-caps 24px/calc(1 + 2*3/4) Times New Roman, serif",
     ],
-    invalid_values: [ "9 fantasy", "-2px fantasy" ]
+    invalid_values: [ "9 fantasy", "-2px fantasy",
+      // line-height with calc()
+      "condensed bold italic small-caps 24px/calc(1 + 2px) Times New Roman, serif",
+      "condensed bold italic small-caps 24px/calc(100% + 0.1) Times New Roman, serif",
+    ]
   },
   "font-family": {
     domProp: "fontFamily",
     inherited: true,
     type: CSS_TYPE_LONGHAND,
     initial_values: [ (gInitialFontFamilyIsSansSerif ? "sans-serif" : "serif") ],
     other_values: [ (gInitialFontFamilyIsSansSerif ? "serif" : "sans-serif"), "Times New Roman, serif", "'Times New Roman', serif", "cursive", "fantasy", "\\\"Times New Roman", "\"Times New Roman\"", "Times, \\\"Times New Roman", "Times, \"Times New Roman\"", "-no-such-font-installed", "inherit roman", "roman inherit", "Times, inherit roman", "inherit roman, Times", "roman inherit, Times", "Times, roman inherit" ],
     invalid_values: [ "\"Times New\" Roman", "\"Times New Roman\n", "Times, \"Times New Roman\n" ]
@@ -3055,18 +3066,18 @@ var gCSSProperties = {
      * getComputedStyle (which uses the CSS2 computed value, or
      * CSS2.1 used value) doesn't match what the CSS2.1 computed
      * value is.  And they even require consistent font metrics for
      * computation of 'normal'.   -moz-block-height requires height
      * on a block.
      */
     prerequisites: { "font-size": "19px", "font-size-adjust": "none", "font-family": "serif", "font-weight": "normal", "font-style": "normal", "height": "18px", "display": "block"},
     initial_values: [ "normal" ],
-    other_values: [ "1.0", "1", "1em", "47px", "-moz-block-height" ],
-    invalid_values: []
+    other_values: [ "1.0", "1", "1em", "47px", "-moz-block-height", "calc(2px)", "calc(50%)", "calc(3*25px)", "calc(25px*3)", "calc(3*25px + 50%)", "calc(1 + 2*3/4)" ],
+    invalid_values: [ "calc(1 + 2px)", "calc(100% + 0.1)" ]
   },
   "list-style": {
     domProp: "listStyle",
     inherited: true,
     type: CSS_TYPE_TRUE_SHORTHAND,
     subproperties: [ "list-style-type", "list-style-position", "list-style-image" ],
     initial_values: [ "outside", "disc", "disc outside", "outside disc", "disc none", "none disc", "none disc outside", "none outside disc", "disc none outside", "disc outside none", "outside none disc", "outside disc none" ],
     other_values: [ "inside none", "none inside", "none none inside", "square", "none", "none none", "outside none none", "none outside none", "none none outside", "none outside", "outside none", "outside outside", "outside inside", "\\32 style", "\\32 style inside",