tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

MiddleCroppingBlockFrame.cpp (7511B)


      1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
      2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
      3 /* This Source Code Form is subject to the terms of the Mozilla Public
      4 * License, v. 2.0. If a copy of the MPL was not distributed with this
      5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      6 
      7 #include "MiddleCroppingBlockFrame.h"
      8 
      9 #include "gfxContext.h"
     10 #include "mozilla/ReflowInput.h"
     11 #include "mozilla/ReflowOutput.h"
     12 #include "mozilla/dom/Document.h"
     13 #include "mozilla/intl/Segmenter.h"
     14 #include "nsLayoutUtils.h"
     15 #include "nsLineLayout.h"
     16 #include "nsTextFrame.h"
     17 #include "nsTextNode.h"
     18 
     19 namespace mozilla {
     20 
     21 NS_QUERYFRAME_HEAD(MiddleCroppingBlockFrame)
     22  NS_QUERYFRAME_ENTRY(nsIAnonymousContentCreator)
     23  NS_QUERYFRAME_ENTRY(MiddleCroppingBlockFrame)
     24 NS_QUERYFRAME_TAIL_INHERITING(nsBlockFrame)
     25 
     26 MiddleCroppingBlockFrame::MiddleCroppingBlockFrame(ComputedStyle* aStyle,
     27                                                   nsPresContext* aPresContext,
     28                                                   ClassID aClassID)
     29    : nsBlockFrame(aStyle, aPresContext, aClassID) {}
     30 
     31 MiddleCroppingBlockFrame::~MiddleCroppingBlockFrame() = default;
     32 
     33 void MiddleCroppingBlockFrame::UpdateDisplayedValue(const nsAString& aValue,
     34                                                    bool aIsCropped,
     35                                                    bool aNotify) {
     36  auto* text = mTextNode.get();
     37  uint32_t oldLength = aNotify ? 0 : text->TextLength();
     38  text->SetText(aValue, aNotify);
     39  if (!aNotify) {
     40    // We can't notify during Reflow so we need to tell the text frame about the
     41    // text content change we just did.
     42    if (auto* textFrame = static_cast<nsTextFrame*>(text->GetPrimaryFrame())) {
     43      textFrame->NotifyNativeAnonymousTextnodeChange(oldLength);
     44    }
     45    if (LinesBegin() != LinesEnd()) {
     46      AddStateBits(NS_BLOCK_NEEDS_BIDI_RESOLUTION);
     47      LinesBegin()->MarkDirty();
     48    }
     49  }
     50  mCropped = aIsCropped;
     51 }
     52 
     53 void MiddleCroppingBlockFrame::UpdateDisplayedValueToUncroppedValue(
     54    bool aNotify) {
     55  nsAutoString value;
     56  GetUncroppedValue(value);
     57  UpdateDisplayedValue(value, /* aIsCropped = */ false, aNotify);
     58 }
     59 
     60 nscoord MiddleCroppingBlockFrame::IntrinsicISize(
     61    const IntrinsicSizeInput& aInput, IntrinsicISizeType aType) {
     62  auto* first = FirstContinuation();
     63  if (this != first) {
     64    return first->IntrinsicISize(aInput, aType);
     65  }
     66  return mCachedIntrinsics.GetOrSet(*this, aType, aInput, [&] {
     67    nsAutoString prevValue;
     68    bool restoreOldValue = false;
     69    if (mCropped) {
     70      // Make sure we measure with the uncropped value, if we're currently
     71      // cropped.
     72      mTextNode->GetNodeValue(prevValue);
     73      UpdateDisplayedValueToUncroppedValue(false);
     74      restoreOldValue = true;
     75    }
     76    // Our min inline size is the same as our pref inline size, so we always
     77    // delegate to nsBlockFrame's pref inline size.
     78    const nscoord result = nsBlockFrame::PrefISize(aInput);
     79    if (restoreOldValue) {
     80      UpdateDisplayedValue(prevValue, /* aIsCropped = */ true, false);
     81    }
     82    return result;
     83  });
     84 }
     85 
     86 bool MiddleCroppingBlockFrame::CropTextToWidth(gfxContext& aRenderingContext,
     87                                               nscoord aWidth,
     88                                               nsString& aText) const {
     89  if (aText.IsEmpty()) {
     90    return false;
     91  }
     92 
     93  RefPtr<nsFontMetrics> fm = nsLayoutUtils::GetFontMetricsForFrame(this, 1.0f);
     94 
     95  // see if the text will completely fit in the width given
     96  if (nsLayoutUtils::AppUnitWidthOfStringBidi(aText, this, *fm,
     97                                              aRenderingContext) <= aWidth) {
     98    return false;
     99  }
    100 
    101  DrawTarget* drawTarget = aRenderingContext.GetDrawTarget();
    102  const nsDependentString& kEllipsis = nsContentUtils::GetLocalizedEllipsis();
    103 
    104  // see if the width is even smaller than the ellipsis
    105  fm->SetTextRunRTL(false);
    106  const nscoord ellipsisWidth =
    107      nsLayoutUtils::AppUnitWidthOfString(kEllipsis, *fm, drawTarget);
    108  if (ellipsisWidth >= aWidth) {
    109    aText = kEllipsis;
    110    return true;
    111  }
    112 
    113  // determine how much of the string will fit in the max width
    114  nscoord totalWidth = ellipsisWidth;
    115  const Span text(aText);
    116  intl::GraphemeClusterBreakIteratorUtf16 leftIter(text);
    117  intl::GraphemeClusterBreakReverseIteratorUtf16 rightIter(text);
    118  uint32_t leftPos = 0;
    119  uint32_t rightPos = aText.Length();
    120  nsAutoString leftString, rightString;
    121 
    122  while (leftPos < rightPos) {
    123    Maybe<uint32_t> pos = leftIter.Next();
    124    Span chars = text.FromTo(leftPos, *pos);
    125    nscoord charWidth =
    126        nsLayoutUtils::AppUnitWidthOfString(chars, *fm, drawTarget);
    127    if (totalWidth + charWidth > aWidth) {
    128      break;
    129    }
    130 
    131    leftString.Append(chars);
    132    leftPos = *pos;
    133    totalWidth += charWidth;
    134 
    135    if (leftPos >= rightPos) {
    136      break;
    137    }
    138 
    139    pos = rightIter.Next();
    140    chars = text.FromTo(*pos, rightPos);
    141    charWidth = nsLayoutUtils::AppUnitWidthOfString(chars, *fm, drawTarget);
    142    if (totalWidth + charWidth > aWidth) {
    143      break;
    144    }
    145 
    146    rightString.Insert(chars, 0);
    147    rightPos = *pos;
    148    totalWidth += charWidth;
    149  }
    150 
    151  aText = leftString + kEllipsis + rightString;
    152  return true;
    153 }
    154 
    155 void MiddleCroppingBlockFrame::Reflow(nsPresContext* aPresContext,
    156                                      ReflowOutput& aDesiredSize,
    157                                      const ReflowInput& aReflowInput,
    158                                      nsReflowStatus& aStatus) {
    159  // Restore the uncropped value.
    160  nsAutoString value;
    161  GetUncroppedValue(value);
    162  bool cropped = false;
    163  while (true) {
    164    UpdateDisplayedValue(value, cropped, false);  // update the text node
    165    AddStateBits(NS_BLOCK_NEEDS_BIDI_RESOLUTION);
    166    LinesBegin()->MarkDirty();
    167    nsBlockFrame::Reflow(aPresContext, aDesiredSize, aReflowInput, aStatus);
    168    if (cropped) {
    169      break;
    170    }
    171    nscoord currentICoord = aReflowInput.mLineLayout
    172                                ? aReflowInput.mLineLayout->GetCurrentICoord()
    173                                : 0;
    174    const nscoord availSize = aReflowInput.AvailableISize() - currentICoord;
    175    const nscoord sizeToFit = std::min(aReflowInput.ComputedISize(), availSize);
    176    if (LinesBegin()->ISize() > sizeToFit) {
    177      // The value overflows - crop it and reflow again (once).
    178      if (CropTextToWidth(*aReflowInput.mRenderingContext, sizeToFit, value)) {
    179        nsBlockFrame::DidReflow(aPresContext, &aReflowInput);
    180        aStatus.Reset();
    181        MarkSubtreeDirty();
    182        AddStateBits(NS_BLOCK_NEEDS_BIDI_RESOLUTION);
    183        // FIXME(emilio): Why do we need to clear cached intrinsics, if they are
    184        // always based off our uncropped value?
    185        mCachedIntrinsics.Clear();
    186        cropped = true;
    187        continue;
    188      }
    189    }
    190    break;
    191  }
    192 }
    193 
    194 nsresult MiddleCroppingBlockFrame::CreateAnonymousContent(
    195    nsTArray<ContentInfo>& aContent) {
    196  auto* doc = PresContext()->Document();
    197  mTextNode = new (doc->NodeInfoManager()) nsTextNode(doc->NodeInfoManager());
    198  // Update the displayed text to reflect the current element's value.
    199  UpdateDisplayedValueToUncroppedValue(false);
    200  aContent.AppendElement(mTextNode);
    201  return NS_OK;
    202 }
    203 
    204 void MiddleCroppingBlockFrame::AppendAnonymousContentTo(
    205    nsTArray<nsIContent*>& aContent, uint32_t aFilter) {
    206  aContent.AppendElement(mTextNode);
    207 }
    208 
    209 void MiddleCroppingBlockFrame::Destroy(DestroyContext& aContext) {
    210  aContext.AddAnonymousContent(mTextNode.forget());
    211  nsBlockFrame::Destroy(aContext);
    212 }
    213 
    214 }  // namespace mozilla