Bug 1093553 - Improve handling of line-height metrics, block ascent, etc., in vertical writing mode. r=smontagu
authorJonathan Kew <jkew@mozilla.com>
Thu, 13 Nov 2014 08:58:06 +0000
changeset 239802 9e5ac3d61831ac98565256fadbb254f03269b3a5
parent 239801 72894fb00f2544fdc68948a3d58eb8dc4b7d39b0
child 239803 acb19a80d5f7a2588d195041abe4e25bd9520aed
push id4311
push userraliiev@mozilla.com
push dateMon, 12 Jan 2015 19:37:41 +0000
treeherdermozilla-beta@150c9fed433b [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssmontagu
bugs1093553
milestone36.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 1093553 - Improve handling of line-height metrics, block ascent, etc., in vertical writing mode. r=smontagu
gfx/thebes/gfxFont.cpp
layout/base/nsCSSRendering.cpp
layout/base/nsLayoutUtils.cpp
layout/base/nsLayoutUtils.h
layout/forms/nsTextControlFrame.cpp
layout/generic/WritingModes.h
layout/generic/nsBRFrame.cpp
layout/generic/nsBlockFrame.cpp
layout/generic/nsFrame.cpp
layout/generic/nsLineLayout.cpp
layout/generic/nsTextFrame.cpp
--- a/gfx/thebes/gfxFont.cpp
+++ b/gfx/thebes/gfxFont.cpp
@@ -1897,32 +1897,38 @@ gfxFont::Draw(gfxTextRun *aTextRun, uint
     bool sideways = false;
     gfxPoint origPt = *aPt;
     if (aRunParams.isVerticalRun && !fontParams.isVerticalFont) {
         sideways = true;
         aRunParams.context->Save();
         gfxPoint p(aPt->x * aRunParams.devPerApp,
                    aPt->y * aRunParams.devPerApp);
         const Metrics& metrics = GetMetrics(eHorizontal);
-        // Adjust the matrix to draw the (horizontally-shaped) textrun with
-        // 90-degree CW rotation, and adjust position so that the rotated
-        // horizontal text (which uses a standard alphabetic baseline) will
+        // Get a matrix we can use to draw the (horizontally-shaped) textrun
+        // with 90-degree CW rotation.
+        gfxMatrix mat = aRunParams.context->CurrentMatrix().
+            Translate(p).       // translate origin for rotation
+            Rotate(M_PI / 2.0). // turn 90deg clockwise
+            Translate(-p);      // undo the translation
+
+        // If we're drawing rotated horizontal text for an element styled
+        // text-orientation:mixed, the dominant baseline will be vertical-
+        // centered. So in this case, we need to adjust the position so that
+        // the rotated horizontal text (which uses an alphabetic baseline) will
         // look OK when juxtaposed with upright glyphs (rendered on a centered
         // vertical baseline). The adjustment here is somewhat ad hoc; we
         // should eventually look for baseline tables[1] in the fonts and use
         // those if available.
-        // [1] http://www.microsoft.com/typography/otspec/base.htm
-        aRunParams.context->SetMatrix(aRunParams.context->CurrentMatrix().
-            Translate(p).       // translate origin for rotation
-            Rotate(M_PI / 2.0). // turn 90deg clockwise
-            Translate(-p).      // undo the translation
-            Translate(gfxPoint(0, (metrics.emAscent - metrics.emDescent) / 2)));
-                                // and offset the (alphabetic) baseline of the
-                                // horizontally-shaped text from the (centered)
-                                // default baseline used for vertical
+        // [1] See http://www.microsoft.com/typography/otspec/base.htm
+        if (aTextRun->UseCenterBaseline()) {
+            gfxPoint baseAdj(0, (metrics.emAscent - metrics.emDescent) / 2);
+            mat.Translate(baseAdj);
+        }
+
+        aRunParams.context->SetMatrix(mat);
     }
 
     nsAutoPtr<gfxTextContextPaint> contextPaint;
     if (fontParams.haveSVGGlyphs && !fontParams.contextPaint) {
         // If no pattern is specified for fill, use the current pattern
         NS_ASSERTION((int(aRunParams.drawMode) & int(DrawMode::GLYPH_STROKE)) == 0,
                      "no pattern supplied for stroking text");
         nsRefPtr<gfxPattern> fillPattern = aRunParams.context->GetPattern();
@@ -2133,25 +2139,43 @@ gfxFont::Measure(gfxTextRun *aTextRun,
                                        aRefContext, aSpacing, aOrientation);
         }
     }
 
     const int32_t appUnitsPerDevUnit = aTextRun->GetAppUnitsPerDevUnit();
     // Current position in appunits
     gfxFont::Orientation orientation =
         aOrientation == gfxTextRunFactory::TEXT_ORIENT_VERTICAL_UPRIGHT
-        ? gfxFont::eVertical : gfxFont::eHorizontal;
+        ? eVertical : eHorizontal;
     const gfxFont::Metrics& fontMetrics = GetMetrics(orientation);
 
+    gfxFloat baselineOffset = 0;
+    if (aTextRun->UseCenterBaseline() && orientation == eHorizontal) {
+        // For a horizontal font being used in vertical writing mode with
+        // text-orientation:mixed, the overall metrics we're accumulating
+        // will be aimed at a center baseline. But this font's metrics were
+        // based on the alphabetic baseline. So we compute a baseline offset
+        // that will be applied to ascent/descent values and glyph rects
+        // to effectively shift them relative to the baseline.
+        // XXX Eventually we should probably use the BASE table, if present.
+        // But it usually isn't, so we need an ad hoc adjustment for now.
+        baselineOffset = appUnitsPerDevUnit *
+            (fontMetrics.emAscent - fontMetrics.emDescent) / 2;
+    }
+
     RunMetrics metrics;
-    metrics.mAscent = fontMetrics.maxAscent*appUnitsPerDevUnit;
-    metrics.mDescent = fontMetrics.maxDescent*appUnitsPerDevUnit;
+    metrics.mAscent = fontMetrics.maxAscent * appUnitsPerDevUnit;
+    metrics.mDescent = fontMetrics.maxDescent * appUnitsPerDevUnit;
+
     if (aStart == aEnd) {
         // exit now before we look at aSpacing[0], which is undefined
-        metrics.mBoundingBox = gfxRect(0, -metrics.mAscent, 0, metrics.mAscent + metrics.mDescent);
+        metrics.mAscent -= baselineOffset;
+        metrics.mDescent += baselineOffset;
+        metrics.mBoundingBox = gfxRect(0, -metrics.mAscent,
+                                       0, metrics.mAscent + metrics.mDescent);
         return metrics;
     }
 
     gfxFloat advanceMin = 0, advanceMax = 0;
     const gfxTextRun::CompressedGlyph *charGlyphs = aTextRun->GetCharacterGlyphs();
     bool isRTL = aTextRun->IsRightToLeft();
     double direction = aTextRun->GetDirection();
     bool needsGlyphExtents = NeedsGlyphExtents(this, aTextRun);
@@ -2242,16 +2266,22 @@ gfxFont::Measure(gfxTextRun *aTextRun,
         gfxRect fontBox(advanceMin, -metrics.mAscent,
                         advanceMax - advanceMin, metrics.mAscent + metrics.mDescent);
         metrics.mBoundingBox = metrics.mBoundingBox.Union(fontBox);
     }
     if (isRTL) {
         metrics.mBoundingBox -= gfxPoint(x, 0);
     }
 
+    if (baselineOffset != 0) {
+        metrics.mAscent -= baselineOffset;
+        metrics.mDescent += baselineOffset;
+        metrics.mBoundingBox.y += baselineOffset;
+    }
+
     metrics.mAdvanceWidth = x*direction;
     return metrics;
 }
 
 static PLDHashOperator
 NotifyGlyphChangeObservers(nsPtrHashKey<gfxFont::GlyphChangeObserver>* aKey,
                            void* aClosure)
 {
--- a/layout/base/nsCSSRendering.cpp
+++ b/layout/base/nsCSSRendering.cpp
@@ -4157,18 +4157,22 @@ nsCSSRendering::PaintDecorationLine(nsIF
         drawOptions.mAntialiasMode = AntialiasMode::NONE;
       }
       break;
     default:
       NS_ERROR("Invalid style value!");
       return;
   }
 
-  // The y position should be set to the middle of the line.
-  rect.y += lineThickness / 2;
+  // The block-direction position should be set to the middle of the line.
+  if (aVertical) {
+    rect.x -= lineThickness / 2;
+  } else {
+    rect.y += lineThickness / 2;
+  }
 
   switch (aStyle) {
     case NS_STYLE_TEXT_DECORATION_STYLE_SOLID:
     case NS_STYLE_TEXT_DECORATION_STYLE_DOTTED:
     case NS_STYLE_TEXT_DECORATION_STYLE_DASHED: {
       Point p1 = rect.TopLeft();
       Point p2 = aVertical ? rect.BottomLeft() : rect.TopRight();
       aDrawTarget.StrokeLine(p1, p2, color, strokeOptions, drawOptions);
@@ -4355,22 +4359,28 @@ nsCSSRendering::DecorationLineToPath(nsI
 
   if (aStyle != NS_STYLE_TEXT_DECORATION_STYLE_SOLID) {
     // For the moment, we support only solid text decorations.
     return;
   }
 
   gfxFloat lineThickness = std::max(NS_round(aLineSize.height), 1.0);
 
-  // The y position should be set to the middle of the line.
-  rect.y += lineThickness / 2;
-
-  aGfxContext->Rectangle
-    (gfxRect(gfxPoint(rect.TopLeft() - gfxPoint(0.0, lineThickness / 2)),
-             gfxSize(rect.Width(), lineThickness)));
+  // The block-direction position should be set to the middle of the line.
+  if (aVertical) {
+    rect.x -= lineThickness / 2;
+    aGfxContext->Rectangle
+      (gfxRect(gfxPoint(rect.TopLeft() - gfxPoint(lineThickness / 2, 0.0)),
+               gfxSize(lineThickness, rect.Height())));
+  } else {
+    rect.y += lineThickness / 2;
+    aGfxContext->Rectangle
+      (gfxRect(gfxPoint(rect.TopLeft() - gfxPoint(0.0, lineThickness / 2)),
+               gfxSize(rect.Width(), lineThickness)));
+  }
 }
 
 nsRect
 nsCSSRendering::GetTextDecorationRect(nsPresContext* aPresContext,
                                       const gfxSize& aLineSize,
                                       const gfxFloat aAscent,
                                       const gfxFloat aOffset,
                                       const uint8_t aDecoration,
--- a/layout/base/nsLayoutUtils.cpp
+++ b/layout/base/nsLayoutUtils.cpp
@@ -3491,17 +3491,18 @@ nsLayoutUtils::GetFontMetricsForStyleCon
   // doing so would be lossy.  Fortunately, in such cases, aInflation is
   // guaranteed to be 1.0f.
   if (aInflation != 1.0f) {
     font.size = NSToCoordRound(font.size * aInflation);
   }
   WritingMode wm(aStyleContext);
   return pc->DeviceContext()->GetMetricsFor(
                   font, aStyleContext->StyleFont()->mLanguage,
-                  wm.IsVertical() ? gfxFont::eVertical : gfxFont::eHorizontal,
+                  wm.IsVertical() && !wm.IsSideways()
+                    ? gfxFont::eVertical : gfxFont::eHorizontal,
                   fs, tp, *aFontMetrics);
 }
 
 nsIFrame*
 nsLayoutUtils::FindChildContainingDescendant(nsIFrame* aParent, nsIFrame* aDescendantFrame)
 {
   nsIFrame* result = aDescendantFrame;
 
@@ -4888,19 +4889,21 @@ nsLayoutUtils::PaintTextShadow(const nsI
 
     contextBoxBlur.DoPaint();
     aDestCtx->Restore();
   }
 }
 
 /* static */ nscoord
 nsLayoutUtils::GetCenteredFontBaseline(nsFontMetrics* aFontMetrics,
-                                       nscoord         aLineHeight)
-{
-  nscoord fontAscent = aFontMetrics->MaxAscent();
+                                       nscoord        aLineHeight,
+                                       bool           aIsInverted)
+{
+  nscoord fontAscent = aIsInverted ? aFontMetrics->MaxDescent()
+                                   : aFontMetrics->MaxAscent();
   nscoord fontHeight = aFontMetrics->MaxHeight();
 
   nscoord leading = aLineHeight - fontHeight;
   return fontAscent + leading/2;
 }
 
 
 /* static */ bool
@@ -7345,17 +7348,18 @@ nsLayoutUtils::SetBSizeFromFontMetrics(c
     // Do things the standard css2 way -- though it's hard to find it
     // in the css2 spec! It's actually found in the css1 spec section
     // 4.4 (you will have to read between the lines to really see
     // it).
     //
     // The height of our box is the sum of our font size plus the top
     // and bottom border and padding. The height of children do not
     // affect our height.
-    aMetrics.SetBlockStartAscent(fm->MaxAscent());
+    aMetrics.SetBlockStartAscent(aLineWM.IsLineInverted() ? fm->MaxDescent()
+                                                          : fm->MaxAscent());
     aMetrics.BSize(aLineWM) = fm->MaxHeight();
   } else {
     NS_WARNING("Cannot get font metrics - defaulting sizes to 0");
     aMetrics.SetBlockStartAscent(aMetrics.BSize(aLineWM) = 0);
   }
   aMetrics.SetBlockStartAscent(aMetrics.BlockStartAscent() +
                                aFramePadding.BStart(aFrameWM));
   aMetrics.BSize(aLineWM) +=
--- a/layout/base/nsLayoutUtils.h
+++ b/layout/base/nsLayoutUtils.h
@@ -1392,21 +1392,25 @@ public:
                               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.
+   * aIsInverted: true if the text is inverted relative to the block
+   * direction, so that the block-dir "ascent" corresponds to font
+   * descent. (Applies to sideways text in vertical-lr mode.)
    *
    * Returns the baseline position relative to the top of the line.
    */
   static nscoord GetCenteredFontBaseline(nsFontMetrics* aFontMetrics,
-                                         nscoord         aLineHeight);
+                                         nscoord        aLineHeight,
+                                         bool           aIsInverted);
 
   /**
    * Derive a baseline of |aFrame| (measured from its top border edge)
    * from its first in-flow line box (not descending into anything with
    * 'overflow' not 'visible', potentially including aFrame itself).
    *
    * Returns true if a baseline was found (and fills in aResult).
    * Otherwise returns false.
--- a/layout/forms/nsTextControlFrame.cpp
+++ b/layout/forms/nsTextControlFrame.cpp
@@ -514,17 +514,18 @@ nsTextControlFrame::Reflow(nsPresContext
     lineHeight = nsHTMLReflowState::CalcLineHeight(GetContent(), StyleContext(),
                                                    NS_AUTOHEIGHT, inflation);
   }
   nsRefPtr<nsFontMetrics> fontMet;
   nsLayoutUtils::GetFontMetricsForFrame(this, getter_AddRefs(fontMet),
                                         inflation);
   // now adjust for our borders and padding
   aDesiredSize.SetBlockStartAscent(
-    nsLayoutUtils::GetCenteredFontBaseline(fontMet, lineHeight) +
+    nsLayoutUtils::GetCenteredFontBaseline(fontMet, lineHeight,
+                                           wm.IsLineInverted()) +
     aReflowState.ComputedLogicalBorderPadding().BStart(wm));
 
   // overflow handling
   aDesiredSize.SetOverflowAreasToDesiredBounds();
   // perform reflow on all kids
   nsIFrame* kid = mFrames.FirstChild();
   while (kid) {
     ReflowTextControlChild(kid, aPresContext, aReflowState, aStatus, aDesiredSize);
--- a/layout/generic/WritingModes.h
+++ b/layout/generic/WritingModes.h
@@ -163,22 +163,27 @@ public:
   /**
    * Return the line-relative inline flow direction as a BidiDir
    */
   BidiDir GetBidiDir() const { return BidiDir(mWritingMode & eBidiMask); }
 
   /**
    * Return true if LTR. (Convenience method)
    */
-  bool IsBidiLTR() const { return eBidiLTR == (mWritingMode & eBidiMask); }
+  bool IsBidiLTR() const { return eBidiLTR == GetBidiDir(); }
 
   /**
    * True if vertical-mode block direction is LR (convenience method).
    */
-  bool IsVerticalLR() const { return eBlockLR == (mWritingMode & eBlockMask); }
+  bool IsVerticalLR() const { return eBlockLR == GetBlockDir(); }
+
+  /**
+   * True if vertical-mode block direction is RL (convenience method).
+   */
+  bool IsVerticalRL() const { return eBlockRL == GetBlockDir(); }
 
   /**
    * True if vertical writing mode, i.e. when
    * writing-mode: vertical-lr | vertical-rl.
    */
 #ifdef WRITING_MODE_VERTICAL_ENABLED
   bool IsVertical() const { return !!(mWritingMode & eOrientationMask); }
 #else
--- a/layout/generic/nsBRFrame.cpp
+++ b/layout/generic/nsBRFrame.cpp
@@ -117,17 +117,18 @@ BRFrame::Reflow(nsPresContext* aPresCont
       // normal inline frame.  That line-height is used is important
       // here for cases where the line-height is less than 1.
       nsRefPtr<nsFontMetrics> fm;
       nsLayoutUtils::GetFontMetricsForFrame(this, getter_AddRefs(fm),
         nsLayoutUtils::FontSizeInflationFor(this));
       if (fm) {
         nscoord logicalHeight = aReflowState.CalcLineHeight();
         finalSize.BSize(wm) = logicalHeight;
-        aMetrics.SetBlockStartAscent(nsLayoutUtils::GetCenteredFontBaseline(fm, logicalHeight));
+        aMetrics.SetBlockStartAscent(nsLayoutUtils::GetCenteredFontBaseline(
+                                       fm, logicalHeight, wm.IsLineInverted()));
       }
       else {
         aMetrics.SetBlockStartAscent(aMetrics.BSize(wm) = 0);
       }
 
       // XXX temporary until I figure out a better solution; see the
       // code in nsLineLayout::VerticalAlignFrames that zaps minY/maxY
       // if the width is zero.
--- a/layout/generic/nsBlockFrame.cpp
+++ b/layout/generic/nsBlockFrame.cpp
@@ -510,17 +510,19 @@ nsBlockFrame::GetCaretBaseline() const
     }
   }
   nsRefPtr<nsFontMetrics> fm;
   float inflation = nsLayoutUtils::FontSizeInflationFor(this);
   nsLayoutUtils::GetFontMetricsForFrame(this, getter_AddRefs(fm), inflation);
   nscoord lineHeight =
     nsHTMLReflowState::CalcLineHeight(GetContent(), StyleContext(),
                                       contentRect.height, inflation);
-  return nsLayoutUtils::GetCenteredFontBaseline(fm, lineHeight) + bp.top;
+  const WritingMode wm = GetWritingMode();
+  return nsLayoutUtils::GetCenteredFontBaseline(fm, lineHeight,
+                                                wm.IsLineInverted()) + bp.top;
 }
 
 /////////////////////////////////////////////////////////////////////////////
 // Child frame enumeration
 
 const nsFrameList&
 nsBlockFrame::GetChildList(ChildListID aListID) const
 {
@@ -2547,17 +2549,18 @@ nsBlockFrame::ReflowDirtyLines(nsBlockRe
         }
       }
 
       nsRefPtr<nsFontMetrics> fm;
       nsLayoutUtils::GetFontMetricsForFrame(this, getter_AddRefs(fm),
         nsLayoutUtils::FontSizeInflationFor(this));
 
       nscoord minAscent =
-        nsLayoutUtils::GetCenteredFontBaseline(fm, aState.mMinLineHeight);
+        nsLayoutUtils::GetCenteredFontBaseline(fm, aState.mMinLineHeight,
+                                               wm.IsLineInverted());
       nscoord minDescent = aState.mMinLineHeight - minAscent;
 
       aState.mBCoord += std::max(minAscent, metrics.BlockStartAscent()) +
                         std::max(minDescent, metrics.BSize(wm) -
                                              metrics.BlockStartAscent());
 
       nscoord offset = minAscent - metrics.BlockStartAscent();
       if (offset > 0) {
--- a/layout/generic/nsFrame.cpp
+++ b/layout/generic/nsFrame.cpp
@@ -1334,17 +1334,22 @@ nsFrame::SetAdditionalStyleContext(int32
   NS_PRECONDITION(aIndex >= 0, "invalid index number");
 }
 
 nscoord
 nsFrame::GetLogicalBaseline(WritingMode aWritingMode) const
 {
   NS_ASSERTION(!NS_SUBTREE_DIRTY(this),
                "frame must not be dirty");
-  // Default to the bottom margin edge, per CSS2.1's definition of the
+  // Baseline for inverted line content is the top (block-start) margin edge,
+  // as the frame is in effect "flipped" for alignment purposes.
+  if (aWritingMode.IsLineInverted()) {
+    return -GetLogicalUsedMargin(aWritingMode).BStart(aWritingMode);
+  }
+  // Otherwise, the bottom margin edge, per CSS2.1's definition of the
   // 'baseline' value of 'vertical-align'.
   return BSize(aWritingMode) +
          GetLogicalUsedMargin(aWritingMode).BEnd(aWritingMode);
 }
 
 const nsFrameList&
 nsFrame::GetChildList(ChildListID aListID) const
 {
--- a/layout/generic/nsLineLayout.cpp
+++ b/layout/generic/nsLineLayout.cpp
@@ -2093,17 +2093,18 @@ nsLineLayout::VerticalAlignFrames(PerSpa
     }
     if (applyMinLH) {
       if (psd->mHasNonemptyContent || preMode || mHasBullet) {
 #ifdef NOISY_BLOCKDIR_ALIGN
         printf("  [span]==> adjusting min/maxBCoord: currentValues: %d,%d", minBCoord, maxBCoord);
 #endif
         nscoord minimumLineBSize = mMinLineBSize;
         nscoord blockStart =
-          -nsLayoutUtils::GetCenteredFontBaseline(fm, minimumLineBSize);
+          -nsLayoutUtils::GetCenteredFontBaseline(fm, minimumLineBSize,
+                                                  lineWM.IsLineInverted());
         nscoord blockEnd = blockStart + minimumLineBSize;
 
         if (blockStart < minBCoord) minBCoord = blockStart;
         if (blockEnd > maxBCoord) maxBCoord = blockEnd;
 
 #ifdef NOISY_BLOCKDIR_ALIGN
         printf(" new values: %d,%d\n", minBCoord, maxBCoord);
 #endif
--- a/layout/generic/nsTextFrame.cpp
+++ b/layout/generic/nsTextFrame.cpp
@@ -1785,23 +1785,23 @@ GetHyphenTextRun(gfxTextRun* aTextRun, g
   if (!ctx)
     return nullptr;
 
   return aTextRun->GetFontGroup()->
     MakeHyphenTextRun(ctx, aTextRun->GetAppUnitsPerDevUnit());
 }
 
 static gfxFont::Metrics
-GetFirstFontMetrics(gfxFontGroup* aFontGroup, bool aVertical)
+GetFirstFontMetrics(gfxFontGroup* aFontGroup, bool aVerticalMetrics)
 {
   if (!aFontGroup)
     return gfxFont::Metrics();
   gfxFont* font = aFontGroup->GetFirstValidFont();
-  return font->GetMetrics(aVertical ? gfxFont::eVertical :
-                                      gfxFont::eHorizontal);
+  return font->GetMetrics(aVerticalMetrics ? gfxFont::eVertical
+                                           : gfxFont::eHorizontal);
 }
 
 PR_STATIC_ASSERT(NS_STYLE_WHITESPACE_NORMAL == 0);
 PR_STATIC_ASSERT(NS_STYLE_WHITESPACE_PRE == 1);
 PR_STATIC_ASSERT(NS_STYLE_WHITESPACE_NOWRAP == 2);
 PR_STATIC_ASSERT(NS_STYLE_WHITESPACE_PRE_WRAP == 3);
 PR_STATIC_ASSERT(NS_STYLE_WHITESPACE_PRE_LINE == 4);
 PR_STATIC_ASSERT(NS_STYLE_WHITESPACE_PRE_SPACE == 5);
@@ -3156,17 +3156,17 @@ ComputeTabWidthAppUnits(nsIFrame* aFrame
 {
   // Get the number of spaces from CSS -moz-tab-size
   const nsStyleText* textStyle = aFrame->StyleText();
   
   // Round the space width when converting to appunits the same way
   // textruns do
   gfxFloat spaceWidthAppUnits =
     NS_round(GetFirstFontMetrics(aTextRun->GetFontGroup(),
-                                 aTextRun->IsVertical()).spaceWidth *
+                                 aTextRun->UseCenterBaseline()).spaceWidth *
              aTextRun->GetAppUnitsPerDevUnit());
   return textStyle->mTabSize * spaceWidthAppUnits;
 }
 
 // aX and the result are in whole appunits.
 static gfxFloat
 AdvanceToNextTab(gfxFloat aX, nsIFrame* aFrame,
                  gfxTextRun* aTextRun, gfxFloat* aCachedTabWidth)
@@ -4913,51 +4913,55 @@ nsTextFrame::UnionAdditionalOverflow(nsP
                                      PropertyProvider& aProvider,
                                      nsRect* aVisualOverflowRect,
                                      bool aIncludeTextDecorations)
 {
   // Text-shadow overflows
   nsRect shadowRect =
     nsLayoutUtils::GetTextShadowRectsUnion(*aVisualOverflowRect, this);
   aVisualOverflowRect->UnionRect(*aVisualOverflowRect, shadowRect);
-  bool vertical = mTextRun->IsVertical();
+  bool verticalRun = mTextRun->IsVertical();
+  bool useVerticalMetrics = verticalRun && mTextRun->UseCenterBaseline();
+  bool inverted = GetWritingMode().IsLineInverted();
 
   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.
     // In this case, aBlock is the ::first-letter frame.
     uint8_t decorationStyle = aBlock->StyleContext()->
                                 StyleTextReset()->GetDecorationStyle();
     // 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;
     }
     nsFontMetrics* fontMetrics = aProvider.GetFontMetrics();
     nscoord underlineOffset, underlineSize;
     fontMetrics->GetUnderline(underlineOffset, underlineSize);
-    nscoord maxAscent = fontMetrics->MaxAscent();
+    nscoord maxAscent = inverted ? fontMetrics->MaxDescent()
+                                 : fontMetrics->MaxAscent();
 
     gfxFloat appUnitsPerDevUnit = aPresContext->AppUnitsPerDevPixel();
     gfxFloat gfxWidth =
-      (vertical ? aVisualOverflowRect->height : aVisualOverflowRect->width) /
+      (verticalRun ? aVisualOverflowRect->height
+                   : aVisualOverflowRect->width) /
       appUnitsPerDevUnit;
     gfxFloat gfxAscent = gfxFloat(mAscent) / appUnitsPerDevUnit;
     gfxFloat gfxMaxAscent = maxAscent / appUnitsPerDevUnit;
     gfxFloat gfxUnderlineSize = underlineSize / appUnitsPerDevUnit;
     gfxFloat gfxUnderlineOffset = underlineOffset / appUnitsPerDevUnit;
     nsRect underlineRect =
       nsCSSRendering::GetTextDecorationRect(aPresContext,
         gfxSize(gfxWidth, gfxUnderlineSize), gfxAscent, gfxUnderlineOffset,
-        NS_STYLE_TEXT_DECORATION_LINE_UNDERLINE, decorationStyle, vertical);
+        NS_STYLE_TEXT_DECORATION_LINE_UNDERLINE, decorationStyle, verticalRun);
     nsRect overlineRect =
       nsCSSRendering::GetTextDecorationRect(aPresContext,
         gfxSize(gfxWidth, gfxUnderlineSize), gfxAscent, gfxMaxAscent,
-        NS_STYLE_TEXT_DECORATION_LINE_OVERLINE, decorationStyle, vertical);
+        NS_STYLE_TEXT_DECORATION_LINE_OVERLINE, decorationStyle, verticalRun);
 
     aVisualOverflowRect->UnionRect(*aVisualOverflowRect, underlineRect);
     aVisualOverflowRect->UnionRect(*aVisualOverflowRect, overlineRect);
 
     // XXX If strikeoutSize is much thicker than the underlineSize, it may
     //     cause overflowing from the overflow rect.  However, such case
     //     isn't realistic, we don't need to compute it now.
   }
@@ -4967,108 +4971,129 @@ nsTextFrame::UnionAdditionalOverflow(nsP
     // maxima and minima are required to reliably generate the rectangle for
     // them
     TextDecorations textDecs;
     GetTextDecorations(aPresContext, eResolvedColors, textDecs);
     if (textDecs.HasDecorationLines()) {
       nscoord inflationMinFontSize =
         nsLayoutUtils::InflationMinFontSizeFor(aBlock);
 
-      const nscoord measure = vertical ? GetSize().height : GetSize().width;
+      const nscoord measure = verticalRun ? GetSize().height : GetSize().width;
       const gfxFloat appUnitsPerDevUnit = aPresContext->AppUnitsPerDevPixel(),
-                     gfxWidth = measure / appUnitsPerDevUnit,
-                     ascent = gfxFloat(mAscent) / appUnitsPerDevUnit;
-      nscoord top(nscoord_MAX), bottom(nscoord_MIN);
+                     gfxWidth = measure / appUnitsPerDevUnit;
+      gfxFloat ascent = gfxFloat(mAscent) / appUnitsPerDevUnit;
+      const WritingMode wm = GetWritingMode();
+      if (wm.IsVerticalRL()) {
+        ascent = -ascent;
+      }
+
+      nscoord topOrLeft(nscoord_MAX), bottomOrRight(nscoord_MIN);
       // Below we loop through all text decorations and compute the rectangle
       // containing all of them, in this frame's coordinate space
       for (uint32_t i = 0; i < textDecs.mUnderlines.Length(); ++i) {
         const LineDecoration& dec = textDecs.mUnderlines[i];
         uint8_t decorationStyle = dec.mStyle;
         // If the style is solid, let's include decoration line rect of 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;
         }
 
         float inflation =
           GetInflationForTextDecorations(dec.mFrame, inflationMinFontSize);
         const gfxFont::Metrics metrics =
           GetFirstFontMetrics(GetFontGroupForFrame(dec.mFrame, inflation),
-                              vertical);
+                              useVerticalMetrics);
 
         const nsRect decorationRect =
           nsCSSRendering::GetTextDecorationRect(aPresContext,
             gfxSize(gfxWidth, metrics.underlineSize),
             ascent, metrics.underlineOffset,
             NS_STYLE_TEXT_DECORATION_LINE_UNDERLINE, decorationStyle,
-            vertical) +
+            verticalRun) +
           nsPoint(0, -dec.mBaselineOffset);
 
-        top = std::min(decorationRect.y, top);
-        bottom = std::max(decorationRect.YMost(), bottom);
+        if (verticalRun) {
+          topOrLeft = std::min(decorationRect.x, topOrLeft);
+          bottomOrRight = std::max(decorationRect.XMost(), bottomOrRight);
+        } else {
+          topOrLeft = std::min(decorationRect.y, topOrLeft);
+          bottomOrRight = std::max(decorationRect.YMost(), bottomOrRight);
+        }
       }
       for (uint32_t i = 0; i < textDecs.mOverlines.Length(); ++i) {
         const LineDecoration& dec = textDecs.mOverlines[i];
         uint8_t decorationStyle = dec.mStyle;
         // If the style is solid, let's include decoration line rect of 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;
         }
 
         float inflation =
           GetInflationForTextDecorations(dec.mFrame, inflationMinFontSize);
         const gfxFont::Metrics metrics =
           GetFirstFontMetrics(GetFontGroupForFrame(dec.mFrame, inflation),
-                              vertical);
+                              useVerticalMetrics);
 
         const nsRect decorationRect =
           nsCSSRendering::GetTextDecorationRect(aPresContext,
             gfxSize(gfxWidth, metrics.underlineSize),
             ascent, metrics.maxAscent,
             NS_STYLE_TEXT_DECORATION_LINE_OVERLINE, decorationStyle,
-            vertical) +
+            verticalRun) +
           nsPoint(0, -dec.mBaselineOffset);
 
-        top = std::min(decorationRect.y, top);
-        bottom = std::max(decorationRect.YMost(), bottom);
+        if (verticalRun) {
+          topOrLeft = std::min(decorationRect.x, topOrLeft);
+          bottomOrRight = std::max(decorationRect.XMost(), bottomOrRight);
+        } else {
+          topOrLeft = std::min(decorationRect.y, topOrLeft);
+          bottomOrRight = std::max(decorationRect.YMost(), bottomOrRight);
+        }
       }
       for (uint32_t i = 0; i < textDecs.mStrikes.Length(); ++i) {
         const LineDecoration& dec = textDecs.mStrikes[i];
         uint8_t decorationStyle = dec.mStyle;
         // If the style is solid, let's include decoration line rect of 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;
         }
 
         float inflation =
           GetInflationForTextDecorations(dec.mFrame, inflationMinFontSize);
         const gfxFont::Metrics metrics =
           GetFirstFontMetrics(GetFontGroupForFrame(dec.mFrame, inflation),
-                              vertical);
+                              useVerticalMetrics);
 
         const nsRect decorationRect =
           nsCSSRendering::GetTextDecorationRect(aPresContext,
             gfxSize(gfxWidth, metrics.strikeoutSize),
             ascent, metrics.strikeoutOffset,
             NS_STYLE_TEXT_DECORATION_LINE_LINE_THROUGH, decorationStyle,
-            vertical) +
+            verticalRun) +
           nsPoint(0, -dec.mBaselineOffset);
-        top = std::min(decorationRect.y, top);
-        bottom = std::max(decorationRect.YMost(), bottom);
+
+        if (verticalRun) {
+          topOrLeft = std::min(decorationRect.x, topOrLeft);
+          bottomOrRight = std::max(decorationRect.XMost(), bottomOrRight);
+        } else {
+          topOrLeft = std::min(decorationRect.y, topOrLeft);
+          bottomOrRight = std::max(decorationRect.YMost(), bottomOrRight);
+        }
       }
 
-      aVisualOverflowRect->UnionRect(*aVisualOverflowRect,
-                                     vertical ?
-                                       nsRect(top, 0, bottom - top, measure) :
-                                       nsRect(0, top, measure, bottom - top));
+      aVisualOverflowRect->UnionRect(
+        *aVisualOverflowRect,
+        verticalRun ? nsRect(topOrLeft, 0, bottomOrRight - topOrLeft, measure)
+                    : nsRect(0, topOrLeft, measure, bottomOrRight - topOrLeft));
     }
   }
   // When this frame is not selected, the text-decoration area must be in
   // frame bounds.
   if (!IsSelected() ||
       !CombineSelectionUnderlineRect(aPresContext, *aVisualOverflowRect))
     return;
   AddStateBits(TEXT_SELECTION_UNDERLINE_OVERFLOWED);
@@ -5718,63 +5743,64 @@ nsTextFrame::PaintTextSelectionDecoratio
       for (int32_t i = start; i < end; ++i) {
         selectedChars[i] = sdptr;
       }
     }
     sdptr = sdptr->mNext;
   }
 
   gfxFont* firstFont = aProvider.GetFontGroup()->GetFirstValidFont();
-  bool vertical = mTextRun->IsVertical();
+  bool verticalRun = mTextRun->IsVertical();
+  bool useVerticalMetrics = verticalRun && mTextRun->UseCenterBaseline();
   gfxFont::Metrics
-    decorationMetrics(firstFont->GetMetrics(vertical ?
+    decorationMetrics(firstFont->GetMetrics(useVerticalMetrics ?
       gfxFont::eVertical : gfxFont::eHorizontal));
-  if (!vertical) {
+  if (!useVerticalMetrics) {
     // The potential adjustment from using gfxFontGroup::GetUnderlineOffset
-    // is only valid for horizontal text.
+    // is only valid for horizontal font metrics.
     decorationMetrics.underlineOffset =
       aProvider.GetFontGroup()->GetUnderlineOffset();
   }
 
   gfxFloat startIOffset =
-    vertical ? aTextBaselinePt.y - aFramePt.y : aTextBaselinePt.x - aFramePt.x;
+    verticalRun ? aTextBaselinePt.y - aFramePt.y : aTextBaselinePt.x - aFramePt.x;
   SelectionIterator iterator(selectedChars, aContentOffset, aContentLength,
                              aProvider, mTextRun, startIOffset);
   gfxFloat iOffset, hyphenWidth;
   uint32_t offset, length;
   int32_t app = aTextPaintStyle.PresContext()->AppUnitsPerDevPixel();
   // XXX aTextBaselinePt is in AppUnits, shouldn't it be nsFloatPoint?
   gfxPoint pt;
-  if (vertical) {
+  if (verticalRun) {
     pt.x = (aTextBaselinePt.x - mAscent) / app;
   } else {
     pt.y = (aTextBaselinePt.y - mAscent) / app;
   }
   gfxRect dirtyRect(aDirtyRect.x / app, aDirtyRect.y / app,
                     aDirtyRect.width / app, aDirtyRect.height / app);
   SelectionType type;
   TextRangeStyle selectedStyle;
   while (iterator.GetNextSegment(&iOffset, &offset, &length, &hyphenWidth,
                                  &type, &selectedStyle)) {
     gfxFloat advance = hyphenWidth +
       mTextRun->GetAdvanceWidth(offset, length, &aProvider);
     if (type == aSelectionType) {
-      if (vertical) {
+      if (verticalRun) {
         pt.y = (aFramePt.y + iOffset -
                (mTextRun->IsRightToLeft() ? advance : 0)) / app;
       } else {
         pt.x = (aFramePt.x + iOffset -
                (mTextRun->IsRightToLeft() ? advance : 0)) / app;
       }
       gfxFloat width = Abs(advance) / app;
       gfxFloat xInFrame = pt.x - (aFramePt.x / app);
       DrawSelectionDecorations(aCtx, dirtyRect, aSelectionType, this,
                                aTextPaintStyle, selectedStyle, pt, xInFrame,
                                width, mAscent / app, decorationMetrics,
-                               aCallbacks, vertical);
+                               aCallbacks, verticalRun);
     }
     iterator.UpdateWithAdvance(advance);
   }
 }
 
 bool
 nsTextFrame::PaintTextWithSelection(gfxContext* aCtx,
     const gfxPoint& aFramePt,
@@ -6008,38 +6034,37 @@ nsTextFrame::PaintText(nsRenderingContex
     return;
 
   PropertyProvider provider(this, iter, nsTextFrame::eInflated);
   // Trim trailing whitespace
   provider.InitializeForDisplay(true);
 
   gfxContext* ctx = aRenderingContext->ThebesContext();
   const bool rtl = mTextRun->IsRightToLeft();
-  const bool vertical = mTextRun->IsVertical();
+  const bool verticalRun = mTextRun->IsVertical();
+  WritingMode wm = GetWritingMode();
   const nscoord frameWidth = GetSize().width;
   gfxPoint framePt(aPt.x, aPt.y);
   gfxPoint textBaselinePt;
-  if (vertical) {
-    WritingMode wm = GetWritingMode();
-    textBaselinePt =
-      gfxPoint(wm.IsVerticalLR() ? aPt.x + mAscent
-                                 : aPt.x + frameWidth - mAscent,
+  if (verticalRun) {
+    textBaselinePt = // XXX sideways-left will need different handling here
+      gfxPoint(aPt.x + (wm.IsVerticalLR() ? mAscent : frameWidth - mAscent),
                rtl ? aPt.y + GetSize().height : aPt.y);
   } else {
     textBaselinePt = gfxPoint(rtl ? gfxFloat(aPt.x + frameWidth) : framePt.x,
              nsLayoutUtils::GetSnappedBaselineY(this, ctx, aPt.y, mAscent));
   }
   uint32_t startOffset = provider.GetStart().GetSkippedOffset();
   uint32_t maxLength = ComputeTransformedLength(provider);
   nscoord snappedLeftEdge, snappedRightEdge;
   if (!MeasureCharClippedText(provider, aItem.mLeftEdge, aItem.mRightEdge,
          &startOffset, &maxLength, &snappedLeftEdge, &snappedRightEdge)) {
     return;
   }
-  if (vertical) {
+  if (verticalRun) {
     textBaselinePt.y += rtl ? -snappedRightEdge : snappedLeftEdge;
   } else {
     textBaselinePt.x += rtl ? -snappedRightEdge : snappedLeftEdge;
   }
   nsCharClipDisplayItem::ClipEdges clipEdges(aItem, snappedLeftEdge,
                                              snappedRightEdge);
   nsTextPaintStyle textPaintStyle(this);
   textPaintStyle.SetResolveColors(!aCallbacks);
@@ -6154,42 +6179,53 @@ nsTextFrame::DrawTextRunAndDecorations(
     gfxFloat& aAdvanceWidth,
     bool aDrawSoftHyphen,
     const TextDecorations& aDecorations,
     const nscolor* const aDecorationOverrideColor,
     gfxTextContextPaint* aContextPaint,
     nsTextFrame::DrawPathCallbacks* aCallbacks)
 {
     const gfxFloat app = aTextStyle.PresContext()->AppUnitsPerDevPixel();
-    bool vertical = mTextRun->IsVertical();
+    bool verticalRun = mTextRun->IsVertical();
+    bool useVerticalMetrics = verticalRun && mTextRun->UseCenterBaseline();
 
     // XXX aFramePt is in AppUnits, shouldn't it be nsFloatPoint?
     nscoord x = NSToCoordRound(aFramePt.x);
     nscoord y = NSToCoordRound(aFramePt.y);
 
-    // 'width' here is textrun-relative, so for a vertical run it's
-    // really the height of the decoration
-    nscoord width = vertical ? GetRect().height : GetRect().width;
+    // 'measure' here is textrun-relative, so for a horizontal run it's the
+    // width, while for a vertical run it's the height of the decoration
+    const nsSize frameSize = GetSize();
+    nscoord measure = verticalRun ? frameSize.height : frameSize.width;
 
     // XXX todo: probably should have a vertical version of this...
-    if (!vertical) {
-      aClipEdges.Intersect(&x, &width);
+    if (!verticalRun) {
+      aClipEdges.Intersect(&x, &measure);
     }
 
     // decPt is the physical point where the decoration is to be drawn,
     // relative to the frame; one of its coordinates will be updated below.
     gfxPoint decPt(x / app, y / app);
-    gfxFloat& bCoord = vertical ? decPt.x : decPt.y;
-
-    // ...whereas decSize is a textrun-relative size
-    gfxSize decSize(width / app, 0);
-    const gfxFloat ascent = gfxFloat(mAscent) / app;
+    gfxFloat& bCoord = verticalRun ? decPt.x : decPt.y;
+
+    // decSize is a textrun-relative size, so its 'width' field is actually
+    // the run-relative measure, and 'height' will be the line thickness
+    gfxSize decSize(measure / app, 0);
+    gfxFloat ascent = gfxFloat(mAscent) / app;
 
     // The starting edge of the frame in block direction
-    const gfxFloat frameBStart = vertical ? aFramePt.x : aFramePt.y;
+    gfxFloat frameBStart = verticalRun ? aFramePt.x : aFramePt.y;
+
+    // In vertical-rl mode, block coordinates are measured from the right,
+    // so we need to adjust here.
+    const WritingMode wm = GetWritingMode();
+    if (wm.IsVerticalRL()) {
+      frameBStart += frameSize.width;
+      ascent = -ascent;
+    }
 
     gfxRect dirtyRect(aDirtyRect.x / app, aDirtyRect.y / app,
                       aDirtyRect.Width() / app, aDirtyRect.Height() / app);
 
     nscoord inflationMinFontSize =
       nsLayoutUtils::InflationMinFontSizeFor(this);
 
     // Underlines
@@ -6198,46 +6234,46 @@ nsTextFrame::DrawTextRunAndDecorations(
       if (dec.mStyle == NS_STYLE_TEXT_DECORATION_STYLE_NONE) {
         continue;
       }
 
       float inflation =
         GetInflationForTextDecorations(dec.mFrame, inflationMinFontSize);
       const gfxFont::Metrics metrics =
         GetFirstFontMetrics(GetFontGroupForFrame(dec.mFrame, inflation),
-                            vertical);
+                            useVerticalMetrics);
 
       decSize.height = metrics.underlineSize;
       bCoord = (frameBStart - dec.mBaselineOffset) / app;
 
       PaintDecorationLine(this, aCtx, dirtyRect, dec.mColor,
         aDecorationOverrideColor, decPt, 0.0, decSize, ascent,
         metrics.underlineOffset, NS_STYLE_TEXT_DECORATION_LINE_UNDERLINE,
-        dec.mStyle, eNormalDecoration, aCallbacks, vertical);
+        dec.mStyle, eNormalDecoration, aCallbacks, verticalRun);
     }
     // Overlines
     for (uint32_t i = aDecorations.mOverlines.Length(); i-- > 0; ) {
       const LineDecoration& dec = aDecorations.mOverlines[i];
       if (dec.mStyle == NS_STYLE_TEXT_DECORATION_STYLE_NONE) {
         continue;
       }
 
       float inflation =
         GetInflationForTextDecorations(dec.mFrame, inflationMinFontSize);
       const gfxFont::Metrics metrics =
         GetFirstFontMetrics(GetFontGroupForFrame(dec.mFrame, inflation),
-                            vertical);
+                            useVerticalMetrics);
 
       decSize.height = metrics.underlineSize;
       bCoord = (frameBStart - dec.mBaselineOffset) / app;
 
       PaintDecorationLine(this, aCtx, dirtyRect, dec.mColor,
         aDecorationOverrideColor, decPt, 0.0, decSize, ascent,
         metrics.maxAscent, NS_STYLE_TEXT_DECORATION_LINE_OVERLINE, dec.mStyle,
-        eNormalDecoration, aCallbacks, vertical);
+        eNormalDecoration, aCallbacks, verticalRun);
     }
 
     // CSS 2.1 mandates that text be painted after over/underlines, and *then*
     // line-throughs
     DrawTextRun(aCtx, aTextBaselinePt, aOffset, aLength, aProvider, aTextColor,
                 aAdvanceWidth, aDrawSoftHyphen, aContextPaint, aCallbacks);
 
     // Line-throughs
@@ -6246,25 +6282,25 @@ nsTextFrame::DrawTextRunAndDecorations(
       if (dec.mStyle == NS_STYLE_TEXT_DECORATION_STYLE_NONE) {
         continue;
       }
 
       float inflation =
         GetInflationForTextDecorations(dec.mFrame, inflationMinFontSize);
       const gfxFont::Metrics metrics =
         GetFirstFontMetrics(GetFontGroupForFrame(dec.mFrame, inflation),
-                            vertical);
+                            useVerticalMetrics);
 
       decSize.height = metrics.strikeoutSize;
       bCoord = (frameBStart - dec.mBaselineOffset) / app;
 
       PaintDecorationLine(this, aCtx, dirtyRect, dec.mColor,
         aDecorationOverrideColor, decPt, 0.0, decSize, ascent,
         metrics.strikeoutOffset, NS_STYLE_TEXT_DECORATION_LINE_LINE_THROUGH,
-        dec.mStyle, eNormalDecoration, aCallbacks, vertical);
+        dec.mStyle, eNormalDecoration, aCallbacks, verticalRun);
     }
 }
 
 void
 nsTextFrame::DrawText(
     gfxContext* const aCtx, const gfxRect& aDirtyRect,
     const gfxPoint& aFramePt, const gfxPoint& aTextBaselinePt,
     uint32_t aOffset, uint32_t aLength,
@@ -6453,19 +6489,22 @@ nsTextFrame::CombineSelectionUnderlineRe
 
   nsRect givenRect = aRect;
 
   nsRefPtr<nsFontMetrics> fm;
   nsLayoutUtils::GetFontMetricsForFrame(this, getter_AddRefs(fm),
                                         GetFontSizeInflation());
   gfxFontGroup* fontGroup = fm->GetThebesFontGroup();
   gfxFont* firstFont = fontGroup->GetFirstValidFont();
-  bool vertical = GetWritingMode().IsVertical();
+  WritingMode wm = GetWritingMode();
+  bool verticalRun = wm.IsVertical();
+  bool useVerticalMetrics = verticalRun && !wm.IsSideways();
   const gfxFont::Metrics& metrics =
-    firstFont->GetMetrics(vertical ? gfxFont::eVertical : gfxFont::eHorizontal);
+    firstFont->GetMetrics(useVerticalMetrics ? gfxFont::eVertical
+                                             : gfxFont::eHorizontal);
   gfxFloat underlineOffset = fontGroup->GetUnderlineOffset();
   gfxFloat ascent = aPresContext->AppUnitsToGfxUnits(mAscent);
   gfxFloat descentLimit =
     ComputeDescentLimitForSelectionUnderline(aPresContext, this, metrics);
 
   SelectionDetails *details = GetSelectionDetails();
   for (SelectionDetails *sd = details; sd; sd = sd->mNext) {
     if (sd->mStart == sd->mEnd || !(sd->mType & SelectionTypesWithDecorations))
@@ -6500,17 +6539,17 @@ nsTextFrame::CombineSelectionUnderlineRe
     gfxSize size(aPresContext->AppUnitsToGfxUnits(aRect.width),
                  ComputeSelectionUnderlineHeight(aPresContext,
                                                  metrics, sd->mType));
     relativeSize = std::max(relativeSize, 1.0f);
     size.height *= relativeSize;
     decorationArea =
       nsCSSRendering::GetTextDecorationRect(aPresContext, size,
         ascent, underlineOffset, NS_STYLE_TEXT_DECORATION_LINE_UNDERLINE,
-        style, vertical, descentLimit);
+        style, verticalRun, descentLimit);
     aRect.UnionRect(aRect, decorationArea);
   }
   DestroySelectionDetails(details);
 
   return !aRect.IsEmpty() && !givenRect.Contains(aRect);
 }
 
 bool
@@ -7612,17 +7651,21 @@ nsTextFrame::ComputeTightBounds(gfxConte
 
   gfxTextRun::Metrics metrics =
         mTextRun->MeasureText(provider.GetStart().GetSkippedOffset(),
                               ComputeTransformedLength(provider),
                               gfxFont::TIGHT_HINTED_OUTLINE_EXTENTS,
                               aContext, &provider);
   // mAscent should be the same as metrics.mAscent, but it's what we use to
   // paint so that's the one we'll use.
-  nsRect boundingBox = RoundOut(metrics.mBoundingBox) + nsPoint(0, mAscent);
+  nsRect boundingBox = RoundOut(metrics.mBoundingBox);
+  if (GetWritingMode().IsLineInverted()) {
+    boundingBox.y = -boundingBox.YMost();
+  }
+  boundingBox += nsPoint(0, mAscent);
   if (mTextRun->IsVertical()) {
     // Swap line-relative textMetrics dimensions to physical coordinates.
     Swap(boundingBox.x, boundingBox.y);
     Swap(boundingBox.width, boundingBox.height);
   }
   return boundingBox;
 }
 
@@ -8297,48 +8340,64 @@ nsTextFrame::ReflowText(nsLineLayout& aL
   finalSize.ISize(wm) = NSToCoordCeil(std::max(gfxFloat(0.0),
                                                textMetrics.mAdvanceWidth));
 
   if (transformedCharsFit == 0 && !usedHyphenation) {
     aMetrics.SetBlockStartAscent(0);
     finalSize.BSize(wm) = 0;
   } else if (boundingBoxType != gfxFont::LOOSE_INK_EXTENTS) {
     // Use actual text metrics for floating first letter frame.
-    aMetrics.SetBlockStartAscent(NSToCoordCeil(textMetrics.mAscent));
-    finalSize.BSize(wm) = aMetrics.BlockStartAscent() +
-      NSToCoordCeil(textMetrics.mDescent);
+    if (wm.IsLineInverted()) {
+      aMetrics.SetBlockStartAscent(NSToCoordCeil(textMetrics.mDescent));
+      finalSize.BSize(wm) = aMetrics.BlockStartAscent() +
+        NSToCoordCeil(textMetrics.mAscent);
+    } else {
+      aMetrics.SetBlockStartAscent(NSToCoordCeil(textMetrics.mAscent));
+      finalSize.BSize(wm) = aMetrics.BlockStartAscent() +
+        NSToCoordCeil(textMetrics.mDescent);
+    }
   } else {
     // Otherwise, ascent should contain the overline drawable area.
     // And also descent should contain the underline drawable area.
     // nsFontMetrics::GetMaxAscent/GetMaxDescent contains them.
     nsFontMetrics* fm = provider.GetFontMetrics();
     nscoord fontAscent = fm->MaxAscent();
     nscoord fontDescent = fm->MaxDescent();
-    aMetrics.SetBlockStartAscent(std::max(NSToCoordCeil(textMetrics.mAscent), fontAscent));
-    nscoord descent = std::max(NSToCoordCeil(textMetrics.mDescent), fontDescent);
-    finalSize.BSize(wm) = aMetrics.BlockStartAscent() + descent;
+    if (wm.IsLineInverted()) {
+      aMetrics.SetBlockStartAscent(std::max(NSToCoordCeil(textMetrics.mDescent), fontDescent));
+      nscoord descent = std::max(NSToCoordCeil(textMetrics.mAscent), fontAscent);
+      finalSize.BSize(wm) = aMetrics.BlockStartAscent() + descent;
+    } else {
+      aMetrics.SetBlockStartAscent(std::max(NSToCoordCeil(textMetrics.mAscent), fontAscent));
+      nscoord descent = std::max(NSToCoordCeil(textMetrics.mDescent), fontDescent);
+      finalSize.BSize(wm) = aMetrics.BlockStartAscent() + descent;
+    }
   }
   aMetrics.SetSize(wm, finalSize);
 
   NS_ASSERTION(aMetrics.BlockStartAscent() >= 0,
                "Negative ascent???");
   NS_ASSERTION(aMetrics.BSize(aMetrics.GetWritingMode()) -
                aMetrics.BlockStartAscent() >= 0,
                "Negative descent???");
 
   mAscent = aMetrics.BlockStartAscent();
 
   // Handle text that runs outside its normal bounds.
-  nsRect boundingBox = RoundOut(textMetrics.mBoundingBox) + nsPoint(0, mAscent);
-  aMetrics.SetOverflowAreasToDesiredBounds();
+  nsRect boundingBox = RoundOut(textMetrics.mBoundingBox);
+  if (wm.IsLineInverted()) {
+    boundingBox.y = -boundingBox.YMost();
+  }
+  boundingBox += nsPoint(0, mAscent);
   if (mTextRun->IsVertical()) {
     // Swap line-relative textMetrics dimensions to physical coordinates.
     Swap(boundingBox.x, boundingBox.y);
     Swap(boundingBox.width, boundingBox.height);
   }
+  aMetrics.SetOverflowAreasToDesiredBounds();
   aMetrics.VisualOverflow().UnionRect(aMetrics.VisualOverflow(), boundingBox);
 
   // When we have text decorations, we don't need to compute their overflow now
   // because we're guaranteed to do it later
   // (see nsLineLayout::RelativePositionFrames)
   UnionAdditionalOverflow(presContext, aLineLayout.LineContainerRS()->frame,
                           provider, &aMetrics.VisualOverflow(), false);
 
@@ -8544,28 +8603,33 @@ nsTextFrame::RecomputeOverflow(const nsH
   PropertyProvider provider(this, iter, nsTextFrame::eInflated);
   provider.InitializeForDisplay(true);
 
   gfxTextRun::Metrics textMetrics =
     mTextRun->MeasureText(provider.GetStart().GetSkippedOffset(),
                           ComputeTransformedLength(provider),
                           gfxFont::LOOSE_INK_EXTENTS, nullptr,
                           &provider);
-  nsRect &vis = result.VisualOverflow();
-  nsRect boundingBox = RoundOut(textMetrics.mBoundingBox) + nsPoint(0, mAscent);
+  nsRect boundingBox = RoundOut(textMetrics.mBoundingBox);
+  if (GetWritingMode().IsLineInverted()) {
+    boundingBox.y = -boundingBox.YMost();
+  }
+  boundingBox += nsPoint(0, mAscent);
   if (mTextRun->IsVertical()) {
     // Swap line-relative textMetrics dimensions to physical coordinates.
     Swap(boundingBox.x, boundingBox.y);
     Swap(boundingBox.width, boundingBox.height);
   }
+  nsRect &vis = result.VisualOverflow();
   vis.UnionRect(vis, boundingBox);
   UnionAdditionalOverflow(PresContext(), aBlockReflowState.frame, provider,
                           &vis, true);
   return result;
 }
+
 static char16_t TransformChar(const nsStyleText* aStyle, gfxTextRun* aTextRun,
                                uint32_t aSkippedOffset, char16_t aChar)
 {
   if (aChar == '\n') {
     return aStyle->NewlineIsSignificant() ? aChar : ' ';
   }
   if (aChar == '\t') {
     return aStyle->TabIsSignificant() ? aChar : ' ';