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/. */
7 #include "MiddleCroppingBlockFrame.h"
8 #include "nsTextFrame.h"
9 #include "nsLayoutUtils.h"
10 #include "nsTextNode.h"
11 #include "nsLineLayout.h"
12 #include "gfxContext.h"
13 #include "mozilla/dom/Document.h"
14 #include "mozilla/intl/Segmenter.h"
15 #include "mozilla/ReflowInput.h"
16 #include "mozilla/ReflowOutput.h"
20 NS_QUERYFRAME_HEAD(MiddleCroppingBlockFrame
)
21 NS_QUERYFRAME_ENTRY(nsIAnonymousContentCreator
)
22 NS_QUERYFRAME_ENTRY(MiddleCroppingBlockFrame
)
23 NS_QUERYFRAME_TAIL_INHERITING(nsBlockFrame
)
25 MiddleCroppingBlockFrame::MiddleCroppingBlockFrame(ComputedStyle
* aStyle
,
26 nsPresContext
* aPresContext
,
28 : nsBlockFrame(aStyle
, aPresContext
, aClassID
) {}
30 MiddleCroppingBlockFrame::~MiddleCroppingBlockFrame() = default;
32 void MiddleCroppingBlockFrame::UpdateDisplayedValue(const nsAString
& aValue
,
35 auto* text
= mTextNode
.get();
36 uint32_t oldLength
= aNotify
? 0 : text
->TextLength();
37 text
->SetText(aValue
, aNotify
);
39 // We can't notify during Reflow so we need to tell the text frame about the
40 // text content change we just did.
41 if (auto* textFrame
= static_cast<nsTextFrame
*>(text
->GetPrimaryFrame())) {
42 textFrame
->NotifyNativeAnonymousTextnodeChange(oldLength
);
44 if (LinesBegin() != LinesEnd()) {
45 AddStateBits(NS_BLOCK_NEEDS_BIDI_RESOLUTION
);
46 LinesBegin()->MarkDirty();
49 mCropped
= aIsCropped
;
52 void MiddleCroppingBlockFrame::UpdateDisplayedValueToUncroppedValue(
55 GetUncroppedValue(value
);
56 UpdateDisplayedValue(value
, /* aIsCropped = */ false, aNotify
);
59 nscoord
MiddleCroppingBlockFrame::GetMinISize(gfxContext
* aRenderingContext
) {
61 DISPLAY_MIN_INLINE_SIZE(this, result
);
63 // Our min inline size is our pref inline size
64 result
= GetPrefISize(aRenderingContext
);
68 nscoord
MiddleCroppingBlockFrame::GetPrefISize(gfxContext
* aRenderingContext
) {
70 DISPLAY_PREF_INLINE_SIZE(this, result
);
72 nsAutoString prevValue
;
73 bool restoreOldValue
= false;
75 // Make sure we measure with the uncropped value.
76 if (mCropped
&& mCachedPrefISize
== NS_INTRINSIC_ISIZE_UNKNOWN
) {
77 mTextNode
->GetNodeValue(prevValue
);
78 restoreOldValue
= true;
79 UpdateDisplayedValueToUncroppedValue(false);
82 result
= nsBlockFrame::GetPrefISize(aRenderingContext
);
84 if (restoreOldValue
) {
85 UpdateDisplayedValue(prevValue
, /* aIsCropped = */ true, false);
91 bool MiddleCroppingBlockFrame::CropTextToWidth(gfxContext
& aRenderingContext
,
93 nsString
& aText
) const {
94 if (aText
.IsEmpty()) {
98 RefPtr
<nsFontMetrics
> fm
= nsLayoutUtils::GetFontMetricsForFrame(this, 1.0f
);
100 // see if the text will completely fit in the width given
101 if (nsLayoutUtils::AppUnitWidthOfStringBidi(aText
, this, *fm
,
102 aRenderingContext
) <= aWidth
) {
106 DrawTarget
* drawTarget
= aRenderingContext
.GetDrawTarget();
107 const nsDependentString
& kEllipsis
= nsContentUtils::GetLocalizedEllipsis();
109 // see if the width is even smaller than the ellipsis
110 fm
->SetTextRunRTL(false);
111 const nscoord ellipsisWidth
=
112 nsLayoutUtils::AppUnitWidthOfString(kEllipsis
, *fm
, drawTarget
);
113 if (ellipsisWidth
>= aWidth
) {
118 // determine how much of the string will fit in the max width
119 nscoord totalWidth
= ellipsisWidth
;
120 const Span
text(aText
);
121 intl::GraphemeClusterBreakIteratorUtf16
leftIter(text
);
122 intl::GraphemeClusterBreakReverseIteratorUtf16
rightIter(text
);
123 uint32_t leftPos
= 0;
124 uint32_t rightPos
= aText
.Length();
125 nsAutoString leftString
, rightString
;
127 while (leftPos
< rightPos
) {
128 Maybe
<uint32_t> pos
= leftIter
.Next();
129 Span chars
= text
.FromTo(leftPos
, *pos
);
131 nsLayoutUtils::AppUnitWidthOfString(chars
, *fm
, drawTarget
);
132 if (totalWidth
+ charWidth
> aWidth
) {
136 leftString
.Append(chars
);
138 totalWidth
+= charWidth
;
140 if (leftPos
>= rightPos
) {
144 pos
= rightIter
.Next();
145 chars
= text
.FromTo(*pos
, rightPos
);
146 charWidth
= nsLayoutUtils::AppUnitWidthOfString(chars
, *fm
, drawTarget
);
147 if (totalWidth
+ charWidth
> aWidth
) {
151 rightString
.Insert(chars
, 0);
153 totalWidth
+= charWidth
;
156 aText
= leftString
+ kEllipsis
+ rightString
;
160 void MiddleCroppingBlockFrame::Reflow(nsPresContext
* aPresContext
,
161 ReflowOutput
& aDesiredSize
,
162 const ReflowInput
& aReflowInput
,
163 nsReflowStatus
& aStatus
) {
164 // Restore the uncropped value.
166 GetUncroppedValue(value
);
167 bool cropped
= false;
169 UpdateDisplayedValue(value
, cropped
, false); // update the text node
170 AddStateBits(NS_BLOCK_NEEDS_BIDI_RESOLUTION
);
171 LinesBegin()->MarkDirty();
172 nsBlockFrame::Reflow(aPresContext
, aDesiredSize
, aReflowInput
, aStatus
);
176 nscoord currentICoord
= aReflowInput
.mLineLayout
177 ? aReflowInput
.mLineLayout
->GetCurrentICoord()
179 const nscoord availSize
= aReflowInput
.AvailableISize() - currentICoord
;
180 const nscoord sizeToFit
= std::min(aReflowInput
.ComputedISize(), availSize
);
181 if (LinesBegin()->ISize() > sizeToFit
) {
182 // The value overflows - crop it and reflow again (once).
183 if (CropTextToWidth(*aReflowInput
.mRenderingContext
, sizeToFit
, value
)) {
184 nsBlockFrame::DidReflow(aPresContext
, &aReflowInput
);
187 AddStateBits(NS_BLOCK_NEEDS_BIDI_RESOLUTION
);
188 mCachedMinISize
= NS_INTRINSIC_ISIZE_UNKNOWN
;
189 mCachedPrefISize
= NS_INTRINSIC_ISIZE_UNKNOWN
;
198 nsresult
MiddleCroppingBlockFrame::CreateAnonymousContent(
199 nsTArray
<ContentInfo
>& aContent
) {
200 auto* doc
= PresContext()->Document();
201 mTextNode
= new (doc
->NodeInfoManager()) nsTextNode(doc
->NodeInfoManager());
202 // Update the displayed text to reflect the current element's value.
203 UpdateDisplayedValueToUncroppedValue(false);
204 aContent
.AppendElement(mTextNode
);
208 void MiddleCroppingBlockFrame::AppendAnonymousContentTo(
209 nsTArray
<nsIContent
*>& aContent
, uint32_t aFilter
) {
210 aContent
.AppendElement(mTextNode
);
213 void MiddleCroppingBlockFrame::Destroy(DestroyContext
& aContext
) {
214 aContext
.AddAnonymousContent(mTextNode
.forget());
215 nsBlockFrame::Destroy(aContext
);
218 } // namespace mozilla