/* -*- 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/. */
/* state and methods used while laying out a single line of a block frame */
#include "nsLineLayout.h"
#include "mozilla/ComputedStyle.h"
#include "mozilla/SVGTextFrame.h"
#include "LayoutLogging.h"
#include "nsBidiPresUtils.h"
#include "nsBlockFrame.h"
#include "nsContainerFrame.h"
#include "nsFloatManager.h"
#include "nsFontMetrics.h"
#include "nsGkAtoms.h"
#include "nsIContent.h"
#include "nsLayoutUtils.h"
#include "nsPresContext.h"
#include "nsRubyFrame.h"
#include "nsRubyTextFrame.h"
#include "nsStyleConsts.h"
#include "nsStyleStructInlines.h"
#include "nsTextFrame.h"
#include "RubyUtils.h"
#include <algorithm>
#ifdef DEBUG
# undef NOISY_INLINEDIR_ALIGN
# undef NOISY_BLOCKDIR_ALIGN
# undef NOISY_REFLOW
# undef REALLY_NOISY_REFLOW
# undef NOISY_PUSHING
# undef REALLY_NOISY_PUSHING
# undef NOISY_CAN_PLACE_FRAME
# undef NOISY_TRIM
# undef REALLY_NOISY_TRIM
#endif
using namespace mozilla;
//----------------------------------------------------------------------
nsLineLayout::nsLineLayout(nsPresContext* aPresContext,
nsFloatManager* aFloatManager,
const ReflowInput& aLineContainerRI,
const nsLineList::iterator* aLine,
nsLineLayout* aBaseLineLayout)
: mPresContext(aPresContext),
mFloatManager(aFloatManager),
mLineContainerRI(aLineContainerRI),
mBaseLineLayout(aBaseLineLayout),
mFirstLetterStyleOK(
false),
mIsTopOfPage(
false),
mImpactedByFloats(
false),
mLastFloatWasLetterFrame(
false),
mLineIsEmpty(
false),
mLineEndsInBR(
false),
mNeedBackup(
false),
mInFirstLine(
false),
mGotLineBox(
false),
mInFirstLetter(
false),
mHasMarker(
false),
mDirtyNextLine(
false),
mLineAtStart(
false),
mHasRuby(
false),
mSuppressLineWrap(LineContainerFrame()->IsInSVGTextSubtree()),
mUsedOverflowWrap(
false) {
NS_ASSERTION(aFloatManager || LineContainerFrame()->IsLetterFrame(),
"float manager should be present");
MOZ_ASSERT(
!!mBaseLineLayout == LineContainerFrame()->IsRubyTextContainerFrame(),
"Only ruby text container frames have a different base line layout");
MOZ_COUNT_CTOR(nsLineLayout);
// Stash away some style data that we need
nsBlockFrame* blockFrame = do_QueryFrame(LineContainerFrame());
mStyleText = blockFrame ? blockFrame->StyleTextForLineLayout()
: LineContainerFrame()->StyleText();
mInflationMinFontSize =
nsLayoutUtils::InflationMinFontSizeFor(LineContainerFrame());
if (aLine) {
mGotLineBox =
true;
mLineBox = *aLine;
}
}
void nsLineLayout::BeginLineReflow(nscoord aICoord, nscoord aBCoord,
nscoord aISize, nscoord aBSize,
bool aImpactedByFloats,
bool aIsTopOfPage,
WritingMode aWritingMode,
const nsSize& aContainerSize,
nscoord aInset) {
MOZ_ASSERT(nullptr == mRootSpan,
"bad linelayout user");
LAYOUT_WARN_IF_FALSE(aISize != NS_UNCONSTRAINEDSIZE,
"have unconstrained width; this should only result from "
"very large sizes, not attempts at intrinsic width "
"calculation");
#ifdef DEBUG
if ((aISize != NS_UNCONSTRAINEDSIZE) && ABSURD_SIZE(aISize) &&
!LineContainerFrame()->GetParent()->IsAbsurdSizeAssertSuppressed()) {
LineContainerFrame()->ListTag(stdout);
printf(
": Init: bad caller: width WAS %d(0x%x)\n", aISize, aISize);
}
if ((aBSize != NS_UNCONSTRAINEDSIZE) && ABSURD_SIZE(aBSize) &&
!LineContainerFrame()->GetParent()->IsAbsurdSizeAssertSuppressed()) {
LineContainerFrame()->ListTag(stdout);
printf(
": Init: bad caller: height WAS %d(0x%x)\n", aBSize, aBSize);
}
#endif
#ifdef NOISY_REFLOW
LineContainerFrame()->ListTag(stdout);
printf(
": BeginLineReflow: %d,%d,%d,%d impacted=%s %s\n", aICoord, aBCoord,
aISize, aBSize, aImpactedByFloats ?
"true" :
"false",
aIsTopOfPage ?
"top-of-page" :
"");
#endif
#ifdef DEBUG
mSpansAllocated = mSpansFreed = mFramesAllocated = mFramesFreed = 0;
#endif
mFirstLetterStyleOK =
false;
mIsTopOfPage = aIsTopOfPage;
mImpactedByFloats = aImpactedByFloats;
mTotalPlacedFrames = 0;
if (!mBaseLineLayout) {
mLineIsEmpty =
true;
mLineAtStart =
true;
}
else {
mLineIsEmpty =
false;
mLineAtStart =
false;
}
mLineEndsInBR =
false;
mSpanDepth = 0;
mMaxStartBoxBSize = mMaxEndBoxBSize = 0;
if (mGotLineBox) {
mLineBox->ClearHasMarker();
}
PerSpanData* psd = NewPerSpanData();
mCurrentSpan = mRootSpan = psd;
psd->mReflowInput = &mLineContainerRI;
psd->mIStart = aICoord;
psd->mICoord = aICoord;
psd->mIEnd = aICoord + aISize;
// Set up inset to be used for text-wrap:balance implementation, but only if
// the available size is greater than inset.
psd->mInset = aISize > aInset ? aInset : 0;
mContainerSize = aContainerSize;
mBStartEdge = aBCoord;
psd->mNoWrap = !mStyleText->WhiteSpaceCanWrapStyle() || mSuppressLineWrap;
psd->mWritingMode = aWritingMode;
// Determine if this is the first line of the block (or first after a hard
// line-break, if `each-line` is in effect).
nsIFrame* containerFrame = LineContainerFrame();
if (!containerFrame->IsRubyTextContainerFrame()) {
bool isFirstLineOrAfterHardBreak = [&] {
if (mLineNumber > 0) {
return mStyleText->mTextIndent.each_line && GetLine() &&
!GetLine()->prev()->IsLineWrapped();
}
if (nsBlockFrame* prevBlock =
do_QueryFrame(containerFrame->GetPrevInFlow())) {
return mStyleText->mTextIndent.each_line &&
(prevBlock->Lines().empty() ||
!prevBlock->LinesEnd().prev()->IsLineWrapped());
}
return true;
}();
// Resolve and apply the text-indent value if this line requires it.
// The `hanging` option inverts which lines are to be indented.
if (isFirstLineOrAfterHardBreak != mStyleText->mTextIndent.hanging) {
nscoord pctBasis = mLineContainerRI.ComputedISize();
mTextIndent = mStyleText->mTextIndent.length.Resolve(pctBasis);
psd->mICoord += mTextIndent;
}
}
PerFrameData* pfd = NewPerFrameData(containerFrame);
pfd->mAscent = 0;
pfd->mSpan = psd;
psd->mFrame = pfd;
if (containerFrame->IsRubyTextContainerFrame()) {
// Ruby text container won't be reflowed via ReflowFrame, hence the
// relative positioning information should be recorded here.
MOZ_ASSERT(mBaseLineLayout !=
this);
pfd->mIsRelativelyOrStickyPos =
mLineContainerRI.mStyleDisplay->IsRelativelyOrStickyPositionedStyle();
if (pfd->mIsRelativelyOrStickyPos) {
MOZ_ASSERT(mLineContainerRI.GetWritingMode() == pfd->mWritingMode,
"mLineContainerRI.frame == frame, "
"hence they should have identical writing mode");
pfd->mOffsets =
mLineContainerRI.ComputedLogicalOffsets(pfd->mWritingMode);
}
}
}
bool nsLineLayout::EndLineReflow() {
#ifdef NOISY_REFLOW
LineContainerFrame()->ListTag(stdout);
printf(
": EndLineReflow: width=%d\n",
mRootSpan->mICoord - mRootSpan->mIStart);
#endif
NS_ASSERTION(!mBaseLineLayout ||
(!mSpansAllocated && !mSpansFreed && !mSpanFreeList &&
!mFramesAllocated && !mFramesFreed && !mFrameFreeList),
"Allocated frames or spans on non-base line layout?");
MOZ_ASSERT(mRootSpan == mCurrentSpan);
UnlinkFrame(mRootSpan->mFrame);
mCurrentSpan = mRootSpan = nullptr;
NS_ASSERTION(mSpansAllocated == mSpansFreed,
"leak");
NS_ASSERTION(mFramesAllocated == mFramesFreed,
"leak");
#if 0
static int32_t maxSpansAllocated = NS_LINELAYOUT_NUM_SPANS;
static int32_t maxFramesAllocated = NS_LINELAYOUT_NUM_FRAMES;
if (mSpansAllocated > maxSpansAllocated) {
printf(
"XXX: saw a line with %d spans\n", mSpansAllocated);
maxSpansAllocated = mSpansAllocated;
}
if (mFramesAllocated > maxFramesAllocated) {
printf(
"XXX: saw a line with %d frames\n", mFramesAllocated);
maxFramesAllocated = mFramesAllocated;
}
#endif
return mUsedOverflowWrap;
}
// XXX swtich to a single mAvailLineWidth that we adjust as each frame
// on the line is placed. Each span can still have a per-span mICoord that
// tracks where a child frame is going in its span; they don't need a
// per-span mIStart?
void nsLineLayout::UpdateBand(WritingMode aWM,
const LogicalRect& aNewAvailSpace,
nsIFrame* aFloatFrame) {
WritingMode lineWM = mRootSpan->mWritingMode;
// need to convert to our writing mode, because we might have a different
// mode from the caller due to dir: auto
LogicalRect availSpace =
aNewAvailSpace.ConvertTo(lineWM, aWM, ContainerSize());
#ifdef REALLY_NOISY_REFLOW
printf(
"nsLL::UpdateBand %d, %d, %d, %d, (converted to %d, %d, %d, %d); "
"frame=%p\n will set mImpacted to true\n",
aNewAvailSpace.IStart(aWM), aNewAvailSpace.BStart(aWM),
aNewAvailSpace.ISize(aWM), aNewAvailSpace.BSize(aWM),
availSpace.IStart(lineWM), availSpace.BStart(lineWM),
availSpace.ISize(lineWM), availSpace.BSize(lineWM), aFloatFrame);
#endif
#ifdef DEBUG
if ((availSpace.ISize(lineWM) != NS_UNCONSTRAINEDSIZE) &&
ABSURD_SIZE(availSpace.ISize(lineWM)) &&
!LineContainerFrame()->GetParent()->IsAbsurdSizeAssertSuppressed()) {
LineContainerFrame()->ListTag(stdout);
printf(
": UpdateBand: bad caller: ISize WAS %d(0x%x)\n",
availSpace.ISize(lineWM), availSpace.ISize(lineWM));
}
if ((availSpace.BSize(lineWM) != NS_UNCONSTRAINEDSIZE) &&
ABSURD_SIZE(availSpace.BSize(lineWM)) &&
!LineContainerFrame()->GetParent()->IsAbsurdSizeAssertSuppressed()) {
LineContainerFrame()->ListTag(stdout);
printf(
": UpdateBand: bad caller: BSize WAS %d(0x%x)\n",
availSpace.BSize(lineWM), availSpace.BSize(lineWM));
}
#endif
// Compute the difference between last times width and the new width
NS_WARNING_ASSERTION(
mRootSpan->mIEnd != NS_UNCONSTRAINEDSIZE &&
availSpace.ISize(lineWM) != NS_UNCONSTRAINEDSIZE,
"have unconstrained inline size; this should only result from very large "
"sizes, not attempts at intrinsic width calculation");
// The root span's mIStart moves to aICoord
nscoord deltaICoord = availSpace.IStart(lineWM) - mRootSpan->mIStart;
// The inline size of all spans changes by this much (the root span's
// mIEnd moves to aICoord + aISize, its new inline size is aISize)
nscoord deltaISize =
availSpace.ISize(lineWM) - (mRootSpan->mIEnd - mRootSpan->mIStart);
#ifdef NOISY_REFLOW
LineContainerFrame()->ListTag(stdout);
printf(
": UpdateBand: %d,%d,%d,%d deltaISize=%d deltaICoord=%d\n",
availSpace.IStart(lineWM), availSpace.BStart(lineWM),
availSpace.ISize(lineWM), availSpace.BSize(lineWM), deltaISize,
deltaICoord);
#endif
// Update the root span position
mRootSpan->mIStart += deltaICoord;
mRootSpan->mIEnd += deltaICoord;
mRootSpan->mICoord += deltaICoord;
// Now update the right edges of the open spans to account for any
// change in available space width
for (PerSpanData* psd = mCurrentSpan; psd; psd = psd->mParent) {
psd->mIEnd += deltaISize;
psd->mContainsFloat =
true;
#ifdef NOISY_REFLOW
printf(
" span %p: oldIEnd=%d newIEnd=%d\n", psd, psd->mIEnd - deltaISize,
psd->mIEnd);
#endif
}
NS_ASSERTION(mRootSpan->mContainsFloat &&
mRootSpan->mIStart == availSpace.IStart(lineWM) &&
mRootSpan->mIEnd == availSpace.IEnd(lineWM),
"root span was updated incorrectly?");
// Update frame bounds
// Note: Only adjust the outermost frames (the ones that are direct
// children of the block), not the ones in the child spans. The reason
// is simple: the frames in the spans have coordinates local to their
// parent therefore they are moved when their parent span is moved.
if (deltaICoord != 0) {
for (PerFrameData* pfd = mRootSpan->mFirstFrame; pfd; pfd = pfd->mNext) {
pfd->mBounds.IStart(lineWM) += deltaICoord;
}
}
mBStartEdge = availSpace.BStart(lineWM);
mImpactedByFloats =
true;
mLastFloatWasLetterFrame = aFloatFrame->IsLetterFrame();
}
nsLineLayout::PerSpanData* nsLineLayout::NewPerSpanData() {
nsLineLayout* outerLineLayout = GetOutermostLineLayout();
PerSpanData* psd = outerLineLayout->mSpanFreeList;
if (!psd) {
void* mem = outerLineLayout->mArena.Allocate(
sizeof(PerSpanData));
psd =
reinterpret_cast<PerSpanData*>(mem);
}
else {
outerLineLayout->mSpanFreeList = psd->mNextFreeSpan;
}
psd->mParent = nullptr;
psd->mFrame = nullptr;
psd->mFirstFrame = nullptr;
psd->mLastFrame = nullptr;
psd->mReflowInput = nullptr;
psd->mContainsFloat =
false;
psd->mHasNonemptyContent =
false;
psd->mBaseline = nullptr;
#ifdef DEBUG
outerLineLayout->mSpansAllocated++;
#endif
return psd;
}
void nsLineLayout::BeginSpan(nsIFrame* aFrame,
const ReflowInput* aSpanReflowInput,
nscoord aIStart, nscoord aIEnd,
nscoord* aBaseline) {
NS_ASSERTION(aIEnd != NS_UNCONSTRAINEDSIZE,
"should no longer be using unconstrained sizes");
#ifdef NOISY_REFLOW
nsIFrame::IndentBy(stdout, mSpanDepth + 1);
aFrame->ListTag(stdout);
printf(
": BeginSpan leftEdge=%d rightEdge=%d\n", aIStart, aIEnd);
#endif
PerSpanData* psd = NewPerSpanData();
// Link up span frame's pfd to point to its child span data
PerFrameData* pfd = mCurrentSpan->mLastFrame;
NS_ASSERTION(pfd->mFrame == aFrame,
"huh?");
pfd->mSpan = psd;
// Init new span
psd->mFrame = pfd;
psd->mParent = mCurrentSpan;
psd->mReflowInput = aSpanReflowInput;
psd->mIStart = aIStart;
psd->mICoord = aIStart;
psd->mIEnd = aIEnd;
psd->mInset = 0;
// inset applies only to the root span
psd->mBaseline = aBaseline;
nsIFrame* frame = aSpanReflowInput->mFrame;
psd->mNoWrap = !frame->StyleText()->WhiteSpaceCanWrap(frame) ||
mSuppressLineWrap || frame->Style()->ShouldSuppressLineBreak();
psd->mWritingMode = aSpanReflowInput->GetWritingMode();
// Switch to new span
mCurrentSpan = psd;
mSpanDepth++;
}
nscoord nsLineLayout::EndSpan(nsIFrame* aFrame) {
NS_ASSERTION(mSpanDepth > 0,
"end-span without begin-span");
#ifdef NOISY_REFLOW
nsIFrame::IndentBy(stdout, mSpanDepth);
aFrame->ListTag(stdout);
printf(
": EndSpan width=%d\n", mCurrentSpan->mICoord - mCurrentSpan->mIStart);
#endif
PerSpanData* psd = mCurrentSpan;
MOZ_ASSERT(psd->mParent,
"We never call this on the root");
if (psd->mNoWrap && !psd->mParent->mNoWrap) {
FlushNoWrapFloats();
}
nscoord iSizeResult = psd->mLastFrame ? (psd->mICoord - psd->mIStart) : 0;
mSpanDepth--;
mCurrentSpan->mReflowInput = nullptr;
// no longer valid so null it out!
mCurrentSpan = mCurrentSpan->mParent;
return iSizeResult;
}
void nsLineLayout::AttachFrameToBaseLineLayout(PerFrameData* aFrame) {
MOZ_ASSERT(mBaseLineLayout,
"This method must not be called in a base line layout.");
PerFrameData* baseFrame = mBaseLineLayout->LastFrame();
MOZ_ASSERT(aFrame && baseFrame);
MOZ_ASSERT(!aFrame->mIsLinkedToBase,
"The frame must not have been linked with the base");
#ifdef DEBUG
LayoutFrameType baseType = baseFrame->mFrame->Type();
LayoutFrameType annotationType = aFrame->mFrame->Type();
MOZ_ASSERT((baseType == LayoutFrameType::RubyBaseContainer &&
annotationType == LayoutFrameType::RubyTextContainer) ||
(baseType == LayoutFrameType::RubyBase &&
annotationType == LayoutFrameType::RubyText));
#endif
aFrame->mNextAnnotation = baseFrame->mNextAnnotation;
baseFrame->mNextAnnotation = aFrame;
aFrame->mIsLinkedToBase =
true;
}
int32_t nsLineLayout::GetCurrentSpanCount()
const {
MOZ_ASSERT(mCurrentSpan == mRootSpan,
"bad linelayout user");
int32_t count = 0;
for (
const auto* pfd = mRootSpan->mFirstFrame; pfd; pfd = pfd->mNext) {
count++;
}
return count;
}
void nsLineLayout::SplitLineTo(int32_t aNewCount) {
MOZ_ASSERT(mCurrentSpan == mRootSpan,
"bad linelayout user");
#ifdef REALLY_NOISY_PUSHING
printf(
"SplitLineTo %d (current count=%d); before:\n", aNewCount,
GetCurrentSpanCount());
DumpPerSpanData(mRootSpan, 1);
#endif
for (
auto* pfd = mRootSpan->mFirstFrame; pfd; pfd = pfd->mNext) {
if (--aNewCount == 0) {
// Truncate list at pfd (we keep pfd, but anything following is freed)
PerFrameData* next = pfd->mNext;
pfd->mNext = nullptr;
mRootSpan->mLastFrame = pfd;
// Now unlink all of the frames following pfd
UnlinkFrame(next);
break;
}
}
#ifdef NOISY_PUSHING
printf(
"SplitLineTo %d (current count=%d); after:\n", aNewCount,
GetCurrentSpanCount());
DumpPerSpanData(mRootSpan, 1);
#endif
}
void nsLineLayout::PushFrame(nsIFrame* aFrame) {
PerSpanData* psd = mCurrentSpan;
NS_ASSERTION(psd->mLastFrame->mFrame == aFrame,
"pushing non-last frame");
#ifdef REALLY_NOISY_PUSHING
nsIFrame::IndentBy(stdout, mSpanDepth);
printf(
"PushFrame %p, before:\n", psd);
DumpPerSpanData(psd, 1);
#endif
// Take the last frame off of the span's frame list
PerFrameData* pfd = psd->mLastFrame;
if (pfd == psd->mFirstFrame) {
// We are pushing away the only frame...empty the list
psd->mFirstFrame = nullptr;
psd->mLastFrame = nullptr;
}
else {
PerFrameData* prevFrame = pfd->mPrev;
prevFrame->mNext = nullptr;
psd->mLastFrame = prevFrame;
}
// Now unlink the frame
MOZ_ASSERT(!pfd->mNext);
UnlinkFrame(pfd);
#ifdef NOISY_PUSHING
nsIFrame::IndentBy(stdout, mSpanDepth);
printf(
"PushFrame: %p after:\n", psd);
DumpPerSpanData(psd, 1);
#endif
}
void nsLineLayout::UnlinkFrame(PerFrameData* pfd) {
while (nullptr != pfd) {
PerFrameData* next = pfd->mNext;
if (pfd->mIsLinkedToBase) {
// This frame is linked to a ruby base, and should not be freed
// now. Just unlink it from the span. It will be freed when its
// base frame gets unlinked.
pfd->mNext = pfd->mPrev = nullptr;
pfd = next;
continue;
}
// It is a ruby base frame. If there are any annotations
// linked to this frame, free them first.
PerFrameData* annotationPFD = pfd->mNextAnnotation;
while (annotationPFD) {
PerFrameData* nextAnnotation = annotationPFD->mNextAnnotation;
MOZ_ASSERT(
annotationPFD->mNext == nullptr && annotationPFD->mPrev == nullptr,
"PFD in annotations should have been unlinked.");
FreeFrame(annotationPFD);
annotationPFD = nextAnnotation;
}
FreeFrame(pfd);
pfd = next;
}
}
void nsLineLayout::FreeFrame(PerFrameData* pfd) {
if (nullptr != pfd->mSpan) {
FreeSpan(pfd->mSpan);
}
nsLineLayout* outerLineLayout = GetOutermostLineLayout();
pfd->mNext = outerLineLayout->mFrameFreeList;
outerLineLayout->mFrameFreeList = pfd;
#ifdef DEBUG
outerLineLayout->mFramesFreed++;
#endif
}
void nsLineLayout::FreeSpan(PerSpanData* psd) {
// Unlink its frames
UnlinkFrame(psd->mFirstFrame);
nsLineLayout* outerLineLayout = GetOutermostLineLayout();
// Now put the span on the free list since it's free too
psd->mNextFreeSpan = outerLineLayout->mSpanFreeList;
outerLineLayout->mSpanFreeList = psd;
#ifdef DEBUG
outerLineLayout->mSpansFreed++;
#endif
}
bool nsLineLayout::IsZeroBSize()
const {
for (
const auto* pfd = mCurrentSpan->mFirstFrame; pfd; pfd = pfd->mNext) {
if (0 != pfd->mBounds.BSize(mCurrentSpan->mWritingMode)) {
return false;
}
}
return true;
}
nsLineLayout::PerFrameData* nsLineLayout::NewPerFrameData(nsIFrame* aFrame) {
nsLineLayout* outerLineLayout = GetOutermostLineLayout();
PerFrameData* pfd = outerLineLayout->mFrameFreeList;
if (!pfd) {
void* mem = outerLineLayout->mArena.Allocate(
sizeof(PerFrameData));
pfd =
reinterpret_cast<PerFrameData*>(mem);
}
else {
outerLineLayout->mFrameFreeList = pfd->mNext;
}
pfd->mSpan = nullptr;
pfd->mNext = nullptr;
pfd->mPrev = nullptr;
pfd->mNextAnnotation = nullptr;
pfd->mFrame = aFrame;
// all flags default to false
pfd->mIsRelativelyOrStickyPos =
false;
pfd->mIsTextFrame =
false;
pfd->mIsNonEmptyTextFrame =
false;
pfd->mIsNonWhitespaceTextFrame =
false;
pfd->mIsLetterFrame =
false;
pfd->mRecomputeOverflow =
false;
pfd->mIsMarker =
false;
pfd->mSkipWhenTrimmingWhitespace =
false;
pfd->mIsEmpty =
false;
pfd->mIsPlaceholder =
false;
pfd->mIsLinkedToBase =
false;
pfd->mWritingMode = aFrame->GetWritingMode();
WritingMode lineWM = mRootSpan->mWritingMode;
pfd->mBounds = LogicalRect(lineWM);
pfd->mOverflowAreas.Clear();
pfd->mMargin = LogicalMargin(lineWM);
pfd->mBorderPadding = LogicalMargin(lineWM);
pfd->mOffsets = LogicalMargin(pfd->mWritingMode);
pfd->mJustificationInfo = JustificationInfo();
pfd->mJustificationAssignment = JustificationAssignment();
#ifdef DEBUG
pfd->mBlockDirAlign = 0xFF;
outerLineLayout->mFramesAllocated++;
#endif
return pfd;
}
// Checks all four sides for percentage units. This means it should
// only be used for things (margin, padding) where percentages on top
// and bottom depend on the *width* just like percentages on left and
// right.
template <
typename T>
static bool HasPercentageUnitSide(
const StyleRect<T>& aSides) {
return aSides.Any([](
const auto& aLength) {
return aLength.HasPercent(); });
}
static bool HasPercentageUnitMargin(
const nsStyleMargin& aStyleMargin) {
for (
const auto side : AllPhysicalSides()) {
if (aStyleMargin.GetMargin(side).HasPercent()) {
return true;
}
}
return false;
}
static bool IsPercentageAware(
const nsIFrame* aFrame, WritingMode aWM) {
MOZ_ASSERT(aFrame,
"null frame is not allowed");
LayoutFrameType fType = aFrame->Type();
if (fType == LayoutFrameType::Text) {
// None of these things can ever be true for text frames.
return false;
}
// Some of these things don't apply to non-replaced inline frames
// (that is, fType == LayoutFrameType::Inline), but we won't bother making
// things unnecessarily complicated, since they'll probably be set
// quite rarely.
const nsStyleMargin* margin = aFrame->StyleMargin();
if (HasPercentageUnitMargin(*margin)) {
return true;
}
const nsStylePadding* padding = aFrame->StylePadding();
if (HasPercentageUnitSide(padding->mPadding)) {
return true;
}
// Note that borders can't be aware of percentages
const nsStylePosition* pos = aFrame->StylePosition();
if ((pos->ISizeDependsOnContainer(aWM) && !pos->ISize(aWM).IsAuto()) ||
pos->MaxISizeDependsOnContainer(aWM) ||
pos->MinISizeDependsOnContainer(aWM) ||
pos->mOffset.Get(LogicalSide::IStart, aWM).MaybePercentageAware() ||
pos->mOffset.Get(LogicalSide::IEnd, aWM).MaybePercentageAware()) {
return true;
}
if (pos->ISize(aWM).IsAuto()) {
// We need to check for frames that shrink-wrap when they're auto
// width.
const nsStyleDisplay* disp = aFrame->StyleDisplay();
if ((disp->DisplayOutside() == StyleDisplayOutside::
Inline &&
(disp->DisplayInside() == StyleDisplayInside::FlowRoot ||
disp->DisplayInside() == StyleDisplayInside::Table)) ||
fType == LayoutFrameType::HTMLButtonControl ||
fType == LayoutFrameType::GfxButtonControl ||
fType == LayoutFrameType::FieldSet) {
return true;
}
// Per CSS 2.1, section 10.3.2:
// If 'height' and 'width' both have computed values of 'auto' and
// the element has an intrinsic ratio but no intrinsic height or
// width and the containing block's width does not itself depend
// on the replaced element's width, then the used value of 'width'
// is calculated from the constraint equation used for
// block-level, non-replaced elements in normal flow.
nsIFrame* f =
const_cast<nsIFrame*>(aFrame);
if (f->GetAspectRatio() &&
// Some percents are treated like 'auto', so check != coord
!pos->BSize(aWM).ConvertsToLength()) {
const IntrinsicSize& intrinsicSize = f->GetIntrinsicSize();
if (!intrinsicSize.width && !intrinsicSize.height) {
return true;
}
}
}
return false;
}
void nsLineLayout::ReflowFrame(nsIFrame* aFrame, nsReflowStatus& aReflowStatus,
ReflowOutput* aMetrics,
bool& aPushedFrame) {
// Initialize OUT parameter
aPushedFrame =
false;
PerFrameData* pfd = NewPerFrameData(aFrame);
PerSpanData* psd = mCurrentSpan;
psd->AppendFrame(pfd);
#ifdef REALLY_NOISY_REFLOW
nsIFrame::IndentBy(stdout, mSpanDepth);
printf(
"%p: Begin ReflowFrame pfd=%p ", psd, pfd);
aFrame->ListTag(stdout);
printf(
"\n");
#endif
if (mCurrentSpan == mRootSpan) {
pfd->mFrame->RemoveProperty(nsIFrame::LineBaselineOffset());
}
else {
#ifdef DEBUG
bool hasLineOffset;
pfd->mFrame->GetProperty(nsIFrame::LineBaselineOffset(), &hasLineOffset);
NS_ASSERTION(!hasLineOffset,
"LineBaselineOffset was set but was not expected");
#endif
}
mJustificationInfo = JustificationInfo();
// Stash copies of some of the computed state away for later
// (block-direction alignment, for example)
WritingMode frameWM = pfd->mWritingMode;
WritingMode lineWM = mRootSpan->mWritingMode;
// NOTE: While the inline direction coordinate remains relative to the
// parent span, the block direction coordinate is fixed at the top
// edge for the line. During VerticalAlignFrames we will repair this
// so that the block direction coordinate is properly set and relative
// to the appropriate span.
pfd->mBounds.IStart(lineWM) = psd->mICoord;
pfd->mBounds.BStart(lineWM) = mBStartEdge;
// We want to guarantee that we always make progress when
// formatting. Therefore, if the object being placed on the line is
// too big for the line, but it is the only thing on the line and is not
// impacted by a float, then we go ahead and place it anyway. (If the line
// is impacted by one or more floats, then it is safe to break because
// we can move the line down below float(s).)
//
// Capture this state *before* we reflow the frame in case it clears
// the state out. We need to know how to treat the current frame
// when breaking.
bool notSafeToBreak = LineIsEmpty() && !mImpactedByFloats;
// Figure out whether we're talking about a textframe here
LayoutFrameType frameType = aFrame->Type();
const bool isText = frameType == LayoutFrameType::Text;
// Inline-ish and text-ish things don't compute their width;
// everything else does. We need to give them an available width that
// reflects the space left on the line.
LAYOUT_WARN_IF_FALSE(psd->mIEnd != NS_UNCONSTRAINEDSIZE,
"have unconstrained width; this should only result from "
"very large sizes, not attempts at intrinsic width "
"calculation");
nscoord availableSpaceOnLine = psd->mIEnd - psd->mICoord - psd->mInset;
// Setup reflow input for reflowing the frame
Maybe<ReflowInput> reflowInputHolder;
if (!isText) {
// Compute the available size for the frame. This available width
// includes room for the side margins.
// For now, set the available block-size to unconstrained always.
LogicalSize availSize = mLineContainerRI.ComputedSize(frameWM);
availSize.BSize(frameWM) = NS_UNCONSTRAINEDSIZE;
reflowInputHolder.emplace(mPresContext, *psd->mReflowInput, aFrame,
availSize);
ReflowInput& reflowInput = *reflowInputHolder;
reflowInput.mLineLayout =
this;
reflowInput.mFlags.mIsTopOfPage = mIsTopOfPage;
if (reflowInput.ComputedISize() == NS_UNCONSTRAINEDSIZE) {
reflowInput.SetAvailableISize(availableSpaceOnLine);
}
pfd->mMargin = reflowInput.ComputedLogicalMargin(lineWM);
pfd->mBorderPadding = reflowInput.ComputedLogicalBorderPadding(lineWM);
pfd->mIsRelativelyOrStickyPos =
reflowInput.mStyleDisplay->IsRelativelyOrStickyPositionedStyle();
if (pfd->mIsRelativelyOrStickyPos) {
pfd->mOffsets = reflowInput.ComputedLogicalOffsets(frameWM);
}
// Calculate whether the the frame should have a start margin and
// subtract the margin from the available width if necessary.
// The margin will be applied to the starting inline coordinates of
// the frame in CanPlaceFrame() after reflowing the frame.
AllowForStartMargin(pfd, reflowInput);
}
// if isText(), no need to propagate NS_FRAME_IS_DIRTY from the parent,
// because reflow doesn't look at the dirty bits on the frame being reflowed.
// See if this frame depends on the inline-size of its containing block.
// If so, disable resize reflow optimizations for the line. (Note that,
// to be conservative, we do this if we *try* to fit a frame on a
// line, even if we don't succeed.) (Note also that we can only make
// this IsPercentageAware check *after* we've constructed our
// ReflowInput, because that construction may be what forces aFrame
// to lazily initialize its (possibly-percent-valued) intrinsic size.)
if (mGotLineBox && IsPercentageAware(aFrame, lineWM)) {
mLineBox->DisableResizeReflowOptimization();
}
// Note that we don't bother positioning the frame yet, because we're probably
// going to end up moving it when we do the block-direction alignment.
// Adjust float manager coordinate system for the frame.
ReflowOutput reflowOutput(lineWM);
#ifdef DEBUG
reflowOutput.ISize(lineWM) = nscoord(0xdeadbeef);
reflowOutput.BSize(lineWM) = nscoord(0xdeadbeef);
#endif
nscoord tI = pfd->mBounds.LineLeft(lineWM, ContainerSize());
nscoord tB = pfd->mBounds.BStart(lineWM);
mFloatManager->Translate(tI, tB);
int32_t savedOptionalBreakOffset;
gfxBreakPriority savedOptionalBreakPriority;
nsIFrame* savedOptionalBreakFrame = GetLastOptionalBreakPosition(
&savedOptionalBreakOffset, &savedOptionalBreakPriority);
if (!isText) {
aFrame->Reflow(mPresContext, reflowOutput, *reflowInputHolder,
aReflowStatus);
}
else {
static_cast<nsTextFrame*>(aFrame)->ReflowText(
*
this, availableSpaceOnLine,
psd->mReflowInput->mRenderingContext->GetDrawTarget(), reflowOutput,
aReflowStatus);
}
pfd->mJustificationInfo = mJustificationInfo;
mJustificationInfo = JustificationInfo();
// See if the frame is a placeholderFrame and if it is process
// the float. At the same time, check if the frame has any non-collapsed-away
// content.
bool placedFloat =
false;
bool isEmpty;
if (frameType == LayoutFrameType::None) {
isEmpty = pfd->mFrame->IsEmpty();
}
else if (LayoutFrameType::Placeholder == frameType) {
isEmpty =
true;
pfd->mIsPlaceholder =
true;
pfd->mSkipWhenTrimmingWhitespace =
true;
nsIFrame* outOfFlowFrame = nsLayoutUtils::GetFloatFromPlaceholder(aFrame);
if (outOfFlowFrame) {
if (psd->mNoWrap &&
// We can always place floats in an empty line.
!LineIsEmpty() &&
// We always place floating letter frames. This kinda sucks. They'd
// usually fall into the LineIsEmpty() check anyway, except when
// there's something like a ::marker before or what not. We actually
// need to place them now, because they're pretty nasty and they
// create continuations that are in flow and not a kid of the
// previous continuation's parent. We don't want the deferred reflow
// of the letter frame to kill a continuation after we've stored it
// in the line layout data structures. See bug 1490281 to fix the
// underlying issue. When that's fixed this check should be removed.
!outOfFlowFrame->IsLetterFrame() &&
!GetOutermostLineLayout()->mBlockRS->mFlags.mCanHaveOverflowMarkers) {
// We'll do this at the next break opportunity.
RecordNoWrapFloat(outOfFlowFrame);
}
else {
placedFloat = TryToPlaceFloat(outOfFlowFrame);
}
}
}
else if (isText) {
// Note non-empty text-frames for inline frame compatibility hackery
pfd->mIsTextFrame =
true;
auto* textFrame =
static_cast<nsTextFrame*>(pfd->mFrame);
isEmpty = !textFrame->HasNoncollapsedCharacters();
if (!isEmpty) {
pfd->mIsNonEmptyTextFrame =
true;
pfd->mIsNonWhitespaceTextFrame =
!textFrame->GetContent()->TextIsOnlyWhitespace();
}
}
else if (LayoutFrameType::Br == frameType) {
pfd->mSkipWhenTrimmingWhitespace =
true;
isEmpty =
false;
}
else {
if (LayoutFrameType::Letter == frameType) {
pfd->mIsLetterFrame =
true;
}
if (pfd->mSpan) {
isEmpty = !pfd->mSpan->mHasNonemptyContent && pfd->mFrame->IsSelfEmpty();
}
else {
isEmpty = pfd->mFrame->IsEmpty();
}
}
pfd->mIsEmpty = isEmpty;
mFloatManager->Translate(-tI, -tB);
NS_ASSERTION(reflowOutput.ISize(lineWM) >= 0,
"bad inline size");
NS_ASSERTION(reflowOutput.BSize(lineWM) >= 0,
"bad block size");
if (reflowOutput.ISize(lineWM) < 0) {
reflowOutput.ISize(lineWM) = 0;
}
if (reflowOutput.BSize(lineWM) < 0) {
reflowOutput.BSize(lineWM) = 0;
}
#ifdef DEBUG
// Note: break-before means ignore the reflow metrics since the
// frame will be reflowed another time.
if (!aReflowStatus.IsInlineBreakBefore()) {
if ((ABSURD_SIZE(reflowOutput.ISize(lineWM)) ||
ABSURD_SIZE(reflowOutput.BSize(lineWM))) &&
!LineContainerFrame()->GetParent()->IsAbsurdSizeAssertSuppressed()) {
printf(
"nsLineLayout: ");
aFrame->ListTag(stdout);
printf(
" metrics=%d,%d!\n", reflowOutput.Width(), reflowOutput.Height());
}
if ((reflowOutput.Width() == nscoord(0xdeadbeef)) ||
(reflowOutput.Height() == nscoord(0xdeadbeef))) {
printf(
"nsLineLayout: ");
aFrame->ListTag(stdout);
printf(
" didn't set w/h %d,%d!\n", reflowOutput.Width(),
reflowOutput.Height());
}
}
#endif
// Unlike with non-inline reflow, the overflow area here does *not*
// include the accumulation of the frame's bounds and its inline
// descendants' bounds. Nor does it include the outline area; it's
// just the union of the bounds of any absolute children. That is
// added in later by nsLineLayout::ReflowInlineFrames.
pfd->mOverflowAreas = reflowOutput.mOverflowAreas;
pfd->mBounds.ISize(lineWM) = reflowOutput.ISize(lineWM);
pfd->mBounds.BSize(lineWM) = reflowOutput.BSize(lineWM);
// Size the frame, but |RelativePositionFrames| will size the view.
aFrame->SetRect(lineWM, pfd->mBounds, ContainerSizeForSpan(psd));
// Tell the frame that we're done reflowing it
aFrame->DidReflow(mPresContext, isText ? nullptr : reflowInputHolder.ptr());
if (aMetrics) {
*aMetrics = reflowOutput;
}
if (!aReflowStatus.IsInlineBreakBefore()) {
// If frame is complete and has a next-in-flow, we need to delete
// them now. Do not do this when a break-before is signaled because
// the frame is going to get reflowed again (and may end up wanting
// a next-in-flow where it ends up).
if (aReflowStatus.IsComplete()) {
if (nsIFrame* kidNextInFlow = aFrame->GetNextInFlow()) {
// Remove all of the childs next-in-flows. Make sure that we ask
// the right parent to do the removal (it's possible that the
// parent is not this because we are executing pullup code)
FrameDestroyContext context(aFrame->PresShell());
kidNextInFlow->GetParent()->DeleteNextInFlowChild(context,
kidNextInFlow,
true);
}
}
// Check whether this frame breaks up text runs. All frames break up text
// runs (hence return false here) except for text frames and inline
// containers.
bool continuingTextRun = aFrame->CanContinueTextRun();
// Clear any residual mTrimmableISize if this isn't a text frame
if (!continuingTextRun && !pfd->mSkipWhenTrimmingWhitespace) {
mTrimmableISize = 0;
}
// See if we can place the frame. If we can't fit it, then we
// return now.
bool optionalBreakAfterFits;
NS_ASSERTION(isText || !reflowInputHolder->mStyleDisplay->IsFloating(
reflowInputHolder->mFrame),
"How'd we get a floated inline frame? "
"The frame ctor should've dealt with this.");
if (CanPlaceFrame(pfd, notSafeToBreak, continuingTextRun,
savedOptionalBreakFrame != nullptr, reflowOutput,
aReflowStatus, &optionalBreakAfterFits)) {
if (!isEmpty) {
psd->mHasNonemptyContent =
true;
mLineIsEmpty =
false;
if (!pfd->mSpan) {
// nonempty leaf content has been placed
mLineAtStart =
false;
}
if (LayoutFrameType::Ruby == frameType) {
mHasRuby =
true;
SyncAnnotationBounds(pfd);
}
}
// Place the frame, updating aBounds with the final size and
// location. Then apply the bottom+right margins (as
// appropriate) to the frame.
PlaceFrame(pfd, reflowOutput);
PerSpanData* span = pfd->mSpan;
if (span) {
// The frame we just finished reflowing is an inline
// container. It needs its child frames aligned in the block direction,
// so do most of it now.
VerticalAlignFrames(span);
}
if (!continuingTextRun && !psd->mNoWrap) {
if (!LineIsEmpty() || placedFloat) {
// record soft break opportunity after this content that can't be
// part of a text run. This is not a text frame so we know
// that offset INT32_MAX means "after the content".
if ((!aFrame->IsPlaceholderFrame() || LineIsEmpty()) &&
NotifyOptionalBreakPosition(aFrame, INT32_MAX,
optionalBreakAfterFits,
gfxBreakPriority::eNormalBreak)) {
// If this returns true then we are being told to actually break
// here.
aReflowStatus.SetInlineLineBreakAfter();
}
}
}
}
else {
PushFrame(aFrame);
aPushedFrame =
true;
// Undo any saved break positions that the frame might have told us about,
// since we didn't end up placing it
RestoreSavedBreakPosition(savedOptionalBreakFrame,
savedOptionalBreakOffset,
savedOptionalBreakPriority);
}
}
else {
PushFrame(aFrame);
aPushedFrame =
true;
}
#ifdef REALLY_NOISY_REFLOW
nsIFrame::IndentBy(stdout, mSpanDepth);
printf(
"End ReflowFrame ");
aFrame->ListTag(stdout);
printf(
" status=%x\n", aReflowStatus);
#endif
}
void nsLineLayout::AllowForStartMargin(PerFrameData* pfd,
ReflowInput& aReflowInput) {
NS_ASSERTION(!aReflowInput.mStyleDisplay->IsFloating(aReflowInput.mFrame),
"How'd we get a floated inline frame? "
"The frame ctor should've dealt with this.");
WritingMode lineWM = mRootSpan->mWritingMode;
// Only apply start-margin on the first-in flow for inline frames,
// and make sure to not apply it to any inline other than the first
// in an ib split. Note that the ib sibling (block-in-inline
// sibling) annotations only live on the first continuation, but we
// don't want to apply the start margin for later continuations
// anyway. For box-decoration-break:clone we apply the start-margin
// on all continuations.
if ((pfd->mFrame->GetPrevContinuation() ||
pfd->mFrame->FrameIsNonFirstInIBSplit()) &&
aReflowInput.mStyleBorder->mBoxDecorationBreak ==
StyleBoxDecorationBreak::Slice) {
// Zero this out so that when we compute the max-element-width of
// the frame we will properly avoid adding in the starting margin.
pfd->mMargin.IStart(lineWM) = 0;
}
else if (NS_UNCONSTRAINEDSIZE == aReflowInput.ComputedISize()) {
NS_WARNING_ASSERTION(
NS_UNCONSTRAINEDSIZE != aReflowInput.AvailableISize(),
"have unconstrained inline-size; this should only result from very "
"large sizes, not attempts at intrinsic inline-size calculation");
// For inline-ish and text-ish things (which don't compute widths
// in the reflow input), adjust available inline-size to account
// for the start margin. The end margin will be accounted for when
// we finish flowing the frame.
WritingMode wm = aReflowInput.GetWritingMode();
aReflowInput.SetAvailableISize(
aReflowInput.AvailableISize() -
pfd->mMargin.ConvertTo(wm, lineWM).IStart(wm));
}
}
nscoord nsLineLayout::GetCurrentFrameInlineDistanceFromBlock() {
nscoord x = 0;
for (
const auto* psd = mCurrentSpan; psd; psd = psd->mParent) {
x += psd->mICoord;
}
return x;
}
/**
* This method syncs bounds of ruby annotations and ruby annotation
* containers from their rect. It is necessary because:
* Containers are not part of the line in their levels, which means
* their bounds are not set properly before.
* Ruby annotations' position may have been changed when reflowing
* their containers.
*/
void nsLineLayout::SyncAnnotationBounds(PerFrameData* aRubyFrame) {
MOZ_ASSERT(aRubyFrame->mFrame->IsRubyFrame());
MOZ_ASSERT(aRubyFrame->mSpan);
PerSpanData* span = aRubyFrame->mSpan;
WritingMode lineWM = mRootSpan->mWritingMode;
for (PerFrameData* pfd = span->mFirstFrame; pfd; pfd = pfd->mNext) {
for (PerFrameData* rtc = pfd->mNextAnnotation; rtc;
rtc = rtc->mNextAnnotation) {
if (lineWM.IsOrthogonalTo(rtc->mFrame->GetWritingMode())) {
// Inter-character case: don't attempt to sync annotation bounds.
continue;
}
// When the annotation container is reflowed, the width of the
// ruby container is unknown so we use a dummy container size;
// in the case of RTL block direction, the final position will be
// fixed up later.
const nsSize dummyContainerSize;
LogicalRect rtcBounds(lineWM, rtc->mFrame->GetRect(), dummyContainerSize);
rtc->mBounds = rtcBounds;
nsSize rtcSize = rtcBounds.Size(lineWM).GetPhysicalSize(lineWM);
for (PerFrameData* rt = rtc->mSpan->mFirstFrame; rt; rt = rt->mNext) {
LogicalRect rtBounds = rt->mFrame->GetLogicalRect(lineWM, rtcSize);
MOZ_ASSERT(rt->mBounds.Size(lineWM) == rtBounds.Size(lineWM),
"Size of the annotation should not have been changed");
rt->mBounds.SetOrigin(lineWM, rtBounds.Origin(lineWM));
}
}
}
}
/**
* See if the frame can be placed now that we know it's desired size.
* We can always place the frame if the line is empty. Note that we
* know that the reflow-status is not a break-before because if it was
* ReflowFrame above would have returned false, preventing this method
* from being called. The logic in this method assumes that.
*
* Note that there is no check against the Y coordinate because we
* assume that the caller will take care of that.
*/
bool nsLineLayout::CanPlaceFrame(PerFrameData* pfd,
bool aNotSafeToBreak,
bool aFrameCanContinueTextRun,
bool aCanRollBackBeforeFrame,
ReflowOutput& aMetrics,
nsReflowStatus& aStatus,
bool* aOptionalBreakAfterFits) {
MOZ_ASSERT(pfd && pfd->mFrame,
"bad args, null pointers for frame data");
*aOptionalBreakAfterFits =
true;
WritingMode lineWM = mRootSpan->mWritingMode;
/*
* We want to only apply the end margin if we're the last continuation and
* either not in an {ib} split or the last inline in it. In all other
* cases we want to zero it out. That means zeroing it out if any of these
* conditions hold:
* 1) The frame is not complete (in this case it will get a next-in-flow)
* 2) The frame is complete but has a non-fluid continuation on its
* continuation chain. Note that if it has a fluid continuation, that
* continuation will get destroyed later, so we don't want to drop the
* end-margin in that case.
* 3) The frame is in an {ib} split and is not the last part.
*
* However, none of that applies if this is a letter frame (XXXbz why?)
*
* For box-decoration-break:clone we apply the end margin on all
* continuations (that are not letter frames).
*/
if ((aStatus.IsIncomplete() ||
pfd->mFrame->LastInFlow()->GetNextContinuation() ||
pfd->mFrame->FrameIsNonLastInIBSplit()) &&
!pfd->mIsLetterFrame &&
pfd->mFrame->StyleBorder()->mBoxDecorationBreak ==
StyleBoxDecorationBreak::Slice) {
pfd->mMargin.IEnd(lineWM) = 0;
}
// Apply the start margin to the frame bounds.
nscoord startMargin = pfd->mMargin.IStart(lineWM);
nscoord endMargin = pfd->mMargin.IEnd(lineWM);
pfd->mBounds.IStart(lineWM) += startMargin;
PerSpanData* psd = mCurrentSpan;
if (psd->mNoWrap) {
// When wrapping is off, everything fits.
return true;
}
#ifdef NOISY_CAN_PLACE_FRAME
if (psd->mFrame) {
psd->mFrame->mFrame->ListTag(stdout);
}
printf(
": aNotSafeToBreak=%s frame=", aNotSafeToBreak ?
"true" :
"false");
pfd->mFrame->ListTag(stdout);
printf(
" frameWidth=%d, margins=%d,%d\n",
pfd->mBounds.IEnd(lineWM) + endMargin - psd->mICoord, startMargin,
endMargin);
#endif
// Set outside to true if the result of the reflow leads to the
// frame sticking outside of our available area.
bool outside =
pfd->mBounds.IEnd(lineWM) - mTrimmableISize + endMargin > psd->mIEnd;
if (!outside) {
// If it fits, it fits
#ifdef NOISY_CAN_PLACE_FRAME
printf(
" ==> inside\n");
#endif
return true;
}
*aOptionalBreakAfterFits =
false;
// When it doesn't fit, check for a few special conditions where we
// allow it to fit anyway.
if (0 == startMargin + pfd->mBounds.ISize(lineWM) + endMargin) {
// Empty frames always fit right where they are
#ifdef NOISY_CAN_PLACE_FRAME
printf(
" ==> empty frame fits\n");
#endif
return true;
}
// another special case: always place a BR
if (pfd->mFrame->IsBrFrame()) {
#ifdef NOISY_CAN_PLACE_FRAME
printf(
" ==> BR frame fits\n");
#endif
return true;
}
if (aNotSafeToBreak) {
// There are no frames on the line that take up width and the line is
// not impacted by floats, so we must allow the current frame to be
// placed on the line
#ifdef NOISY_CAN_PLACE_FRAME
printf(
" ==> not-safe and not-impacted fits: ");
while (nullptr != psd) {
printf(
" ", psd, psd->mICoord, psd->mIStart);
psd = psd->mParent;
}
printf(
"\n");
#endif
return true;
}
// Special check for span frames
if (pfd->mSpan && pfd->mSpan->mContainsFloat) {
// If the span either directly or indirectly contains a float then
// it fits. Why? It's kind of complicated, but here goes:
//
// 1. CanPlaceFrame is used for all frame placements on a line,
// and in a span. This includes recursively placement of frames
// inside of spans, and the span itself. Because the logic always
// checks for room before proceeding (the code above here), the
// only things on a line will be those things that "fit".
//
// 2. Before a float is placed on a line, the line has to be empty
// (otherwise it's a "below current line" float and will be placed
// after the line).
//
// Therefore, if the span directly or indirectly has a float
// then it means that at the time of the placement of the float
// the line was empty. Because of #1, only the frames that fit can
// be added after that point, therefore we can assume that the
// current span being placed has fit.
//
// So how do we get here and have a span that should already fit
// and yet doesn't: Simple: span's that have the no-wrap attribute
// set on them and contain a float and are placed where they
// don't naturally fit.
return true;
}
if (aFrameCanContinueTextRun) {
// Let it fit, but we reserve the right to roll back.
// Note that we usually won't get here because a text frame will break
// itself to avoid exceeding the available width.
// We'll only get here for text frames that couldn't break early enough.
#ifdef NOISY_CAN_PLACE_FRAME
printf(
" ==> placing overflowing textrun, requesting backup\n");
#endif
// We will want to try backup.
mNeedBackup =
true;
return true;
}
#ifdef NOISY_CAN_PLACE_FRAME
printf(
" ==> didn't fit\n");
#endif
aStatus.SetInlineLineBreakBeforeAndReset();
return false;
}
/**
* Place the frame. Update running counters.
*/
void nsLineLayout::PlaceFrame(PerFrameData* pfd, ReflowOutput& aMetrics) {
WritingMode lineWM = mRootSpan->mWritingMode;
// If the frame's block direction does not match the line's, we can't use
// its ascent; instead, treat it as a block with baseline at the block-end
// edge (or block-begin in the case of an "inverted" line).
if (pfd->mWritingMode.GetBlockDir() != lineWM.GetBlockDir()) {
pfd->mAscent = lineWM.IsAlphabeticalBaseline()
? lineWM.IsLineInverted() ? 0 : aMetrics.BSize(lineWM)
: aMetrics.BSize(lineWM) / 2;
}
else {
// For inline reflow participants, baseline may get assigned as the frame is
// vertically aligned, which happens after this.
const auto baselineSource = pfd->mFrame->StyleDisplay()->mBaselineSource;
if (baselineSource == StyleBaselineSource::
Auto ||
pfd->mFrame->IsLineParticipant()) {
if (aMetrics.BlockStartAscent() == ReflowOutput::ASK_FOR_BASELINE) {
pfd->mAscent = pfd->mFrame->GetLogicalBaseline(lineWM);
}
else {
pfd->mAscent = aMetrics.BlockStartAscent();
}
}
else {
const auto sourceGroup = [baselineSource]() {
switch (baselineSource) {
case StyleBaselineSource::First:
return BaselineSharingGroup::First;
case StyleBaselineSource::Last:
return BaselineSharingGroup::Last;
case StyleBaselineSource::
Auto:
break;
}
MOZ_ASSERT_UNREACHABLE(
"Auto should be already handled?");
return BaselineSharingGroup::First;
}();
// We ignore line-layout specific layout quirks by setting
// `BaselineExportContext::Other`.
// Note(dshin): For a lot of frames, the export context does not make a
// difference, and we may be wasting the value cached in
// `BlockStartAscent`.
pfd->mAscent = pfd->mFrame->GetLogicalBaseline(
lineWM, sourceGroup, BaselineExportContext::Other);
}
}
// Advance to next inline coordinate
mCurrentSpan->mICoord = pfd->mBounds.IEnd(lineWM) + pfd->mMargin.IEnd(lineWM);
// Count the number of non-placeholder frames on the line...
if (pfd->mFrame->IsPlaceholderFrame()) {
NS_ASSERTION(
pfd->mBounds.ISize(lineWM) == 0 && pfd->mBounds.BSize(lineWM) == 0,
"placeholders should have 0 width/height (checking "
"placeholders were never counted by the old code in "
"this function)");
}
else {
mTotalPlacedFrames++;
}
}
void nsLineLayout::AddMarkerFrame(nsIFrame* aFrame,
const ReflowOutput& aMetrics) {
MOZ_ASSERT(mCurrentSpan == mRootSpan,
"bad linelayout user");
MOZ_ASSERT(mGotLineBox,
"must have line box");
nsBlockFrame* blockFrame = do_QueryFrame(LineContainerFrame());
MOZ_ASSERT(blockFrame,
"must be for block");
if (!blockFrame->MarkerIsEmpty(aFrame)) {
mLineIsEmpty =
false;
mHasMarker =
true;
mLineBox->SetHasMarker();
}
WritingMode lineWM = mRootSpan->mWritingMode;
PerFrameData* pfd = NewPerFrameData(aFrame);
PerSpanData* psd = mRootSpan;
MOZ_ASSERT(psd->mFirstFrame,
"adding marker to an empty line?");
// Prepend the marker frame to the line.
psd->mFirstFrame->mPrev = pfd;
pfd->mNext = psd->mFirstFrame;
psd->mFirstFrame = pfd;
pfd->mIsMarker =
true;
if (aMetrics.BlockStartAscent() == ReflowOutput::ASK_FOR_BASELINE) {
pfd->mAscent = aFrame->GetLogicalBaseline(lineWM);
}
else {
pfd->mAscent = aMetrics.BlockStartAscent();
}
// Note: block-coord value will be updated during block-direction alignment
pfd->mBounds = LogicalRect(lineWM, aFrame->GetRect(), ContainerSize());
pfd->mOverflowAreas = aMetrics.mOverflowAreas;
}
void nsLineLayout::RemoveMarkerFrame(nsIFrame* aFrame) {
PerSpanData* psd = mCurrentSpan;
MOZ_ASSERT(psd == mRootSpan,
"::marker on non-root span?");
MOZ_ASSERT(psd->mFirstFrame->mFrame == aFrame,
"::marker is not the first frame?");
PerFrameData* pfd = psd->mFirstFrame;
MOZ_ASSERT(pfd != psd->mLastFrame,
"::marker is the only frame?");
pfd->mNext->mPrev = nullptr;
psd->mFirstFrame = pfd->mNext;
FreeFrame(pfd);
}
#ifdef DEBUG
void nsLineLayout::DumpPerSpanData(PerSpanData* psd, int32_t aIndent) {
nsIFrame::IndentBy(stdout, aIndent);
printf(
"%p: left=%d x=%d right=%d\n",
static_cast<
void*>(psd), psd->mIStart,
psd->mICoord, psd->mIEnd);
PerFrameData* pfd = psd->mFirstFrame;
while (nullptr != pfd) {
nsIFrame::IndentBy(stdout, aIndent + 1);
pfd->mFrame->ListTag(stdout);
nsRect rect =
pfd->mBounds.GetPhysicalRect(psd->mWritingMode, ContainerSize());
printf(
" %d,%d,%d,%d\n", rect.x, rect.y, rect.width, rect.height);
if (pfd->mSpan) {
DumpPerSpanData(pfd->mSpan, aIndent + 1);
}
pfd = pfd->mNext;
}
}
#endif
void nsLineLayout::RecordNoWrapFloat(nsIFrame* aFloat) {
GetOutermostLineLayout()->mBlockRS->mNoWrapFloats.AppendElement(aFloat);
}
void nsLineLayout::FlushNoWrapFloats() {
auto& noWrapFloats = GetOutermostLineLayout()->mBlockRS->mNoWrapFloats;
for (nsIFrame* floatedFrame : noWrapFloats) {
TryToPlaceFloat(floatedFrame);
}
noWrapFloats.Clear();
}
bool nsLineLayout::TryToPlaceFloat(nsIFrame* aFloat) {
// Add mTrimmableISize to the available width since if the line ends here, the
// width of the inline content will be reduced by mTrimmableISize.
nscoord availableISize =
mCurrentSpan->mIEnd - (mCurrentSpan->mICoord - mTrimmableISize);
NS_ASSERTION(!(aFloat->IsLetterFrame() && GetFirstLetterStyleOK()),
"FirstLetterStyle set on line with floating first letter");
return GetOutermostLineLayout()->AddFloat(aFloat, availableISize);
}
bool nsLineLayout::NotifyOptionalBreakPosition(nsIFrame* aFrame,
int32_t aOffset,
bool aFits,
gfxBreakPriority aPriority) {
NS_ASSERTION(!aFits || !mNeedBackup,
"Shouldn't be updating the break position with a break that fits"
" after we've already flagged an overrun");
MOZ_ASSERT(mCurrentSpan,
"Should be doing line layout");
if (mCurrentSpan->mNoWrap) {
FlushNoWrapFloats();
}
// Remember the last break position that fits; if there was no break that fit,
// just remember the first break
if ((aFits && aPriority >= mLastOptionalBreakPriority) ||
!mLastOptionalBreakFrame) {
mLastOptionalBreakFrame = aFrame;
mLastOptionalBreakFrameOffset = aOffset;
mLastOptionalBreakPriority = aPriority;
}
return aFrame && mForceBreakFrame == aFrame &&
mForceBreakFrameOffset == aOffset;
}
#define VALIGN_OTHER 0
#define VALIGN_TOP 1
#define VALIGN_BOTTOM 2
void nsLineLayout::SetSpanForEmptyLine(PerSpanData* aPerSpanData,
WritingMode aWM,
const nsSize& aContainerSize,
nscoord aBStartEdge) {
for (PerFrameData* pfd = aPerSpanData->mFirstFrame; pfd; pfd = pfd->mNext) {
// Ideally, if the frame would collapse itself - but it depends on
// knowing that the line is empty.
if (!pfd->mFrame->IsInlineFrame() && !pfd->mFrame->IsRubyFrame() &&
!pfd->mFrame->IsPlaceholderFrame()) {
continue;
}
// Collapse the physical size to 0.
pfd->mBounds.BStart(aWM) = aBStartEdge;
pfd->mBounds.BSize(aWM) = 0;
// Initialize mBlockDirAlign (though it doesn't make much difference
// because we don't align empty boxes).
pfd->mBlockDirAlign = VALIGN_OTHER;
pfd->mFrame->SetRect(aWM, pfd->mBounds, aContainerSize);
if (pfd->mSpan) {
// For child spans, the block-start edge is relative to that of parent,
// which is zero (since it is empty). See NOTE in
// `nsLineLayout::ReflowFrame`.
SetSpanForEmptyLine(pfd->mSpan, aWM, aContainerSize, 0);
}
}
}
void nsLineLayout::VerticalAlignLine() {
// Partially place the children of the block frame. The baseline for
// this operation is set to zero so that the y coordinates for all
// of the placed children will be relative to there.
PerSpanData* psd = mRootSpan;
if (mLineIsEmpty) {
// This line is empty, and should be consisting of only inline elements.
// (inline-block elements would make the line non-empty).
SetSpanForEmptyLine(psd, mRootSpan->mWritingMode, ContainerSize(),
mBStartEdge);
mFinalLineBSize = 0;
if (mGotLineBox) {
mLineBox->SetBounds(psd->mWritingMode, psd->mIStart, mBStartEdge,
psd->mICoord - psd->mIStart, 0, ContainerSize());
mLineBox->SetLogicalAscent(0);
}
return;
}
VerticalAlignFrames(psd);
// *** Note that comments here still use the anachronistic term
// "line-height" when we really mean "size of the line in the block
// direction", "vertical-align" when we really mean "alignment in
// the block direction", and "top" and "bottom" when we really mean
// "block start" and "block end". This is partly for brevity and
// partly to retain the association with the CSS line-height and
// vertical-align properties.
//
// Compute the line-height. The line-height will be the larger of:
//
// [1] maxBCoord - minBCoord (the distance between the first child's
// block-start edge and the last child's block-end edge)
//
// [2] the maximum logical box block size (since not every frame may have
// participated in #1; for example: "top" and "botttom" aligned frames)
//
// [3] the minimum line height ("line-height" property set on the
// block frame)
nscoord lineBSize = psd->mMaxBCoord - psd->mMinBCoord;
// Now that the line-height is computed, we need to know where the
// baseline is in the line. Position baseline so that mMinBCoord is just
// inside the start of the line box.
nscoord baselineBCoord = mBStartEdge - std::min(0, psd->mMinBCoord);
// It's also possible that the line block-size isn't tall enough because
// of "top" and "bottom" aligned elements that were not accounted for in
// min/max BCoord.
//
// The CSS2 spec doesn't really say what happens when to the
// baseline in this situations. What we do is if the largest start
// aligned box block size is greater than the line block-size then we leave
// the baseline alone. If the largest end aligned box is greater
// than the line block-size then we slide the baseline forward by the extra
// amount.
//
// Navigator 4 gives precedence to the first top/bottom aligned
// object. We just let block end aligned objects win.
if (lineBSize < mMaxEndBoxBSize) {
// When the line is shorter than the maximum block start aligned box
nscoord extra = mMaxEndBoxBSize - lineBSize;
baselineBCoord += extra;
lineBSize = mMaxEndBoxBSize;
}
lineBSize = std::max(lineBSize, mMaxStartBoxBSize);
#ifdef NOISY_BLOCKDIR_ALIGN
printf(
" [line]==> lineBSize=%d baselineBCoord=%d\n", lineBSize,
baselineBCoord);
#endif
// Now position all of the frames in the root span. We will also
// recurse over the child spans and place any frames we find with
// vertical-align: top or bottom.
// XXX PERFORMANCE: set a bit per-span to avoid the extra work
// (propagate it upward too)
WritingMode lineWM = psd->mWritingMode;
for (PerFrameData* pfd = psd->mFirstFrame; pfd; pfd = pfd->mNext) {
if (pfd->mBlockDirAlign == VALIGN_OTHER) {
pfd->mBounds.BStart(lineWM) += baselineBCoord;
pfd->mFrame->SetRect(lineWM, pfd->mBounds, ContainerSize());
}
}
PlaceTopBottomFrames(psd, -mBStartEdge, lineBSize);
mFinalLineBSize = lineBSize;
if (mGotLineBox) {
// Fill in returned line-box and max-element-width data
mLineBox->SetBounds(lineWM, psd->mIStart, mBStartEdge,
psd->mICoord - psd->mIStart, lineBSize,
ContainerSize());
mLineBox->SetLogicalAscent(baselineBCoord - mBStartEdge);
#ifdef NOISY_BLOCKDIR_ALIGN
printf(
" [line]==> bounds{x,y,w,h}={%d,%d,%d,%d} lh=%d a=%d\n",
mLineBox->GetBounds().IStart(lineWM),
mLineBox->GetBounds().BStart(lineWM),
mLineBox->GetBounds().ISize(lineWM),
mLineBox->GetBounds().BSize(lineWM), mFinalLineBSize,
mLineBox->GetLogicalAscent());
#endif
}
}
// Place frames with CSS property vertical-align: top or bottom.
void nsLineLayout::PlaceTopBottomFrames(PerSpanData* psd,
nscoord aDistanceFromStart,
nscoord aLineBSize) {
for (PerFrameData* pfd = psd->mFirstFrame; pfd; pfd = pfd->mNext) {
PerSpanData* span = pfd->mSpan;
#ifdef DEBUG
NS_ASSERTION(0xFF != pfd->mBlockDirAlign,
"umr");
#endif
WritingMode lineWM = mRootSpan->mWritingMode;
nsSize containerSize = ContainerSizeForSpan(psd);
switch (pfd->mBlockDirAlign) {
case VALIGN_TOP:
if (span) {
pfd->mBounds.BStart(lineWM) = -aDistanceFromStart - span->mMinBCoord;
}
else {
pfd->mBounds.BStart(lineWM) =
-aDistanceFromStart + pfd->mMargin.BStart(lineWM);
}
pfd->mFrame->SetRect(lineWM, pfd->mBounds, containerSize);
#ifdef NOISY_BLOCKDIR_ALIGN
printf(
" ");
pfd->mFrame->ListTag(stdout);
printf(
": y=%d dTop=%d [bp.top=%d topLeading=%d]\n",
pfd->mBounds.BStart(lineWM), aDistanceFromStart,
span ? pfd->mBorderPadding.BStart(lineWM) : 0,
span ? span->mBStartLeading : 0);
#endif
break;
case VALIGN_BOTTOM:
if (span) {
// Compute bottom leading
pfd->mBounds.BStart(lineWM) =
-aDistanceFromStart + aLineBSize - span->mMaxBCoord;
}
else {
pfd->mBounds.BStart(lineWM) = -aDistanceFromStart + aLineBSize -
pfd->mMargin.BEnd(lineWM) -
pfd->mBounds.BSize(lineWM);
}
pfd->mFrame->SetRect(lineWM, pfd->mBounds, containerSize);
#ifdef NOISY_BLOCKDIR_ALIGN
printf(
" ");
pfd->mFrame->ListTag(stdout);
printf(
": y=%d\n", pfd->mBounds.BStart(lineWM));
#endif
break;
}
if (span) {
nscoord fromStart = aDistanceFromStart + pfd->mBounds.BStart(lineWM);
PlaceTopBottomFrames(span, fromStart, aLineBSize);
}
}
}
static nscoord GetBSizeOfEmphasisMarks(nsIFrame* aSpanFrame,
float aInflation) {
RefPtr<nsFontMetrics> fm = nsLayoutUtils::GetFontMetricsOfEmphasisMarks(
aSpanFrame->Style(), aSpanFrame->PresContext(), aInflation);
return fm->MaxHeight();
}
void nsLineLayout::AdjustLeadings(nsIFrame* spanFrame, PerSpanData* psd,
const nsStyleText* aStyleText,
float aInflation,
bool* aZeroEffectiveSpanBox) {
MOZ_ASSERT(spanFrame == psd->mFrame->mFrame);
nscoord requiredStartLeading = 0;
nscoord requiredEndLeading = 0;
if (spanFrame->IsRubyFrame()) {
// We may need to extend leadings here for ruby annotations as
// required by section Line Spacing in the CSS Ruby spec.
// See http://dev.w3.org/csswg/css-ruby/#line-height
auto rubyFrame =
static_cast<nsRubyFrame*>(spanFrame);
RubyBlockLeadings leadings = rubyFrame->GetBlockLeadings();
requiredStartLeading += leadings.mStart;
requiredEndLeading += leadings.mEnd;
}
if (aStyleText->HasEffectiveTextEmphasis()) {
nscoord bsize = GetBSizeOfEmphasisMarks(spanFrame, aInflation);
LogicalSide side = aStyleText->TextEmphasisSide(
mRootSpan->mWritingMode, spanFrame->StyleFont()->mLanguage);
if (side == LogicalSide::BStart) {
requiredStartLeading += bsize;
}
else {
MOZ_ASSERT(side == LogicalSide::BEnd,
"emphasis marks must be in block axis");
requiredEndLeading += bsize;
}
}
--> --------------------
--> maximum size reached
--> --------------------