Source code

Revision control

Other Tools

/* -*- 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 "mozilla/SVGUtils.h"
#include "LayoutLogging.h"
#include "nsBlockFrame.h"
#include "nsFontMetrics.h"
#include "nsStyleConsts.h"
#include "nsContainerFrame.h"
#include "nsFloatManager.h"
#include "nsPresContext.h"
#include "nsGkAtoms.h"
#include "nsIContent.h"
#include "nsLayoutUtils.h"
#include "nsTextFrame.h"
#include "nsStyleStructInlines.h"
#include "nsBidiPresUtils.h"
#include "nsRubyFrame.h"
#include "nsRubyTextFrame.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* aOuterReflowInput,
const nsLineList::iterator* aLine,
nsLineLayout* aBaseLineLayout)
: mPresContext(aPresContext),
mFloatManager(aFloatManager),
mBlockReflowInput(aOuterReflowInput),
mBaseLineLayout(aBaseLineLayout),
mLastOptionalBreakFrame(nullptr),
mForceBreakFrame(nullptr),
mBlockRI(nullptr), /* XXX temporary */
mLastOptionalBreakPriority(gfxBreakPriority::eNoBreak),
mLastOptionalBreakFrameOffset(-1),
mForceBreakFrameOffset(-1),
mMinLineBSize(0),
mTextIndent(0),
mMaxStartBoxBSize(0),
mMaxEndBoxBSize(0),
mFinalLineBSize(0),
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(SVGUtils::IsInSVGTextSubtree(aOuterReflowInput->mFrame))
#ifdef DEBUG
,
mSpansAllocated(0),
mSpansFreed(0),
mFramesAllocated(0),
mFramesFreed(0)
#endif
{
MOZ_ASSERT(aOuterReflowInput, "aOuterReflowInput must not be null");
NS_ASSERTION(aFloatManager || aOuterReflowInput->mFrame->IsLetterFrame(),
"float manager should be present");
MOZ_ASSERT((!!mBaseLineLayout) ==
aOuterReflowInput->mFrame->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(aOuterReflowInput->mFrame);
if (blockFrame)
mStyleText = blockFrame->StyleTextForLineLayout();
else
mStyleText = aOuterReflowInput->mFrame->StyleText();
mLineNumber = 0;
mTotalPlacedFrames = 0;
mBStartEdge = 0;
mTrimmableISize = 0;
mInflationMinFontSize =
nsLayoutUtils::InflationMinFontSizeFor(aOuterReflowInput->mFrame);
// Instead of always pre-initializing the free-lists for frames and
// spans, we do it on demand so that situations that only use a few
// frames and spans won't waste a lot of time in unneeded
// initialization.
mFrameFreeList = nullptr;
mSpanFreeList = nullptr;
mCurrentSpan = mRootSpan = nullptr;
mSpanDepth = 0;
if (aLine) {
mGotLineBox = true;
mLineBox = *aLine;
}
}
nsLineLayout::~nsLineLayout() {
MOZ_COUNT_DTOR(nsLineLayout);
NS_ASSERTION(nullptr == mRootSpan, "bad line-layout user");
}
// Find out if the frame has a non-null prev-in-flow, i.e., whether it
// is a continuation.
inline bool HasPrevInFlow(nsIFrame* aFrame) {
nsIFrame* prevInFlow = aFrame->GetPrevInFlow();
return prevInFlow != nullptr;
}
void nsLineLayout::BeginLineReflow(nscoord aICoord, nscoord aBCoord,
nscoord aISize, nscoord aBSize,
bool aImpactedByFloats, bool aIsTopOfPage,
WritingMode aWritingMode,
const nsSize& aContainerSize) {
NS_ASSERTION(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()) {
mBlockReflowInput->mFrame->ListTag(stdout);
printf(": Init: bad caller: width WAS %d(0x%x)\n", aISize, aISize);
}
if ((aBSize != NS_UNCONSTRAINEDSIZE) && ABSURD_SIZE(aBSize) &&
!LineContainerFrame()->GetParent()->IsAbsurdSizeAssertSuppressed()) {
mBlockReflowInput->mFrame->ListTag(stdout);
printf(": Init: bad caller: height WAS %d(0x%x)\n", aBSize, aBSize);
}
#endif
#ifdef NOISY_REFLOW
mBlockReflowInput->mFrame->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 = mBlockReflowInput;
psd->mIStart = aICoord;
psd->mICoord = aICoord;
psd->mIEnd = aICoord + aISize;
mContainerSize = aContainerSize;
mBStartEdge = aBCoord;
psd->mNoWrap = !mStyleText->WhiteSpaceCanWrapStyle() || mSuppressLineWrap;
psd->mWritingMode = aWritingMode;
// If this is the first line of a block then see if the text-indent
// property amounts to anything.
if (0 == mLineNumber && !HasPrevInFlow(mBlockReflowInput->mFrame)) {
nscoord pctBasis = mBlockReflowInput->ComputedISize();
mTextIndent = mStyleText->mTextIndent.Resolve(pctBasis);
psd->mICoord += mTextIndent;
}
PerFrameData* pfd = NewPerFrameData(mBlockReflowInput->mFrame);
pfd->mAscent = 0;
pfd->mSpan = psd;
psd->mFrame = pfd;
nsIFrame* frame = mBlockReflowInput->mFrame;
if (frame->IsRubyTextContainerFrame()) {
// Ruby text container won't be reflowed via ReflowFrame, hence the
// relative positioning information should be recorded here.
MOZ_ASSERT(mBaseLineLayout != this);
pfd->mRelativePos =
mBlockReflowInput->mStyleDisplay->IsRelativelyPositionedStyle();
if (pfd->mRelativePos) {
MOZ_ASSERT(mBlockReflowInput->GetWritingMode() == pfd->mWritingMode,
"mBlockReflowInput->frame == frame, "
"hence they should have identical writing mode");
pfd->mOffsets =
mBlockReflowInput->ComputedLogicalOffsets(pfd->mWritingMode);
}
}
}
void nsLineLayout::EndLineReflow() {
#ifdef NOISY_REFLOW
mBlockReflowInput->mFrame->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
}
// 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()) {
mBlockReflowInput->mFrame->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()) {
mBlockReflowInput->mFrame->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
mBlockReflowInput->mFrame->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->mContainsFloat = false;
psd->mHasNonemptyContent = false;
#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->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 {
NS_ASSERTION(mCurrentSpan == mRootSpan, "bad linelayout user");
int32_t count = 0;
PerFrameData* pfd = mRootSpan->mFirstFrame;
while (nullptr != pfd) {
count++;
pfd = pfd->mNext;
}
return count;
}
void nsLineLayout::SplitLineTo(int32_t aNewCount) {
NS_ASSERTION(mCurrentSpan == mRootSpan, "bad linelayout user");
#ifdef REALLY_NOISY_PUSHING
printf("SplitLineTo %d (current count=%d); before:\n", aNewCount,
GetCurrentSpanCount());
DumpPerSpanData(mRootSpan, 1);
#endif
PerSpanData* psd = mRootSpan;
PerFrameData* pfd = psd->mFirstFrame;
while (nullptr != pfd) {
if (--aNewCount == 0) {
// Truncate list at pfd (we keep pfd, but anything following is freed)
PerFrameData* next = pfd->mNext;
pfd->mNext = nullptr;
psd->mLastFrame = pfd;
// Now unlink all of the frames following pfd
UnlinkFrame(next);
break;
}
pfd = pfd->mNext;
}
#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() {
PerSpanData* psd = mCurrentSpan;
PerFrameData* pfd = psd->mFirstFrame;
while (nullptr != pfd) {
if (0 != pfd->mBounds.BSize(psd->mWritingMode)) {
return false;
}
pfd = pfd->mNext;
}
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->mRelativePos = 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;
}
bool nsLineLayout::LineIsBreakable() const {
// XXX mTotalPlacedFrames should go away and we should just use
// mLineIsEmpty here instead
if ((0 != mTotalPlacedFrames) || mImpactedByFloats) {
return true;
}
return false;
}
// 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 IsPercentageAware(const nsIFrame* aFrame, WritingMode aWM) {
NS_ASSERTION(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 (HasPercentageUnitSide(margin->mMargin)) {
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.GetIStart(aWM).HasPercent() ||
pos->mOffset.GetIEnd(aWM).HasPercent()) {
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 ||
fType == LayoutFrameType::ComboboxDisplay) {
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;
// 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 = mBlockReflowInput->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.AvailableISize() = availableSpaceOnLine;
}
pfd->mMargin = reflowInput.ComputedLogicalMargin(lineWM);
pfd->mBorderPadding = reflowInput.ComputedLogicalBorderPadding(lineWM);
pfd->mRelativePos =
reflowInput.mStyleDisplay->IsRelativelyPositionedStyle();
if (pfd->mRelativePos) {
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()
->mBlockRI->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;
nsTextFrame* textFrame = static_cast<nsTextFrame*>(pfd->mFrame);
isEmpty = !textFrame->HasNoncollapsedCharacters();
if (!isEmpty) {
pfd->mIsNonEmptyTextFrame = true;
nsIContent* content = textFrame->GetContent();
const nsTextFragment* frag = content->GetText();
if (frag) {
pfd->mIsNonWhitespaceTextFrame = !content->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()) {
nsIFrame* kidNextInFlow = aFrame->GetNextInFlow();
if (nullptr != kidNextInFlow) {
// 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)
kidNextInFlow->GetParent()->DeleteNextInFlowChild(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.AvailableISize() -=
pfd->mMargin.ConvertTo(wm, lineWM).IStart(wm);
}
}
nscoord nsLineLayout::GetCurrentFrameInlineDistanceFromBlock() {
PerSpanData* psd;
nscoord x = 0;
for (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 (nullptr != 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=%p x=%d left=%d> ", 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.IsLineInverted() ? 0 : aMetrics.BSize(lineWM);
} else {
if (aMetrics.BlockStartAscent() == ReflowOutput::ASK_FOR_BASELINE) {
pfd->mAscent = pfd->mFrame->GetLogicalBaseline(lineWM);
} else {
pfd->mAscent = aMetrics.BlockStartAscent();
}
}
// 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) {
NS_ASSERTION(mCurrentSpan == mRootSpan, "bad linelayout user");
NS_ASSERTION(mGotLineBox, "must have line box");
nsBlockFrame* blockFrame = do_QueryFrame(mBlockReflowInput->mFrame);
MOZ_ASSERT(blockFrame, "must be for block");
if (!blockFrame->MarkerIsEmpty()) {
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()->mBlockRI->mNoWrapFloats.AppendElement(aFloat);
}
void nsLineLayout::FlushNoWrapFloats() {
auto& noWrapFloats = GetOutermostLineLayout()->mBlockRI->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::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;
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;
if (psd->mMinBCoord < 0) {
baselineBCoord = mBStartEdge - psd->mMinBCoord;
} else {
baselineBCoord = mBStartEdge;
}
// 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;
}
if (lineBSize < mMaxStartBoxBSize) {
lineBSize = mMaxStartBoxBSize;
}
#ifdef NOISY_BLOCKDIR_ALIGN
printf(" [line]==> lineBSize=%d baselineBCoord=%d\n", lineBSize,
baselineBCoord);