mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-23 12:51:06 +00:00
d4e13f7543
This avoids returning wrong intrinsic values with different calls into intrinsic size computation, which is wrong by definition. We shouldn't be wallpapering over it by clearing intrinsic sizes mid-layout as the original patch was doing. Differential Revision: https://phabricator.services.mozilla.com/D229621
214 lines
7.3 KiB
C++
214 lines
7.3 KiB
C++
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
|
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
#include "MiddleCroppingBlockFrame.h"
|
|
#include "nsTextFrame.h"
|
|
#include "nsLayoutUtils.h"
|
|
#include "nsTextNode.h"
|
|
#include "nsLineLayout.h"
|
|
#include "gfxContext.h"
|
|
#include "mozilla/dom/Document.h"
|
|
#include "mozilla/intl/Segmenter.h"
|
|
#include "mozilla/ReflowInput.h"
|
|
#include "mozilla/ReflowOutput.h"
|
|
|
|
namespace mozilla {
|
|
|
|
NS_QUERYFRAME_HEAD(MiddleCroppingBlockFrame)
|
|
NS_QUERYFRAME_ENTRY(nsIAnonymousContentCreator)
|
|
NS_QUERYFRAME_ENTRY(MiddleCroppingBlockFrame)
|
|
NS_QUERYFRAME_TAIL_INHERITING(nsBlockFrame)
|
|
|
|
MiddleCroppingBlockFrame::MiddleCroppingBlockFrame(ComputedStyle* aStyle,
|
|
nsPresContext* aPresContext,
|
|
ClassID aClassID)
|
|
: nsBlockFrame(aStyle, aPresContext, aClassID) {}
|
|
|
|
MiddleCroppingBlockFrame::~MiddleCroppingBlockFrame() = default;
|
|
|
|
void MiddleCroppingBlockFrame::UpdateDisplayedValue(const nsAString& aValue,
|
|
bool aIsCropped,
|
|
bool aNotify) {
|
|
auto* text = mTextNode.get();
|
|
uint32_t oldLength = aNotify ? 0 : text->TextLength();
|
|
text->SetText(aValue, aNotify);
|
|
if (!aNotify) {
|
|
// We can't notify during Reflow so we need to tell the text frame about the
|
|
// text content change we just did.
|
|
if (auto* textFrame = static_cast<nsTextFrame*>(text->GetPrimaryFrame())) {
|
|
textFrame->NotifyNativeAnonymousTextnodeChange(oldLength);
|
|
}
|
|
if (LinesBegin() != LinesEnd()) {
|
|
AddStateBits(NS_BLOCK_NEEDS_BIDI_RESOLUTION);
|
|
LinesBegin()->MarkDirty();
|
|
}
|
|
}
|
|
mCropped = aIsCropped;
|
|
}
|
|
|
|
void MiddleCroppingBlockFrame::UpdateDisplayedValueToUncroppedValue(
|
|
bool aNotify) {
|
|
nsAutoString value;
|
|
GetUncroppedValue(value);
|
|
UpdateDisplayedValue(value, /* aIsCropped = */ false, aNotify);
|
|
}
|
|
|
|
nscoord MiddleCroppingBlockFrame::IntrinsicISize(
|
|
const IntrinsicSizeInput& aInput, IntrinsicISizeType aType) {
|
|
auto* first = FirstContinuation();
|
|
if (this != first) {
|
|
return first->IntrinsicISize(aInput, aType);
|
|
}
|
|
return mCachedIntrinsics.GetOrSet(*this, aType, aInput, [&] {
|
|
nsAutoString prevValue;
|
|
bool restoreOldValue = false;
|
|
if (mCropped) {
|
|
// Make sure we measure with the uncropped value, if we're currently
|
|
// cropped.
|
|
mTextNode->GetNodeValue(prevValue);
|
|
UpdateDisplayedValueToUncroppedValue(false);
|
|
restoreOldValue = true;
|
|
}
|
|
// Our min inline size is the same as our pref inline size, so we always
|
|
// delegate to nsBlockFrame's pref inline size.
|
|
const nscoord result = nsBlockFrame::PrefISize(aInput);
|
|
if (restoreOldValue) {
|
|
UpdateDisplayedValue(prevValue, /* aIsCropped = */ true, false);
|
|
}
|
|
return result;
|
|
});
|
|
}
|
|
|
|
bool MiddleCroppingBlockFrame::CropTextToWidth(gfxContext& aRenderingContext,
|
|
nscoord aWidth,
|
|
nsString& aText) const {
|
|
if (aText.IsEmpty()) {
|
|
return false;
|
|
}
|
|
|
|
RefPtr<nsFontMetrics> fm = nsLayoutUtils::GetFontMetricsForFrame(this, 1.0f);
|
|
|
|
// see if the text will completely fit in the width given
|
|
if (nsLayoutUtils::AppUnitWidthOfStringBidi(aText, this, *fm,
|
|
aRenderingContext) <= aWidth) {
|
|
return false;
|
|
}
|
|
|
|
DrawTarget* drawTarget = aRenderingContext.GetDrawTarget();
|
|
const nsDependentString& kEllipsis = nsContentUtils::GetLocalizedEllipsis();
|
|
|
|
// see if the width is even smaller than the ellipsis
|
|
fm->SetTextRunRTL(false);
|
|
const nscoord ellipsisWidth =
|
|
nsLayoutUtils::AppUnitWidthOfString(kEllipsis, *fm, drawTarget);
|
|
if (ellipsisWidth >= aWidth) {
|
|
aText = kEllipsis;
|
|
return true;
|
|
}
|
|
|
|
// determine how much of the string will fit in the max width
|
|
nscoord totalWidth = ellipsisWidth;
|
|
const Span text(aText);
|
|
intl::GraphemeClusterBreakIteratorUtf16 leftIter(text);
|
|
intl::GraphemeClusterBreakReverseIteratorUtf16 rightIter(text);
|
|
uint32_t leftPos = 0;
|
|
uint32_t rightPos = aText.Length();
|
|
nsAutoString leftString, rightString;
|
|
|
|
while (leftPos < rightPos) {
|
|
Maybe<uint32_t> pos = leftIter.Next();
|
|
Span chars = text.FromTo(leftPos, *pos);
|
|
nscoord charWidth =
|
|
nsLayoutUtils::AppUnitWidthOfString(chars, *fm, drawTarget);
|
|
if (totalWidth + charWidth > aWidth) {
|
|
break;
|
|
}
|
|
|
|
leftString.Append(chars);
|
|
leftPos = *pos;
|
|
totalWidth += charWidth;
|
|
|
|
if (leftPos >= rightPos) {
|
|
break;
|
|
}
|
|
|
|
pos = rightIter.Next();
|
|
chars = text.FromTo(*pos, rightPos);
|
|
charWidth = nsLayoutUtils::AppUnitWidthOfString(chars, *fm, drawTarget);
|
|
if (totalWidth + charWidth > aWidth) {
|
|
break;
|
|
}
|
|
|
|
rightString.Insert(chars, 0);
|
|
rightPos = *pos;
|
|
totalWidth += charWidth;
|
|
}
|
|
|
|
aText = leftString + kEllipsis + rightString;
|
|
return true;
|
|
}
|
|
|
|
void MiddleCroppingBlockFrame::Reflow(nsPresContext* aPresContext,
|
|
ReflowOutput& aDesiredSize,
|
|
const ReflowInput& aReflowInput,
|
|
nsReflowStatus& aStatus) {
|
|
// Restore the uncropped value.
|
|
nsAutoString value;
|
|
GetUncroppedValue(value);
|
|
bool cropped = false;
|
|
while (true) {
|
|
UpdateDisplayedValue(value, cropped, false); // update the text node
|
|
AddStateBits(NS_BLOCK_NEEDS_BIDI_RESOLUTION);
|
|
LinesBegin()->MarkDirty();
|
|
nsBlockFrame::Reflow(aPresContext, aDesiredSize, aReflowInput, aStatus);
|
|
if (cropped) {
|
|
break;
|
|
}
|
|
nscoord currentICoord = aReflowInput.mLineLayout
|
|
? aReflowInput.mLineLayout->GetCurrentICoord()
|
|
: 0;
|
|
const nscoord availSize = aReflowInput.AvailableISize() - currentICoord;
|
|
const nscoord sizeToFit = std::min(aReflowInput.ComputedISize(), availSize);
|
|
if (LinesBegin()->ISize() > sizeToFit) {
|
|
// The value overflows - crop it and reflow again (once).
|
|
if (CropTextToWidth(*aReflowInput.mRenderingContext, sizeToFit, value)) {
|
|
nsBlockFrame::DidReflow(aPresContext, &aReflowInput);
|
|
aStatus.Reset();
|
|
MarkSubtreeDirty();
|
|
AddStateBits(NS_BLOCK_NEEDS_BIDI_RESOLUTION);
|
|
// FIXME(emilio): Why do we need to clear cached intrinsics, if they are
|
|
// always based off our uncropped value?
|
|
mCachedIntrinsics.Clear();
|
|
cropped = true;
|
|
continue;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
nsresult MiddleCroppingBlockFrame::CreateAnonymousContent(
|
|
nsTArray<ContentInfo>& aContent) {
|
|
auto* doc = PresContext()->Document();
|
|
mTextNode = new (doc->NodeInfoManager()) nsTextNode(doc->NodeInfoManager());
|
|
// Update the displayed text to reflect the current element's value.
|
|
UpdateDisplayedValueToUncroppedValue(false);
|
|
aContent.AppendElement(mTextNode);
|
|
return NS_OK;
|
|
}
|
|
|
|
void MiddleCroppingBlockFrame::AppendAnonymousContentTo(
|
|
nsTArray<nsIContent*>& aContent, uint32_t aFilter) {
|
|
aContent.AppendElement(mTextNode);
|
|
}
|
|
|
|
void MiddleCroppingBlockFrame::Destroy(DestroyContext& aContext) {
|
|
aContext.AddAnonymousContent(mTextNode.forget());
|
|
nsBlockFrame::Destroy(aContext);
|
|
}
|
|
|
|
} // namespace mozilla
|