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