diff --git a/gfx/thebes/gfxTextRun.cpp b/gfx/thebes/gfxTextRun.cpp index ad3fa27f943c..f3ebb74db550 100644 --- a/gfx/thebes/gfxTextRun.cpp +++ b/gfx/thebes/gfxTextRun.cpp @@ -928,7 +928,7 @@ uint32_t gfxTextRun::BreakAndMeasureText( gfxFloat aWidth, const PropertyProvider& aProvider, SuppressBreak aSuppressBreak, gfxFont::BoundingBoxType aBoundingBoxType, DrawTarget* aRefDrawTarget, bool aCanWordWrap, bool aCanWhitespaceWrap, - gfxFloat* aOutTrimmableWhitespace, Metrics& aOutMetrics, + TrimmableWS* aOutTrimmableWhitespace, Metrics& aOutMetrics, bool& aOutUsedHyphenation, uint32_t& aOutLastBreak, gfxBreakPriority& aBreakPriority) { aMaxLength = std::min(aMaxLength, GetLength() - aStart); @@ -1174,7 +1174,8 @@ uint32_t gfxTextRun::BreakAndMeasureText( aRefDrawTarget, &aProvider); if (aOutTrimmableWhitespace) { - *aOutTrimmableWhitespace = trimmableAdvance; + aOutTrimmableWhitespace->mAdvance = trimmableAdvance; + aOutTrimmableWhitespace->mCount = trimmableChars; } if (charsFit == aMaxLength) { diff --git a/gfx/thebes/gfxTextRun.h b/gfx/thebes/gfxTextRun.h index 2551b1b3ffbd..ce58b8c123cb 100644 --- a/gfx/thebes/gfxTextRun.h +++ b/gfx/thebes/gfxTextRun.h @@ -384,6 +384,13 @@ class gfxTextRun : public gfxShapedText { nsTArray& aHyphenBuffer, HyphenationState* aWordState); + // Struct used by BreakAndMeasureText to return the amount of trimmable + // trailing whitespace included in the run. + struct TrimmableWS { + mozilla::gfx::Float mAdvance = 0; + uint32_t mCount = 0; + }; + /** * Finds the longest substring that will fit into the given width. * Uses GetHyphenationBreaks and GetSpacing from aProvider. @@ -455,7 +462,7 @@ class gfxTextRun : public gfxShapedText { SuppressBreak aSuppressBreak, gfxFont::BoundingBoxType aBoundingBoxType, DrawTarget* aRefDrawTarget, bool aCanWordWrap, bool aCanWhitespaceWrap, // Output parameters: - gfxFloat* aOutTrimmableWhitespace, // may be null + TrimmableWS* aOutTrimmableWhitespace, // may be null Metrics& aOutMetrics, bool& aOutUsedHyphenation, uint32_t& aOutLastBreak, // In/out: gfxBreakPriority& aBreakPriority); diff --git a/layout/generic/nsLineLayout.cpp b/layout/generic/nsLineLayout.cpp index 263c0f41dfff..b5aefd45266c 100644 --- a/layout/generic/nsLineLayout.cpp +++ b/layout/generic/nsLineLayout.cpp @@ -3046,7 +3046,8 @@ void nsLineLayout::ExpandInlineRubyBoxes(PerSpanData* aSpan) { } } -nscoord nsLineLayout::GetHangFrom(const PerSpanData* aSpan, bool aLineIsRTL) { +nscoord nsLineLayout::GetHangFrom(const PerSpanData* aSpan, + bool aLineIsRTL) const { const PerFrameData* pfd = aSpan->mLastFrame; nscoord result = 0; while (pfd) { @@ -3079,6 +3080,36 @@ nscoord nsLineLayout::GetHangFrom(const PerSpanData* aSpan, bool aLineIsRTL) { return result; } +gfxTextRun::TrimmableWS nsLineLayout::GetTrimFrom(const PerSpanData* aSpan, + bool aLineIsRTL) const { + const PerFrameData* pfd = aSpan->mLastFrame; + while (pfd) { + if (const PerSpanData* childSpan = pfd->mSpan) { + return GetTrimFrom(childSpan, aLineIsRTL); + } + if (pfd->mIsTextFrame) { + auto* lastText = static_cast(pfd->mFrame); + auto result = lastText->GetTrimmableWS(); + if (result.mAdvance) { + lastText->EnsureTextRun(nsTextFrame::eInflated); + auto* textRun = lastText->GetTextRun(nsTextFrame::eInflated); + if (textRun && textRun->IsRightToLeft() != aLineIsRTL) { + result.mAdvance = -result.mAdvance; + } + } + return result; + } + if (!pfd->mSkipWhenTrimmingWhitespace) { + // If we hit a frame on the end that's not text and not a placeholder or + //
, then there is no trailing whitespace to trim. Stop the search. + return gfxTextRun::TrimmableWS{}; + } + // Scan back for a preceding frame whose whitespace we can trim. + pfd = pfd->mPrev; + } + return gfxTextRun::TrimmableWS{}; +} + // Align inline frames within the line according to the CSS text-align // property. void nsLineLayout::TextAlignLine(nsLineBox* aLine, bool aIsLastLine) { @@ -3106,8 +3137,15 @@ void nsLineLayout::TextAlignLine(nsLineBox* aLine, bool aIsLastLine) { // Check if there's trailing whitespace we need to "hang" at line-wrap. nscoord hang = 0; + uint32_t trimCount = 0; if (aLine->IsLineWrapped()) { - hang = GetHangFrom(mRootSpan, lineWM.IsBidiRTL()); + if (textAlign == StyleTextAlign::Justify) { + auto trim = GetTrimFrom(mRootSpan, lineWM.IsBidiRTL()); + hang = NSToCoordRound(trim.mAdvance); + trimCount = trim.mCount; + } else { + hang = GetHangFrom(mRootSpan, lineWM.IsBidiRTL()); + } } bool isSVG = LineContainerFrame()->IsInSVGTextSubtree(); @@ -3144,9 +3182,11 @@ void nsLineLayout::TextAlignLine(nsLineBox* aLine, bool aIsLastLine) { switch (textAlign) { case StyleTextAlign::Justify: { int32_t opportunities = - psd->mFrame->mJustificationInfo.mInnerOpportunities; + psd->mFrame->mJustificationInfo.mInnerOpportunities - + (hang ? trimCount : 0); if (opportunities > 0) { int32_t gaps = opportunities * 2 + additionalGaps; + remainingISize += std::abs(hang); JustificationApplicationState applyState(gaps, remainingISize); // Apply the justification, and make sure to update our linebox @@ -3154,11 +3194,29 @@ void nsLineLayout::TextAlignLine(nsLineBox* aLine, bool aIsLastLine) { aLine->ExpandBy(ApplyFrameJustification(psd, applyState), ContainerSizeForSpan(psd)); - MOZ_ASSERT(applyState.mGaps.mHandled == applyState.mGaps.mCount, + // If the trimmable trailing whitespace that we want to hang had + // reverse-inline directionality, adjust line position to account for + // it being at the inline-start side. + // On top of the original "hang" amount, justification will have + // modified its width, so we include that adjustment here. + if (hang < 0) { + dx = hang - trimCount * remainingISize / opportunities; + } + + // Gaps that belong to trimmed whitespace were not included in the + // applyState count, so we need to add them here for the assert. + DebugOnly trimmedGaps = hang ? trimCount * 2 : 0; + MOZ_ASSERT(applyState.mGaps.mHandled == + applyState.mGaps.mCount + trimmedGaps, "Unprocessed justification gaps"); - NS_ASSERTION( - applyState.mWidth.mConsumed == applyState.mWidth.mAvailable, - "Unprocessed justification width"); + // Similarly, account for the adjustment applied to the trimmed + // whitespace, which is in addition to the adjustment that applies + // within the actual width of the line. + DebugOnly trimmedAdjustment = + trimCount * remainingISize / opportunities; + NS_ASSERTION(applyState.mWidth.mConsumed == + applyState.mWidth.mAvailable + trimmedAdjustment, + "Unprocessed justification width"); break; } // Fall through to the default case if we could not justify to fill diff --git a/layout/generic/nsLineLayout.h b/layout/generic/nsLineLayout.h index 5aa9ecc403ef..1c265c9e9153 100644 --- a/layout/generic/nsLineLayout.h +++ b/layout/generic/nsLineLayout.h @@ -10,6 +10,7 @@ #define nsLineLayout_h___ #include "gfxTypes.h" +#include "gfxTextRun.h" #include "JustificationUtils.h" #include "mozilla/ArenaAllocator.h" #include "mozilla/WritingModes.h" @@ -526,7 +527,9 @@ class nsLineLayout { // Get the advance of any trailing hangable whitespace. If the whitespace // has directionality opposite to the line, the result is negated. - nscoord GetHangFrom(const PerSpanData* aSpan, bool aLineIsRTL); + nscoord GetHangFrom(const PerSpanData* aSpan, bool aLineIsRTL) const; + gfxTextRun::TrimmableWS GetTrimFrom(const PerSpanData* aSpan, + bool aLineIsRTL) const; gfxBreakPriority mLastOptionalBreakPriority; int32_t mLastOptionalBreakFrameOffset; diff --git a/layout/generic/nsTextFrame.cpp b/layout/generic/nsTextFrame.cpp index f326b1f11938..35d3c5767bb9 100644 --- a/layout/generic/nsTextFrame.cpp +++ b/layout/generic/nsTextFrame.cpp @@ -198,6 +198,8 @@ NS_DECLARE_FRAME_PROPERTY_RELEASABLE(UninflatedTextRunProperty, gfxTextRun) NS_DECLARE_FRAME_PROPERTY_SMALL_VALUE(FontSizeInflationProperty, float) NS_DECLARE_FRAME_PROPERTY_SMALL_VALUE(HangableWhitespaceProperty, nscoord) +NS_DECLARE_FRAME_PROPERTY_SMALL_VALUE(TrimmableWhitespaceProperty, + gfxTextRun::TrimmableWS) struct nsTextFrame::PaintTextSelectionParams : nsTextFrame::PaintTextParams { Point textBaselinePt; @@ -3888,7 +3890,7 @@ nsTArray* nsTextFrame::GetContinuations() { if (!mNextContinuation) { return nullptr; } - if (mHasContinuationsProperty) { + if (mPropertyFlags & PropertyFlags::Continuations) { return GetProperty(ContinuationsProperty()); } size_t count = 0; @@ -3906,7 +3908,7 @@ nsTArray* nsTextFrame::GetContinuations() { continuations = nullptr; } AddProperty(ContinuationsProperty(), continuations); - mHasContinuationsProperty = true; + mPropertyFlags |= PropertyFlags::Continuations; return continuations; } @@ -8155,20 +8157,53 @@ void nsTextFrame::SetFontSizeInflation(float aInflation) { void nsTextFrame::SetHangableISize(nscoord aISize) { MOZ_ASSERT(aISize >= 0, "unexpected negative hangable advance"); if (aISize <= 0) { - if (mHasHangableWS) { - RemoveProperty(HangableWhitespaceProperty()); - } - mHasHangableWS = false; + ClearHangableISize(); return; } SetProperty(HangableWhitespaceProperty(), aISize); - mHasHangableWS = true; + mPropertyFlags |= PropertyFlags::HangableWS; } nscoord nsTextFrame::GetHangableISize() const { - MOZ_ASSERT(mHasHangableWS == HasProperty(HangableWhitespaceProperty()), + MOZ_ASSERT(!!(mPropertyFlags & PropertyFlags::HangableWS) == + HasProperty(HangableWhitespaceProperty()), "flag/property mismatch!"); - return mHasHangableWS ? GetProperty(HangableWhitespaceProperty()) : 0; + return (mPropertyFlags & PropertyFlags::HangableWS) + ? GetProperty(HangableWhitespaceProperty()) + : 0; +} + +void nsTextFrame::ClearHangableISize() { + if (mPropertyFlags & PropertyFlags::HangableWS) { + RemoveProperty(HangableWhitespaceProperty()); + mPropertyFlags &= ~PropertyFlags::HangableWS; + } +} + +void nsTextFrame::SetTrimmableWS(gfxTextRun::TrimmableWS aTrimmableWS) { + MOZ_ASSERT(aTrimmableWS.mAdvance >= 0, "negative trimmable size"); + if (aTrimmableWS.mAdvance <= 0) { + ClearTrimmableWS(); + return; + } + SetProperty(TrimmableWhitespaceProperty(), aTrimmableWS); + mPropertyFlags |= PropertyFlags::TrimmableWS; +} + +gfxTextRun::TrimmableWS nsTextFrame::GetTrimmableWS() const { + MOZ_ASSERT(!!(mPropertyFlags & PropertyFlags::TrimmableWS) == + HasProperty(TrimmableWhitespaceProperty()), + "flag/property mismatch!"); + return (mPropertyFlags & PropertyFlags::TrimmableWS) + ? GetProperty(TrimmableWhitespaceProperty()) + : gfxTextRun::TrimmableWS{}; +} + +void nsTextFrame::ClearTrimmableWS() { + if (mPropertyFlags & PropertyFlags::TrimmableWS) { + RemoveProperty(TrimmableWhitespaceProperty()); + mPropertyFlags &= ~PropertyFlags::TrimmableWS; + } } /* virtual */ @@ -9167,7 +9202,7 @@ void nsTextFrame::ReflowText(nsLineLayout& aLineLayout, nscoord aAvailableWidth, gfxTextRun::Metrics textMetrics; uint32_t transformedLastBreak = 0; bool usedHyphenation = false; - gfxFloat trimmableWidth = 0; + gfxTextRun::TrimmableWS trimmableWS; gfxFloat availWidth = aAvailableWidth; if (Style()->IsTextCombined()) { // If text-combine-upright is 'all', we would compress whatever long @@ -9192,8 +9227,7 @@ void nsTextFrame::ReflowText(nsLineLayout& aLineLayout, nscoord aAvailableWidth, availWidth, provider, suppressBreak, boundingBoxType, aDrawTarget, textStyle->WordCanWrap(this), isBreakSpaces, // The following are output parameters: - canTrimTrailingWhitespace || whitespaceCanHang ? &trimmableWidth - : nullptr, + canTrimTrailingWhitespace || whitespaceCanHang ? &trimmableWS : nullptr, textMetrics, usedHyphenation, transformedLastBreak, // In/out breakPriority); @@ -9254,7 +9288,7 @@ void nsTextFrame::ReflowText(nsLineLayout& aLineLayout, nscoord aAvailableWidth, } bool brokeText = forceBreak >= 0 || transformedCharsFit < transformedLength; - if (trimmableWidth > 0.0) { + if (trimmableWS.mAdvance > 0.0) { if (canTrimTrailingWhitespace) { // Optimization: if we we can be sure this frame will be at end of line, // then trim the whitespace now. @@ -9262,32 +9296,41 @@ void nsTextFrame::ReflowText(nsLineLayout& aLineLayout, nscoord aAvailableWidth, // We're definitely going to break so our trailing whitespace should // definitely be trimmed. Record that we've already done it. AddStateBits(TEXT_TRIMMED_TRAILING_WHITESPACE); - textMetrics.mAdvanceWidth -= trimmableWidth; - trimmableWidth = 0.0; + textMetrics.mAdvanceWidth -= trimmableWS.mAdvance; + trimmableWS.mAdvance = 0.0; } - SetHangableISize(0); + ClearHangableISize(); + ClearTrimmableWS(); } else if (whitespaceCanHang) { // Figure out how much whitespace will hang if at end-of-line. gfxFloat hang = std::min(std::max(0.0, textMetrics.mAdvanceWidth - availWidth), - trimmableWidth); - SetHangableISize(NSToCoordRound(trimmableWidth - hang)); + gfxFloat(trimmableWS.mAdvance)); + SetHangableISize(NSToCoordRound(trimmableWS.mAdvance - hang)); + // nsLineLayout only needs the TrimmableWS property if justifying, so + // check whether this is relevant. + if (textStyle->mTextAlign == StyleTextAlign::Justify || + textStyle->mTextAlignLast == StyleTextAlignLast::Justify) { + SetTrimmableWS(trimmableWS); + } textMetrics.mAdvanceWidth -= hang; - trimmableWidth = 0.0; + trimmableWS.mAdvance = 0.0; } else { - MOZ_ASSERT_UNREACHABLE("How did trimmableWidth get set?!"); - SetHangableISize(0); - trimmableWidth = 0.0; + MOZ_ASSERT_UNREACHABLE("How did trimmableWS get set?!"); + ClearHangableISize(); + ClearTrimmableWS(); + trimmableWS.mAdvance = 0.0; } } else { - // Remove any stale frame property. - SetHangableISize(0); + // Remove any stale frame properties. + ClearHangableISize(); + ClearTrimmableWS(); } if (!brokeText && lastBreak >= 0) { // Since everything fit and no break was forced, // record the last break opportunity - NS_ASSERTION(textMetrics.mAdvanceWidth - trimmableWidth <= availWidth, + NS_ASSERTION(textMetrics.mAdvanceWidth - trimmableWS.mAdvance <= availWidth, "If the text doesn't fit, and we have a break opportunity, " "why didn't MeasureText use it?"); MOZ_ASSERT(lastBreak >= offset, "Strange break position"); @@ -9415,7 +9458,7 @@ void nsTextFrame::ReflowText(nsLineLayout& aLineLayout, nscoord aAvailableWidth, // at most one space so there's no way for trimmable width from a previous // frame to accumulate with trimmable width from this frame.) if (transformedCharsFit > 0) { - aLineLayout.SetTrimmableISize(NSToCoordFloor(trimmableWidth)); + aLineLayout.SetTrimmableISize(NSToCoordFloor(trimmableWS.mAdvance)); AddStateBits(TEXT_HAS_NONCOLLAPSED_CHARACTERS); } bool breakAfter = forceBreakAfter; @@ -9443,7 +9486,7 @@ void nsTextFrame::ReflowText(nsLineLayout& aLineLayout, nscoord aAvailableWidth, // trailing whitespace. So we need to subtract trimmableWidth here // because if we did break at this point, that much width would be // trimmed. - if (textMetrics.mAdvanceWidth - trimmableWidth > availWidth) { + if (textMetrics.mAdvanceWidth - trimmableWS.mAdvance > availWidth) { breakAfter = true; } else { aLineLayout.NotifyOptionalBreakPosition(this, length, true, diff --git a/layout/generic/nsTextFrame.h b/layout/generic/nsTextFrame.h index 51cc15c6e28c..01f0e56f82ef 100644 --- a/layout/generic/nsTextFrame.h +++ b/layout/generic/nsTextFrame.h @@ -773,6 +773,11 @@ class nsTextFrame : public nsIFrame { void SetHangableISize(nscoord aISize); nscoord GetHangableISize() const; + void ClearHangableISize(); + + void SetTrimmableWS(gfxTextRun::TrimmableWS aTrimmableWS); + gfxTextRun::TrimmableWS GetTrimmableWS() const; + void ClearTrimmableWS(); protected: virtual ~nsTextFrame(); @@ -808,13 +813,20 @@ class nsTextFrame : public nsIFrame { }; mutable SelectionState mIsSelected; - // Whether a cached continuations array is present. - bool mHasContinuationsProperty = false; + // Flags used to track whether certain properties are present. + // (Public to keep MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS happy.) + public: + enum class PropertyFlags : uint8_t { + // Whether a cached continuations array is present. + Continuations = 1 << 0, + // Whether a HangableWhitespace property is present. + HangableWS = 1 << 1, + // Whether a TrimmableWhitespace property is present. + TrimmableWS = 2 << 1, + }; - // Whether a HangableWhitespace property is present. This could have been a - // frame state bit, but they are currently full. Because we have a uint8_t - // and a bool just above, there's a hole here that we can use. - bool mHasHangableWS = false; + protected: + PropertyFlags mPropertyFlags = PropertyFlags(0); /** * Return true if the frame is part of a Selection. @@ -1000,13 +1012,7 @@ class nsTextFrame : public nsIFrame { // Clear any cached continuations array; this should be called whenever the // chain is modified. - void ClearCachedContinuations() { - MOZ_ASSERT(NS_IsMainThread()); - if (mHasContinuationsProperty) { - RemoveProperty(ContinuationsProperty()); - mHasContinuationsProperty = false; - } - } + inline void ClearCachedContinuations(); /** * UpdateIteratorFromOffset() updates the iterator from a given offset. @@ -1022,5 +1028,14 @@ class nsTextFrame : public nsIFrame { }; MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(nsTextFrame::TrimmedOffsetFlags) +MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(nsTextFrame::PropertyFlags) + +inline void nsTextFrame::ClearCachedContinuations() { + MOZ_ASSERT(NS_IsMainThread()); + if (mPropertyFlags & PropertyFlags::Continuations) { + RemoveProperty(ContinuationsProperty()); + mPropertyFlags &= ~PropertyFlags::Continuations; + } +} #endif