From 927f4cacfdcfb879025ebf74fd3714b1195d8b43 Mon Sep 17 00:00:00 2001 From: Jonathan Kew Date: Thu, 13 Nov 2014 08:58:06 +0000 Subject: [PATCH] Bug 1093553 - Improve handling of line-height metrics, block ascent, etc., in vertical writing mode. r=smontagu --- gfx/thebes/gfxFont.cpp | 62 +++++--- layout/base/nsCSSRendering.cpp | 26 ++-- layout/base/nsLayoutUtils.cpp | 12 +- layout/base/nsLayoutUtils.h | 6 +- layout/forms/nsTextControlFrame.cpp | 3 +- layout/generic/WritingModes.h | 9 +- layout/generic/nsBRFrame.cpp | 3 +- layout/generic/nsBlockFrame.cpp | 7 +- layout/generic/nsFrame.cpp | 7 +- layout/generic/nsLineLayout.cpp | 3 +- layout/generic/nsTextFrame.cpp | 214 ++++++++++++++++++---------- 11 files changed, 240 insertions(+), 112 deletions(-) diff --git a/gfx/thebes/gfxFont.cpp b/gfx/thebes/gfxFont.cpp index 490bee2574e..188e87af55c 100644 --- a/gfx/thebes/gfxFont.cpp +++ b/gfx/thebes/gfxFont.cpp @@ -1902,22 +1902,28 @@ gfxFont::Draw(gfxTextRun *aTextRun, uint32_t aStart, uint32_t aEnd, 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 contextPaint; @@ -2138,15 +2144,33 @@ gfxFont::Measure(gfxTextRun *aTextRun, // 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; } @@ -2247,6 +2271,12 @@ gfxFont::Measure(gfxTextRun *aTextRun, metrics.mBoundingBox -= gfxPoint(x, 0); } + if (baselineOffset != 0) { + metrics.mAscent -= baselineOffset; + metrics.mDescent += baselineOffset; + metrics.mBoundingBox.y += baselineOffset; + } + metrics.mAdvanceWidth = x*direction; return metrics; } diff --git a/layout/base/nsCSSRendering.cpp b/layout/base/nsCSSRendering.cpp index ca328f61e6a..f3342e36b76 100644 --- a/layout/base/nsCSSRendering.cpp +++ b/layout/base/nsCSSRendering.cpp @@ -4162,8 +4162,12 @@ nsCSSRendering::PaintDecorationLine(nsIFrame* aFrame, 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: @@ -4360,12 +4364,18 @@ nsCSSRendering::DecorationLineToPath(nsIFrame* aFrame, 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 diff --git a/layout/base/nsLayoutUtils.cpp b/layout/base/nsLayoutUtils.cpp index bb6f8e21af4..445b152edab 100644 --- a/layout/base/nsLayoutUtils.cpp +++ b/layout/base/nsLayoutUtils.cpp @@ -3496,7 +3496,8 @@ nsLayoutUtils::GetFontMetricsForStyleContext(nsStyleContext* aStyleContext, 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); } @@ -4893,9 +4894,11 @@ nsLayoutUtils::PaintTextShadow(const nsIFrame* aFrame, /* static */ nscoord nsLayoutUtils::GetCenteredFontBaseline(nsFontMetrics* aFontMetrics, - nscoord aLineHeight) + nscoord aLineHeight, + bool aIsInverted) { - nscoord fontAscent = aFontMetrics->MaxAscent(); + nscoord fontAscent = aIsInverted ? aFontMetrics->MaxDescent() + : aFontMetrics->MaxAscent(); nscoord fontHeight = aFontMetrics->MaxHeight(); nscoord leading = aLineHeight - fontHeight; @@ -7350,7 +7353,8 @@ nsLayoutUtils::SetBSizeFromFontMetrics(const nsIFrame* aFrame, // 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"); diff --git a/layout/base/nsLayoutUtils.h b/layout/base/nsLayoutUtils.h index b75f2d98f6d..a692e0b4cb9 100644 --- a/layout/base/nsLayoutUtils.h +++ b/layout/base/nsLayoutUtils.h @@ -1397,11 +1397,15 @@ public: /** * 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) diff --git a/layout/forms/nsTextControlFrame.cpp b/layout/forms/nsTextControlFrame.cpp index ea69931a390..9c5daf7850d 100644 --- a/layout/forms/nsTextControlFrame.cpp +++ b/layout/forms/nsTextControlFrame.cpp @@ -519,7 +519,8 @@ nsTextControlFrame::Reflow(nsPresContext* aPresContext, 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 diff --git a/layout/generic/WritingModes.h b/layout/generic/WritingModes.h index e95660f83ce..1b112aedd94 100644 --- a/layout/generic/WritingModes.h +++ b/layout/generic/WritingModes.h @@ -168,12 +168,17 @@ public: /** * 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 diff --git a/layout/generic/nsBRFrame.cpp b/layout/generic/nsBRFrame.cpp index 0077ee9f52a..5f6fbd47312 100644 --- a/layout/generic/nsBRFrame.cpp +++ b/layout/generic/nsBRFrame.cpp @@ -122,7 +122,8 @@ BRFrame::Reflow(nsPresContext* aPresContext, 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); diff --git a/layout/generic/nsBlockFrame.cpp b/layout/generic/nsBlockFrame.cpp index f092f1e5954..f62ff951e7e 100644 --- a/layout/generic/nsBlockFrame.cpp +++ b/layout/generic/nsBlockFrame.cpp @@ -515,7 +515,9 @@ nsBlockFrame::GetCaretBaseline() const 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; } ///////////////////////////////////////////////////////////////////////////// @@ -2552,7 +2554,8 @@ nsBlockFrame::ReflowDirtyLines(nsBlockReflowState& aState) 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()) + diff --git a/layout/generic/nsFrame.cpp b/layout/generic/nsFrame.cpp index 9670a0ae808..bcd09db3f56 100644 --- a/layout/generic/nsFrame.cpp +++ b/layout/generic/nsFrame.cpp @@ -1339,7 +1339,12 @@ 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); diff --git a/layout/generic/nsLineLayout.cpp b/layout/generic/nsLineLayout.cpp index def2786560c..c5a97137cfd 100644 --- a/layout/generic/nsLineLayout.cpp +++ b/layout/generic/nsLineLayout.cpp @@ -2098,7 +2098,8 @@ nsLineLayout::VerticalAlignFrames(PerSpanData* psd) #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; diff --git a/layout/generic/nsTextFrame.cpp b/layout/generic/nsTextFrame.cpp index 18f2542dd95..47a19661539 100644 --- a/layout/generic/nsTextFrame.cpp +++ b/layout/generic/nsTextFrame.cpp @@ -1790,13 +1790,13 @@ GetHyphenTextRun(gfxTextRun* aTextRun, gfxContext* aContext, nsTextFrame* aTextF } 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); @@ -3161,7 +3161,7 @@ ComputeTabWidthAppUnits(nsIFrame* aFrame, gfxTextRun* aTextRun) // textruns do gfxFloat spaceWidthAppUnits = NS_round(GetFirstFontMetrics(aTextRun->GetFontGroup(), - aTextRun->IsVertical()).spaceWidth * + aTextRun->UseCenterBaseline()).spaceWidth * aTextRun->GetAppUnitsPerDevUnit()); return textStyle->mTabSize * spaceWidthAppUnits; } @@ -4918,7 +4918,9 @@ nsTextFrame::UnionAdditionalOverflow(nsPresContext* aPresContext, 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 @@ -4935,11 +4937,13 @@ nsTextFrame::UnionAdditionalOverflow(nsPresContext* aPresContext, 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; @@ -4948,11 +4952,11 @@ nsTextFrame::UnionAdditionalOverflow(nsPresContext* aPresContext, 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); @@ -4972,11 +4976,16 @@ nsTextFrame::UnionAdditionalOverflow(nsPresContext* aPresContext, 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) { @@ -4993,18 +5002,23 @@ nsTextFrame::UnionAdditionalOverflow(nsPresContext* aPresContext, 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]; @@ -5020,18 +5034,23 @@ nsTextFrame::UnionAdditionalOverflow(nsPresContext* aPresContext, 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]; @@ -5047,23 +5066,29 @@ nsTextFrame::UnionAdditionalOverflow(nsPresContext* aPresContext, 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 @@ -5723,19 +5748,20 @@ nsTextFrame::PaintTextSelectionDecorations(gfxContext* aCtx, } 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; @@ -5743,7 +5769,7 @@ nsTextFrame::PaintTextSelectionDecorations(gfxContext* aCtx, 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; @@ -5757,7 +5783,7 @@ nsTextFrame::PaintTextSelectionDecorations(gfxContext* aCtx, 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 { @@ -5769,7 +5795,7 @@ nsTextFrame::PaintTextSelectionDecorations(gfxContext* aCtx, DrawSelectionDecorations(aCtx, dirtyRect, aSelectionType, this, aTextPaintStyle, selectedStyle, pt, xInFrame, width, mAscent / app, decorationMetrics, - aCallbacks, vertical); + aCallbacks, verticalRun); } iterator.UpdateWithAdvance(advance); } @@ -6013,15 +6039,14 @@ nsTextFrame::PaintText(nsRenderingContext* aRenderingContext, nsPoint aPt, 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, @@ -6034,7 +6059,7 @@ nsTextFrame::PaintText(nsRenderingContext* aRenderingContext, nsPoint aPt, &startOffset, &maxLength, &snappedLeftEdge, &snappedRightEdge)) { return; } - if (vertical) { + if (verticalRun) { textBaselinePt.y += rtl ? -snappedRightEdge : snappedLeftEdge; } else { textBaselinePt.x += rtl ? -snappedRightEdge : snappedLeftEdge; @@ -6159,32 +6184,43 @@ nsTextFrame::DrawTextRunAndDecorations( 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; + gfxFloat& bCoord = verticalRun ? decPt.x : decPt.y; - // ...whereas decSize is a textrun-relative size - gfxSize decSize(width / app, 0); - const gfxFloat ascent = gfxFloat(mAscent) / app; + // 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); @@ -6203,7 +6239,7 @@ nsTextFrame::DrawTextRunAndDecorations( GetInflationForTextDecorations(dec.mFrame, inflationMinFontSize); const gfxFont::Metrics metrics = GetFirstFontMetrics(GetFontGroupForFrame(dec.mFrame, inflation), - vertical); + useVerticalMetrics); decSize.height = metrics.underlineSize; bCoord = (frameBStart - dec.mBaselineOffset) / app; @@ -6211,7 +6247,7 @@ nsTextFrame::DrawTextRunAndDecorations( 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; ) { @@ -6224,7 +6260,7 @@ nsTextFrame::DrawTextRunAndDecorations( GetInflationForTextDecorations(dec.mFrame, inflationMinFontSize); const gfxFont::Metrics metrics = GetFirstFontMetrics(GetFontGroupForFrame(dec.mFrame, inflation), - vertical); + useVerticalMetrics); decSize.height = metrics.underlineSize; bCoord = (frameBStart - dec.mBaselineOffset) / app; @@ -6232,7 +6268,7 @@ nsTextFrame::DrawTextRunAndDecorations( 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* @@ -6251,7 +6287,7 @@ nsTextFrame::DrawTextRunAndDecorations( GetInflationForTextDecorations(dec.mFrame, inflationMinFontSize); const gfxFont::Metrics metrics = GetFirstFontMetrics(GetFontGroupForFrame(dec.mFrame, inflation), - vertical); + useVerticalMetrics); decSize.height = metrics.strikeoutSize; bCoord = (frameBStart - dec.mBaselineOffset) / app; @@ -6259,7 +6295,7 @@ nsTextFrame::DrawTextRunAndDecorations( 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); } } @@ -6458,9 +6494,12 @@ nsTextFrame::CombineSelectionUnderlineRect(nsPresContext* aPresContext, 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 = @@ -6505,7 +6544,7 @@ nsTextFrame::CombineSelectionUnderlineRect(nsPresContext* aPresContext, 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); @@ -7617,7 +7656,11 @@ nsTextFrame::ComputeTightBounds(gfxContext* aContext) const 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); @@ -8302,9 +8345,15 @@ nsTextFrame::ReflowText(nsLineLayout& aLineLayout, nscoord aAvailableWidth, 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. @@ -8312,9 +8361,15 @@ nsTextFrame::ReflowText(nsLineLayout& aLineLayout, nscoord aAvailableWidth, 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); @@ -8327,13 +8382,17 @@ nsTextFrame::ReflowText(nsLineLayout& aLineLayout, nscoord aAvailableWidth, 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 @@ -8549,18 +8608,23 @@ nsTextFrame::RecomputeOverflow(const nsHTMLReflowState& aBlockReflowState) 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) {