Commit 8109de85 authored by Jonathan Kew's avatar Jonathan Kew
Browse files

Bug 1253840 - patch 2 - When justifying, the full advance of any trimmable...

Bug 1253840 - patch 2 - When justifying, the full advance of any trimmable end-of-line whitespace needs to be hung into the margin. r=emilio

Depends on D178210

Differential Revision: https://phabricator.services.mozilla.com/D178211
parent d76893cc
Loading
Loading
Loading
Loading
+3 −2
Original line number Diff line number Diff line
@@ -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) {
+8 −1
Original line number Diff line number Diff line
@@ -384,6 +384,13 @@ class gfxTextRun : public gfxShapedText {
                                nsTArray<HyphenType>& 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);
+65 −7
Original line number Diff line number Diff line
@@ -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<nsTextFrame*>(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
      // <br>, 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,9 +3137,16 @@ 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()) {
    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();
  bool doTextAlign = remainingISize > 0 || hang != 0;
@@ -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,10 +3194,28 @@ 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<int32_t> 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,
          // 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<int32_t> trimmedAdjustment =
              trimCount * remainingISize / opportunities;
          NS_ASSERTION(applyState.mWidth.mConsumed ==
                           applyState.mWidth.mAvailable + trimmedAdjustment,
                       "Unprocessed justification width");
          break;
        }
+4 −1
Original line number Diff line number Diff line
@@ -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;
+70 −27
Original line number Diff line number Diff line
@@ -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*>* 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*>* 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 (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 mHasHangableWS ? GetProperty(HangableWhitespaceProperty()) : 0;
  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,
Loading