Bumping manifests a=b2g-bump
[gecko.git] / layout / generic / nsLineLayout.cpp
blob37f1a0c5acff9be036e3d80051000e89d5a18755
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 /* state and methods used while laying out a single line of a block frame */
8 // This has to be defined before nsLineLayout.h is included, because
9 // nsLineLayout.h has a #include for plarena.h, which needs this defined:
10 #define PL_ARENA_CONST_ALIGN_MASK (sizeof(void*)-1)
11 #include "nsLineLayout.h"
13 #include "SVGTextFrame.h"
14 #include "nsBlockFrame.h"
15 #include "nsFontMetrics.h"
16 #include "nsStyleConsts.h"
17 #include "nsContainerFrame.h"
18 #include "nsFloatManager.h"
19 #include "nsStyleContext.h"
20 #include "nsPresContext.h"
21 #include "nsGkAtoms.h"
22 #include "nsIContent.h"
23 #include "nsLayoutUtils.h"
24 #include "nsTextFrame.h"
25 #include "nsStyleStructInlines.h"
26 #include "nsBidiPresUtils.h"
27 #include "nsRubyFrame.h"
28 #include "RubyUtils.h"
29 #include <algorithm>
31 #ifdef DEBUG
32 #undef NOISY_INLINEDIR_ALIGN
33 #undef NOISY_BLOCKDIR_ALIGN
34 #undef REALLY_NOISY_BLOCKDIR_ALIGN
35 #undef NOISY_REFLOW
36 #undef REALLY_NOISY_REFLOW
37 #undef NOISY_PUSHING
38 #undef REALLY_NOISY_PUSHING
39 #undef DEBUG_ADD_TEXT
40 #undef NOISY_MAX_ELEMENT_SIZE
41 #undef REALLY_NOISY_MAX_ELEMENT_SIZE
42 #undef NOISY_CAN_PLACE_FRAME
43 #undef NOISY_TRIM
44 #undef REALLY_NOISY_TRIM
45 #endif
47 using namespace mozilla;
49 //----------------------------------------------------------------------
51 #define FIX_BUG_50257
53 nsLineLayout::nsLineLayout(nsPresContext* aPresContext,
54 nsFloatManager* aFloatManager,
55 const nsHTMLReflowState* aOuterReflowState,
56 const nsLineList::iterator* aLine,
57 nsLineLayout* aBaseLineLayout)
58 : mPresContext(aPresContext),
59 mFloatManager(aFloatManager),
60 mBlockReflowState(aOuterReflowState),
61 mBaseLineLayout(aBaseLineLayout ? aBaseLineLayout->mBaseLineLayout : this),
62 mLastOptionalBreakFrame(nullptr),
63 mForceBreakFrame(nullptr),
64 mBlockRS(nullptr),/* XXX temporary */
65 mLastOptionalBreakPriority(gfxBreakPriority::eNoBreak),
66 mLastOptionalBreakFrameOffset(-1),
67 mForceBreakFrameOffset(-1),
68 mMinLineBSize(0),
69 mTextIndent(0),
70 mRubyReflowState(nullptr),
71 mFirstLetterStyleOK(false),
72 mIsTopOfPage(false),
73 mImpactedByFloats(false),
74 mLastFloatWasLetterFrame(false),
75 mLineIsEmpty(false),
76 mLineEndsInBR(false),
77 mNeedBackup(false),
78 mInFirstLine(false),
79 mGotLineBox(false),
80 mInFirstLetter(false),
81 mHasBullet(false),
82 mDirtyNextLine(false),
83 mLineAtStart(false),
84 mHasRuby(false)
86 MOZ_ASSERT(aOuterReflowState, "aOuterReflowState must not be null");
87 NS_ASSERTION(aFloatManager || aOuterReflowState->frame->GetType() ==
88 nsGkAtoms::letterFrame,
89 "float manager should be present");
90 MOZ_COUNT_CTOR(nsLineLayout);
92 // Stash away some style data that we need
93 nsBlockFrame* blockFrame = do_QueryFrame(aOuterReflowState->frame);
94 if (blockFrame)
95 mStyleText = blockFrame->StyleTextForLineLayout();
96 else
97 mStyleText = aOuterReflowState->frame->StyleText();
99 mLineNumber = 0;
100 mTotalPlacedFrames = 0;
101 mBStartEdge = 0;
102 mTrimmableISize = 0;
103 #ifdef DEBUG
104 mFinalLineBSize = nscoord_MIN;
105 #endif
107 mInflationMinFontSize =
108 nsLayoutUtils::InflationMinFontSizeFor(aOuterReflowState->frame);
110 // Instead of always pre-initializing the free-lists for frames and
111 // spans, we do it on demand so that situations that only use a few
112 // frames and spans won't waste a lot of time in unneeded
113 // initialization.
114 PL_INIT_ARENA_POOL(&mArena, "nsLineLayout", 1024);
115 mFrameFreeList = nullptr;
116 mSpanFreeList = nullptr;
118 mCurrentSpan = mRootSpan = nullptr;
119 mSpanDepth = 0;
121 if (aLine) {
122 mGotLineBox = true;
123 mLineBox = *aLine;
127 nsLineLayout::~nsLineLayout()
129 MOZ_COUNT_DTOR(nsLineLayout);
131 NS_ASSERTION(nullptr == mRootSpan, "bad line-layout user");
133 PL_FinishArenaPool(&mArena);
136 // Find out if the frame has a non-null prev-in-flow, i.e., whether it
137 // is a continuation.
138 inline bool
139 HasPrevInFlow(nsIFrame *aFrame)
141 nsIFrame *prevInFlow = aFrame->GetPrevInFlow();
142 return prevInFlow != nullptr;
145 void
146 nsLineLayout::BeginLineReflow(nscoord aICoord, nscoord aBCoord,
147 nscoord aISize, nscoord aBSize,
148 bool aImpactedByFloats,
149 bool aIsTopOfPage,
150 WritingMode aWritingMode,
151 nscoord aContainerWidth)
153 NS_ASSERTION(nullptr == mRootSpan, "bad linelayout user");
154 NS_WARN_IF_FALSE(aISize != NS_UNCONSTRAINEDSIZE,
155 "have unconstrained width; this should only result from "
156 "very large sizes, not attempts at intrinsic width "
157 "calculation");
158 #ifdef DEBUG
159 if ((aISize != NS_UNCONSTRAINEDSIZE) && CRAZY_SIZE(aISize)) {
160 nsFrame::ListTag(stdout, mBlockReflowState->frame);
161 printf(": Init: bad caller: width WAS %d(0x%x)\n",
162 aISize, aISize);
164 if ((aBSize != NS_UNCONSTRAINEDSIZE) && CRAZY_SIZE(aBSize)) {
165 nsFrame::ListTag(stdout, mBlockReflowState->frame);
166 printf(": Init: bad caller: height WAS %d(0x%x)\n",
167 aBSize, aBSize);
169 #endif
170 #ifdef NOISY_REFLOW
171 nsFrame::ListTag(stdout, mBlockReflowState->frame);
172 printf(": BeginLineReflow: %d,%d,%d,%d impacted=%s %s\n",
173 aICoord, aBCoord, aISize, aBSize,
174 aImpactedByFloats?"true":"false",
175 aIsTopOfPage ? "top-of-page" : "");
176 #endif
177 #ifdef DEBUG
178 mSpansAllocated = mSpansFreed = mFramesAllocated = mFramesFreed = 0;
179 #endif
181 mFirstLetterStyleOK = false;
182 mIsTopOfPage = aIsTopOfPage;
183 mImpactedByFloats = aImpactedByFloats;
184 mTotalPlacedFrames = 0;
185 if (mBaseLineLayout == this) {
186 mLineIsEmpty = true;
187 mLineAtStart = true;
188 } else {
189 mLineIsEmpty = false;
190 mLineAtStart = false;
192 mLineEndsInBR = false;
193 mSpanDepth = 0;
194 mMaxStartBoxBSize = mMaxEndBoxBSize = 0;
196 if (mGotLineBox) {
197 mLineBox->ClearHasBullet();
200 PerSpanData* psd = NewPerSpanData();
201 mCurrentSpan = mRootSpan = psd;
202 psd->mReflowState = mBlockReflowState;
203 psd->mIStart = aICoord;
204 psd->mICoord = aICoord;
205 psd->mIEnd = aICoord + aISize;
206 mContainerWidth = aContainerWidth;
208 // If we're in a constrained height frame, then we don't allow a
209 // max line box width to take effect.
210 if (!(LineContainerFrame()->GetStateBits() &
211 NS_FRAME_IN_CONSTRAINED_HEIGHT)) {
213 // If the available size is greater than the maximum line box width (if
214 // specified), then we need to adjust the line box width to be at the max
215 // possible width.
216 nscoord maxLineBoxWidth =
217 LineContainerFrame()->PresContext()->PresShell()->MaxLineBoxWidth();
219 if (maxLineBoxWidth > 0 &&
220 psd->mIEnd - psd->mIStart > maxLineBoxWidth) {
221 psd->mIEnd = psd->mIStart + maxLineBoxWidth;
225 mBStartEdge = aBCoord;
227 psd->mNoWrap =
228 !mStyleText->WhiteSpaceCanWrapStyle() || LineContainerFrame()->IsSVGText();
229 psd->mWritingMode = aWritingMode;
231 // If this is the first line of a block then see if the text-indent
232 // property amounts to anything.
234 if (0 == mLineNumber && !HasPrevInFlow(mBlockReflowState->frame)) {
235 const nsStyleCoord &textIndent = mStyleText->mTextIndent;
236 nscoord pctBasis = 0;
237 if (textIndent.HasPercent()) {
238 pctBasis =
239 nsHTMLReflowState::GetContainingBlockContentWidth(mBlockReflowState);
241 if (mGotLineBox) {
242 mLineBox->DisableResizeReflowOptimization();
245 nscoord indent = nsRuleNode::ComputeCoordPercentCalc(textIndent, pctBasis);
247 mTextIndent = indent;
249 psd->mICoord += indent;
252 PerFrameData* pfd = NewPerFrameData(mBlockReflowState->frame);
253 pfd->mAscent = 0;
254 pfd->mSpan = psd;
255 psd->mFrame = pfd;
258 void
259 nsLineLayout::EndLineReflow()
261 #ifdef NOISY_REFLOW
262 nsFrame::ListTag(stdout, mBlockReflowState->frame);
263 printf(": EndLineReflow: width=%d\n", mRootSpan->mICoord - mRootSpan->mIStart);
264 #endif
266 NS_ASSERTION(mBaseLineLayout == this ||
267 (!mSpansAllocated && !mSpansFreed && !mSpanFreeList &&
268 !mFramesAllocated && !mFramesFreed && !mFrameFreeList),
269 "Allocated frames or spans on non-base line layout?");
271 UnlinkFrame(mRootSpan->mFrame);
272 mCurrentSpan = mRootSpan = nullptr;
274 NS_ASSERTION(mSpansAllocated == mSpansFreed, "leak");
275 NS_ASSERTION(mFramesAllocated == mFramesFreed, "leak");
277 #if 0
278 static int32_t maxSpansAllocated = NS_LINELAYOUT_NUM_SPANS;
279 static int32_t maxFramesAllocated = NS_LINELAYOUT_NUM_FRAMES;
280 if (mSpansAllocated > maxSpansAllocated) {
281 printf("XXX: saw a line with %d spans\n", mSpansAllocated);
282 maxSpansAllocated = mSpansAllocated;
284 if (mFramesAllocated > maxFramesAllocated) {
285 printf("XXX: saw a line with %d frames\n", mFramesAllocated);
286 maxFramesAllocated = mFramesAllocated;
288 #endif
291 // XXX swtich to a single mAvailLineWidth that we adjust as each frame
292 // on the line is placed. Each span can still have a per-span mICoord that
293 // tracks where a child frame is going in its span; they don't need a
294 // per-span mIStart?
296 void
297 nsLineLayout::UpdateBand(const nsRect& aNewAvailSpace,
298 nsIFrame* aFloatFrame)
300 WritingMode lineWM = mRootSpan->mWritingMode;
301 LogicalRect availSpace(lineWM, aNewAvailSpace, mContainerWidth);
302 #ifdef REALLY_NOISY_REFLOW
303 printf("nsLL::UpdateBand %d, %d, %d, %d, (logical %d, %d, %d, %d); frame=%p\n will set mImpacted to true\n",
304 aNewAvailSpace.x, aNewAvailSpace.y,
305 aNewAvailSpace.width, aNewAvailSpace.height,
306 availSpace.IStart(lineWM), availSpace.BStart(lineWM),
307 availSpace.ISize(lineWM), availSpace.BSize(lineWM),
308 aFloatFrame);
309 #endif
310 #ifdef DEBUG
311 if ((availSpace.ISize(lineWM) != NS_UNCONSTRAINEDSIZE) &&
312 CRAZY_SIZE(availSpace.ISize(lineWM))) {
313 nsFrame::ListTag(stdout, mBlockReflowState->frame);
314 printf(": UpdateBand: bad caller: ISize WAS %d(0x%x)\n",
315 availSpace.ISize(lineWM), availSpace.ISize(lineWM));
317 if ((availSpace.BSize(lineWM) != NS_UNCONSTRAINEDSIZE) &&
318 CRAZY_SIZE(availSpace.BSize(lineWM))) {
319 nsFrame::ListTag(stdout, mBlockReflowState->frame);
320 printf(": UpdateBand: bad caller: BSize WAS %d(0x%x)\n",
321 availSpace.BSize(lineWM), availSpace.BSize(lineWM));
323 #endif
325 // Compute the difference between last times width and the new width
326 NS_WARN_IF_FALSE(mRootSpan->mIEnd != NS_UNCONSTRAINEDSIZE &&
327 aNewAvailSpace.width != NS_UNCONSTRAINEDSIZE,
328 "have unconstrained width; this should only result from "
329 "very large sizes, not attempts at intrinsic width "
330 "calculation");
331 // The root span's mIStart moves to aICoord
332 nscoord deltaICoord = availSpace.IStart(lineWM) - mRootSpan->mIStart;
333 // The width of all spans changes by this much (the root span's
334 // mIEnd moves to aICoord + aISize, its new width is aISize)
335 nscoord deltaISize = availSpace.ISize(lineWM) -
336 (mRootSpan->mIEnd - mRootSpan->mIStart);
337 #ifdef NOISY_REFLOW
338 nsFrame::ListTag(stdout, mBlockReflowState->frame);
339 printf(": UpdateBand: %d,%d,%d,%d deltaISize=%d deltaICoord=%d\n",
340 aNewAvailSpace.IStart(lineWM), aNewAvailSpace.BStart(lineWM),
341 aNewAvailSpace.ISize(lineWM), aNewAvailSpace.BSize(lineWM), deltaISize, deltaICoord);
342 #endif
344 // Update the root span position
345 mRootSpan->mIStart += deltaICoord;
346 mRootSpan->mIEnd += deltaICoord;
347 mRootSpan->mICoord += deltaICoord;
349 // Now update the right edges of the open spans to account for any
350 // change in available space width
351 for (PerSpanData* psd = mCurrentSpan; psd; psd = psd->mParent) {
352 psd->mIEnd += deltaISize;
353 psd->mContainsFloat = true;
354 #ifdef NOISY_REFLOW
355 printf(" span %p: oldIEnd=%d newIEnd=%d\n",
356 psd, psd->mIEnd - deltaISize, psd->mIEnd);
357 #endif
359 NS_ASSERTION(mRootSpan->mContainsFloat &&
360 mRootSpan->mIStart == availSpace.IStart(lineWM) &&
361 mRootSpan->mIEnd == availSpace.IEnd(lineWM),
362 "root span was updated incorrectly?");
364 // Update frame bounds
365 // Note: Only adjust the outermost frames (the ones that are direct
366 // children of the block), not the ones in the child spans. The reason
367 // is simple: the frames in the spans have coordinates local to their
368 // parent therefore they are moved when their parent span is moved.
369 if (deltaICoord != 0) {
370 for (PerFrameData* pfd = mRootSpan->mFirstFrame; pfd; pfd = pfd->mNext) {
371 pfd->mBounds.IStart(lineWM) += deltaICoord;
375 mBStartEdge = availSpace.BStart(lineWM);
376 mImpactedByFloats = true;
378 mLastFloatWasLetterFrame = nsGkAtoms::letterFrame == aFloatFrame->GetType();
381 nsLineLayout::PerSpanData*
382 nsLineLayout::NewPerSpanData()
384 PerSpanData* psd = mBaseLineLayout->mSpanFreeList;
385 if (!psd) {
386 void *mem;
387 size_t sz = sizeof(PerSpanData);
388 PL_ARENA_ALLOCATE(mem, &mBaseLineLayout->mArena, sz);
389 if (!mem) {
390 NS_ABORT_OOM(sz);
392 psd = reinterpret_cast<PerSpanData*>(mem);
394 else {
395 mBaseLineLayout->mSpanFreeList = psd->mNextFreeSpan;
397 psd->mParent = nullptr;
398 psd->mFrame = nullptr;
399 psd->mFirstFrame = nullptr;
400 psd->mLastFrame = nullptr;
401 psd->mContainsFloat = false;
402 psd->mZeroEffectiveSpanBox = false;
403 psd->mHasNonemptyContent = false;
405 #ifdef DEBUG
406 mBaseLineLayout->mSpansAllocated++;
407 #endif
408 return psd;
411 void
412 nsLineLayout::BeginSpan(nsIFrame* aFrame,
413 const nsHTMLReflowState* aSpanReflowState,
414 nscoord aIStart, nscoord aIEnd,
415 nscoord* aBaseline)
417 NS_ASSERTION(aIEnd != NS_UNCONSTRAINEDSIZE,
418 "should no longer be using unconstrained sizes");
419 #ifdef NOISY_REFLOW
420 nsFrame::IndentBy(stdout, mSpanDepth+1);
421 nsFrame::ListTag(stdout, aFrame);
422 printf(": BeginSpan leftEdge=%d rightEdge=%d\n", aIStart, aIEnd);
423 #endif
425 PerSpanData* psd = NewPerSpanData();
426 // Link up span frame's pfd to point to its child span data
427 PerFrameData* pfd = mCurrentSpan->mLastFrame;
428 NS_ASSERTION(pfd->mFrame == aFrame, "huh?");
429 pfd->mSpan = psd;
431 // Init new span
432 psd->mFrame = pfd;
433 psd->mParent = mCurrentSpan;
434 psd->mReflowState = aSpanReflowState;
435 psd->mIStart = aIStart;
436 psd->mICoord = aIStart;
437 psd->mIEnd = aIEnd;
438 psd->mBaseline = aBaseline;
440 nsIFrame* frame = aSpanReflowState->frame;
441 psd->mNoWrap = !frame->StyleText()->WhiteSpaceCanWrap(frame) ||
442 frame->StyleContext()->IsInlineDescendantOfRuby();
443 psd->mWritingMode = aSpanReflowState->GetWritingMode();
445 // Switch to new span
446 mCurrentSpan = psd;
447 mSpanDepth++;
450 nscoord
451 nsLineLayout::EndSpan(nsIFrame* aFrame)
453 NS_ASSERTION(mSpanDepth > 0, "end-span without begin-span");
454 #ifdef NOISY_REFLOW
455 nsFrame::IndentBy(stdout, mSpanDepth);
456 nsFrame::ListTag(stdout, aFrame);
457 printf(": EndSpan width=%d\n", mCurrentSpan->mICoord - mCurrentSpan->mIStart);
458 #endif
459 PerSpanData* psd = mCurrentSpan;
460 nscoord iSizeResult = psd->mLastFrame ? (psd->mICoord - psd->mIStart) : 0;
462 mSpanDepth--;
463 mCurrentSpan->mReflowState = nullptr; // no longer valid so null it out!
464 mCurrentSpan = mCurrentSpan->mParent;
465 return iSizeResult;
468 void
469 nsLineLayout::AttachFrameToBaseLineLayout(PerFrameData* aFrame)
471 NS_PRECONDITION(this != mBaseLineLayout,
472 "This method must not be called in a base line layout.");
474 PerFrameData* baseFrame = mBaseLineLayout->LastFrame();
475 MOZ_ASSERT(aFrame && baseFrame);
476 MOZ_ASSERT(!aFrame->mIsLinkedToBase,
477 "The frame must not have been linked with the base");
479 aFrame->mNextAnnotation = baseFrame->mNextAnnotation;
480 baseFrame->mNextAnnotation = aFrame;
481 aFrame->mIsLinkedToBase = true;
484 int32_t
485 nsLineLayout::GetCurrentSpanCount() const
487 NS_ASSERTION(mCurrentSpan == mRootSpan, "bad linelayout user");
488 int32_t count = 0;
489 PerFrameData* pfd = mRootSpan->mFirstFrame;
490 while (nullptr != pfd) {
491 count++;
492 pfd = pfd->mNext;
494 return count;
497 void
498 nsLineLayout::SplitLineTo(int32_t aNewCount)
500 NS_ASSERTION(mCurrentSpan == mRootSpan, "bad linelayout user");
502 #ifdef REALLY_NOISY_PUSHING
503 printf("SplitLineTo %d (current count=%d); before:\n", aNewCount,
504 GetCurrentSpanCount());
505 DumpPerSpanData(mRootSpan, 1);
506 #endif
507 PerSpanData* psd = mRootSpan;
508 PerFrameData* pfd = psd->mFirstFrame;
509 while (nullptr != pfd) {
510 if (--aNewCount == 0) {
511 // Truncate list at pfd (we keep pfd, but anything following is freed)
512 PerFrameData* next = pfd->mNext;
513 pfd->mNext = nullptr;
514 psd->mLastFrame = pfd;
516 // Now unlink all of the frames following pfd
517 UnlinkFrame(next);
518 break;
520 pfd = pfd->mNext;
522 #ifdef NOISY_PUSHING
523 printf("SplitLineTo %d (current count=%d); after:\n", aNewCount,
524 GetCurrentSpanCount());
525 DumpPerSpanData(mRootSpan, 1);
526 #endif
529 void
530 nsLineLayout::PushFrame(nsIFrame* aFrame)
532 PerSpanData* psd = mCurrentSpan;
533 NS_ASSERTION(psd->mLastFrame->mFrame == aFrame, "pushing non-last frame");
535 #ifdef REALLY_NOISY_PUSHING
536 nsFrame::IndentBy(stdout, mSpanDepth);
537 printf("PushFrame %p, before:\n", psd);
538 DumpPerSpanData(psd, 1);
539 #endif
541 // Take the last frame off of the span's frame list
542 PerFrameData* pfd = psd->mLastFrame;
543 if (pfd == psd->mFirstFrame) {
544 // We are pushing away the only frame...empty the list
545 psd->mFirstFrame = nullptr;
546 psd->mLastFrame = nullptr;
548 else {
549 PerFrameData* prevFrame = pfd->mPrev;
550 prevFrame->mNext = nullptr;
551 psd->mLastFrame = prevFrame;
554 // Now unlink the frame
555 MOZ_ASSERT(!pfd->mNext);
556 UnlinkFrame(pfd);
557 #ifdef NOISY_PUSHING
558 nsFrame::IndentBy(stdout, mSpanDepth);
559 printf("PushFrame: %p after:\n", psd);
560 DumpPerSpanData(psd, 1);
561 #endif
564 void
565 nsLineLayout::UnlinkFrame(PerFrameData* pfd)
567 while (nullptr != pfd) {
568 PerFrameData* next = pfd->mNext;
569 if (pfd->mIsLinkedToBase) {
570 // This frame is linked to a ruby base, and should not be freed
571 // now. Just unlink it from the span. It will be freed when its
572 // base frame gets unlinked.
573 pfd->mNext = pfd->mPrev = nullptr;
574 pfd = next;
575 continue;
578 // It is a ruby base frame. If there are any annotations
579 // linked to this frame, free them first.
580 PerFrameData* annotationPFD = pfd->mNextAnnotation;
581 while (annotationPFD) {
582 PerFrameData* nextAnnotation = annotationPFD->mNextAnnotation;
583 MOZ_ASSERT(annotationPFD->mNext == nullptr &&
584 annotationPFD->mPrev == nullptr,
585 "PFD in annotations should have been unlinked.");
586 FreeFrame(annotationPFD);
587 annotationPFD = nextAnnotation;
590 FreeFrame(pfd);
591 pfd = next;
595 void
596 nsLineLayout::FreeFrame(PerFrameData* pfd)
598 if (nullptr != pfd->mSpan) {
599 FreeSpan(pfd->mSpan);
601 pfd->mNext = mBaseLineLayout->mFrameFreeList;
602 mBaseLineLayout->mFrameFreeList = pfd;
603 #ifdef DEBUG
604 mBaseLineLayout->mFramesFreed++;
605 #endif
608 void
609 nsLineLayout::FreeSpan(PerSpanData* psd)
611 // Unlink its frames
612 UnlinkFrame(psd->mFirstFrame);
614 // Now put the span on the free list since it's free too
615 psd->mNextFreeSpan = mBaseLineLayout->mSpanFreeList;
616 mBaseLineLayout->mSpanFreeList = psd;
617 #ifdef DEBUG
618 mBaseLineLayout->mSpansFreed++;
619 #endif
622 bool
623 nsLineLayout::IsZeroBSize()
625 PerSpanData* psd = mCurrentSpan;
626 PerFrameData* pfd = psd->mFirstFrame;
627 while (nullptr != pfd) {
628 if (0 != pfd->mBounds.BSize(psd->mWritingMode)) {
629 return false;
631 pfd = pfd->mNext;
633 return true;
636 nsLineLayout::PerFrameData*
637 nsLineLayout::NewPerFrameData(nsIFrame* aFrame)
639 PerFrameData* pfd = mBaseLineLayout->mFrameFreeList;
640 if (!pfd) {
641 void *mem;
642 size_t sz = sizeof(PerFrameData);
643 PL_ARENA_ALLOCATE(mem, &mBaseLineLayout->mArena, sz);
644 if (!mem) {
645 NS_ABORT_OOM(sz);
647 pfd = reinterpret_cast<PerFrameData*>(mem);
649 else {
650 mBaseLineLayout->mFrameFreeList = pfd->mNext;
652 pfd->mSpan = nullptr;
653 pfd->mNext = nullptr;
654 pfd->mPrev = nullptr;
655 pfd->mNextAnnotation = nullptr;
656 pfd->mFrame = aFrame;
658 // all flags default to false
659 pfd->mRelativePos = false;
660 pfd->mIsTextFrame = false;
661 pfd->mIsNonEmptyTextFrame = false;
662 pfd->mIsNonWhitespaceTextFrame = false;
663 pfd->mIsLetterFrame = false;
664 pfd->mRecomputeOverflow = false;
665 pfd->mIsBullet = false;
666 pfd->mSkipWhenTrimmingWhitespace = false;
667 pfd->mIsEmpty = false;
668 pfd->mIsLinkedToBase = false;
670 WritingMode frameWM = aFrame->GetWritingMode();
671 WritingMode lineWM = mRootSpan->mWritingMode;
672 pfd->mBounds = LogicalRect(lineWM);
673 pfd->mMargin = LogicalMargin(lineWM);
674 pfd->mBorderPadding = LogicalMargin(lineWM);
675 pfd->mOffsets = LogicalMargin(frameWM);
677 pfd->mJustificationInfo = JustificationInfo();
678 pfd->mJustificationAssignment = JustificationAssignment();
680 #ifdef DEBUG
681 pfd->mBlockDirAlign = 0xFF;
682 mBaseLineLayout->mFramesAllocated++;
683 #endif
684 return pfd;
687 bool
688 nsLineLayout::LineIsBreakable() const
690 // XXX mTotalPlacedFrames should go away and we should just use
691 // mLineIsEmpty here instead
692 if ((0 != mTotalPlacedFrames) || mImpactedByFloats) {
693 return true;
695 return false;
698 // Checks all four sides for percentage units. This means it should
699 // only be used for things (margin, padding) where percentages on top
700 // and bottom depend on the *width* just like percentages on left and
701 // right.
702 static bool
703 HasPercentageUnitSide(const nsStyleSides& aSides)
705 NS_FOR_CSS_SIDES(side) {
706 if (aSides.Get(side).HasPercent())
707 return true;
709 return false;
712 static bool
713 IsPercentageAware(const nsIFrame* aFrame)
715 NS_ASSERTION(aFrame, "null frame is not allowed");
717 nsIAtom *fType = aFrame->GetType();
718 if (fType == nsGkAtoms::textFrame) {
719 // None of these things can ever be true for text frames.
720 return false;
723 // Some of these things don't apply to non-replaced inline frames
724 // (that is, fType == nsGkAtoms::inlineFrame), but we won't bother making
725 // things unnecessarily complicated, since they'll probably be set
726 // quite rarely.
728 const nsStyleMargin* margin = aFrame->StyleMargin();
729 if (HasPercentageUnitSide(margin->mMargin)) {
730 return true;
733 const nsStylePadding* padding = aFrame->StylePadding();
734 if (HasPercentageUnitSide(padding->mPadding)) {
735 return true;
738 // Note that borders can't be aware of percentages
740 const nsStylePosition* pos = aFrame->StylePosition();
742 if ((pos->WidthDependsOnContainer() &&
743 pos->mWidth.GetUnit() != eStyleUnit_Auto) ||
744 pos->MaxWidthDependsOnContainer() ||
745 pos->MinWidthDependsOnContainer() ||
746 pos->OffsetHasPercent(NS_SIDE_RIGHT) ||
747 pos->OffsetHasPercent(NS_SIDE_LEFT)) {
748 return true;
751 if (eStyleUnit_Auto == pos->mWidth.GetUnit()) {
752 // We need to check for frames that shrink-wrap when they're auto
753 // width.
754 const nsStyleDisplay* disp = aFrame->StyleDisplay();
755 if (disp->mDisplay == NS_STYLE_DISPLAY_INLINE_BLOCK ||
756 disp->mDisplay == NS_STYLE_DISPLAY_INLINE_TABLE ||
757 fType == nsGkAtoms::HTMLButtonControlFrame ||
758 fType == nsGkAtoms::gfxButtonControlFrame ||
759 fType == nsGkAtoms::fieldSetFrame ||
760 fType == nsGkAtoms::comboboxDisplayFrame) {
761 return true;
764 // Per CSS 2.1, section 10.3.2:
765 // If 'height' and 'width' both have computed values of 'auto' and
766 // the element has an intrinsic ratio but no intrinsic height or
767 // width and the containing block's width does not itself depend
768 // on the replaced element's width, then the used value of 'width'
769 // is calculated from the constraint equation used for
770 // block-level, non-replaced elements in normal flow.
771 nsIFrame *f = const_cast<nsIFrame*>(aFrame);
772 if (f->GetIntrinsicRatio() != nsSize(0, 0) &&
773 // Some percents are treated like 'auto', so check != coord
774 pos->mHeight.GetUnit() != eStyleUnit_Coord) {
775 const IntrinsicSize &intrinsicSize = f->GetIntrinsicSize();
776 if (intrinsicSize.width.GetUnit() == eStyleUnit_None &&
777 intrinsicSize.height.GetUnit() == eStyleUnit_None) {
778 return true;
783 return false;
786 void
787 nsLineLayout::ReflowFrame(nsIFrame* aFrame,
788 nsReflowStatus& aReflowStatus,
789 nsHTMLReflowMetrics* aMetrics,
790 bool& aPushedFrame)
792 // Initialize OUT parameter
793 aPushedFrame = false;
795 PerFrameData* pfd = NewPerFrameData(aFrame);
796 PerSpanData* psd = mCurrentSpan;
797 psd->AppendFrame(pfd);
799 #ifdef REALLY_NOISY_REFLOW
800 nsFrame::IndentBy(stdout, mSpanDepth);
801 printf("%p: Begin ReflowFrame pfd=%p ", psd, pfd);
802 nsFrame::ListTag(stdout, aFrame);
803 printf("\n");
804 #endif
806 if (mCurrentSpan == mRootSpan) {
807 pfd->mFrame->Properties().Remove(nsIFrame::LineBaselineOffset());
808 } else {
809 #ifdef DEBUG
810 bool hasLineOffset;
811 pfd->mFrame->Properties().Get(nsIFrame::LineBaselineOffset(), &hasLineOffset);
812 NS_ASSERTION(!hasLineOffset, "LineBaselineOffset was set but was not expected");
813 #endif
816 mJustificationInfo = JustificationInfo();
818 // Stash copies of some of the computed state away for later
819 // (block-direction alignment, for example)
820 WritingMode frameWM = aFrame->GetWritingMode();
821 WritingMode lineWM = mRootSpan->mWritingMode;
823 // NOTE: While the inline direction coordinate remains relative to the
824 // parent span, the block direction coordinate is fixed at the top
825 // edge for the line. During VerticalAlignFrames we will repair this
826 // so that the block direction coordinate is properly set and relative
827 // to the appropriate span.
828 pfd->mBounds.IStart(lineWM) = psd->mICoord;
829 pfd->mBounds.BStart(lineWM) = mBStartEdge;
831 // We want to guarantee that we always make progress when
832 // formatting. Therefore, if the object being placed on the line is
833 // too big for the line, but it is the only thing on the line and is not
834 // impacted by a float, then we go ahead and place it anyway. (If the line
835 // is impacted by one or more floats, then it is safe to break because
836 // we can move the line down below float(s).)
838 // Capture this state *before* we reflow the frame in case it clears
839 // the state out. We need to know how to treat the current frame
840 // when breaking.
841 bool notSafeToBreak = LineIsEmpty() && !mImpactedByFloats;
843 // Figure out whether we're talking about a textframe here
844 nsIAtom* frameType = aFrame->GetType();
845 bool isText = frameType == nsGkAtoms::textFrame;
847 // Inline-ish and text-ish things don't compute their width;
848 // everything else does. We need to give them an available width that
849 // reflects the space left on the line.
850 NS_WARN_IF_FALSE(psd->mIEnd != NS_UNCONSTRAINEDSIZE,
851 "have unconstrained width; this should only result from "
852 "very large sizes, not attempts at intrinsic width "
853 "calculation");
854 nscoord availableSpaceOnLine = psd->mIEnd - psd->mICoord;
856 // Setup reflow state for reflowing the frame
857 Maybe<nsHTMLReflowState> reflowStateHolder;
858 if (!isText) {
859 // Compute the available size for the frame. This available width
860 // includes room for the side margins.
861 // For now, set the available block-size to unconstrained always.
862 LogicalSize availSize = mBlockReflowState->ComputedSize(frameWM);
863 availSize.BSize(frameWM) = NS_UNCONSTRAINEDSIZE;
864 reflowStateHolder.emplace(mPresContext, *psd->mReflowState,
865 aFrame, availSize);
866 nsHTMLReflowState& reflowState = *reflowStateHolder;
867 reflowState.mLineLayout = this;
868 if (mRubyReflowState) {
869 reflowState.mRubyReflowState = mRubyReflowState;
870 mRubyReflowState = nullptr;
872 reflowState.mFlags.mIsTopOfPage = mIsTopOfPage;
873 if (reflowState.ComputedISize() == NS_UNCONSTRAINEDSIZE) {
874 reflowState.AvailableISize() = availableSpaceOnLine;
876 WritingMode stateWM = reflowState.GetWritingMode();
877 pfd->mMargin =
878 reflowState.ComputedLogicalMargin().ConvertTo(lineWM, stateWM);
879 pfd->mBorderPadding =
880 reflowState.ComputedLogicalBorderPadding().ConvertTo(lineWM, stateWM);
881 pfd->mRelativePos =
882 reflowState.mStyleDisplay->IsRelativelyPositionedStyle();
883 if (pfd->mRelativePos) {
884 pfd->mOffsets =
885 reflowState.ComputedLogicalOffsets().ConvertTo(frameWM, stateWM);
888 // Calculate whether the the frame should have a start margin and
889 // subtract the margin from the available width if necessary.
890 // The margin will be applied to the starting inline coordinates of
891 // the frame in CanPlaceFrame() after reflowing the frame.
892 AllowForStartMargin(pfd, reflowState);
894 // if isText(), no need to propagate NS_FRAME_IS_DIRTY from the parent,
895 // because reflow doesn't look at the dirty bits on the frame being reflowed.
897 // See if this frame depends on the width of its containing block. If
898 // so, disable resize reflow optimizations for the line. (Note that,
899 // to be conservative, we do this if we *try* to fit a frame on a
900 // line, even if we don't succeed.) (Note also that we can only make
901 // this IsPercentageAware check *after* we've constructed our
902 // nsHTMLReflowState, because that construction may be what forces aFrame
903 // to lazily initialize its (possibly-percent-valued) intrinsic size.)
904 if (mGotLineBox && IsPercentageAware(aFrame)) {
905 mLineBox->DisableResizeReflowOptimization();
908 // Let frame know that are reflowing it. Note that we don't bother
909 // positioning the frame yet, because we're probably going to end up
910 // moving it when we do the block-direction alignment
911 aFrame->WillReflow(mPresContext);
913 // Adjust spacemanager coordinate system for the frame.
914 nsHTMLReflowMetrics metrics(lineWM);
915 #ifdef DEBUG
916 metrics.ISize(lineWM) = nscoord(0xdeadbeef);
917 metrics.BSize(lineWM) = nscoord(0xdeadbeef);
918 #endif
919 nsRect physicalBounds = pfd->mBounds.GetPhysicalRect(lineWM, mContainerWidth);
920 nscoord tx = physicalBounds.x;
921 nscoord ty = physicalBounds.y;
922 mFloatManager->Translate(tx, ty);
924 int32_t savedOptionalBreakOffset;
925 gfxBreakPriority savedOptionalBreakPriority;
926 nsIFrame* savedOptionalBreakFrame =
927 GetLastOptionalBreakPosition(&savedOptionalBreakOffset,
928 &savedOptionalBreakPriority);
930 if (!isText) {
931 aFrame->Reflow(mPresContext, metrics, *reflowStateHolder, aReflowStatus);
932 } else {
933 static_cast<nsTextFrame*>(aFrame)->
934 ReflowText(*this, availableSpaceOnLine, psd->mReflowState->rendContext,
935 metrics, aReflowStatus);
938 pfd->mJustificationInfo = mJustificationInfo;
939 mJustificationInfo = JustificationInfo();
941 // See if the frame is a placeholderFrame and if it is process
942 // the float. At the same time, check if the frame has any non-collapsed-away
943 // content.
944 bool placedFloat = false;
945 bool isEmpty;
946 if (!frameType) {
947 isEmpty = pfd->mFrame->IsEmpty();
948 } else {
949 if (nsGkAtoms::placeholderFrame == frameType) {
950 isEmpty = true;
951 pfd->mSkipWhenTrimmingWhitespace = true;
952 nsIFrame* outOfFlowFrame = nsLayoutUtils::GetFloatFromPlaceholder(aFrame);
953 if (outOfFlowFrame) {
954 // Add mTrimmableISize to the available width since if the line ends
955 // here, the width of the inline content will be reduced by
956 // mTrimmableISize.
957 nscoord availableISize = psd->mIEnd - (psd->mICoord - mTrimmableISize);
958 if (psd->mNoWrap) {
959 // If we place floats after inline content where there's
960 // no break opportunity, we don't know how much additional
961 // width is required for the non-breaking content after the float,
962 // so we can't know whether the float plus that content will fit
963 // on the line. So for now, don't place floats after inline
964 // content where there's no break opportunity. This is incorrect
965 // but hopefully rare. Fixing it will require significant
966 // restructuring of line layout.
967 // We might as well allow zero-width floats to be placed, though.
968 availableISize = 0;
970 placedFloat = mBaseLineLayout->AddFloat(outOfFlowFrame, availableISize);
971 NS_ASSERTION(!(outOfFlowFrame->GetType() == nsGkAtoms::letterFrame &&
972 GetFirstLetterStyleOK()),
973 "FirstLetterStyle set on line with floating first letter");
976 else if (isText) {
977 // Note non-empty text-frames for inline frame compatibility hackery
978 pfd->mIsTextFrame = true;
979 nsTextFrame* textFrame = static_cast<nsTextFrame*>(pfd->mFrame);
980 isEmpty = !textFrame->HasNoncollapsedCharacters();
981 if (!isEmpty) {
982 pfd->mIsNonEmptyTextFrame = true;
983 nsIContent* content = textFrame->GetContent();
985 const nsTextFragment* frag = content->GetText();
986 if (frag) {
987 pfd->mIsNonWhitespaceTextFrame = !content->TextIsOnlyWhitespace();
991 else if (nsGkAtoms::brFrame == frameType) {
992 pfd->mSkipWhenTrimmingWhitespace = true;
993 isEmpty = false;
994 } else {
995 if (nsGkAtoms::letterFrame==frameType) {
996 pfd->mIsLetterFrame = true;
997 } else if (nsGkAtoms::rubyFrame == frameType) {
998 SyncAnnotationContainersBounds(pfd);
1000 if (pfd->mSpan) {
1001 isEmpty = !pfd->mSpan->mHasNonemptyContent && pfd->mFrame->IsSelfEmpty();
1002 } else {
1003 isEmpty = pfd->mFrame->IsEmpty();
1007 pfd->mIsEmpty = isEmpty;
1009 mFloatManager->Translate(-tx, -ty);
1011 NS_ASSERTION(metrics.ISize(lineWM) >= 0, "bad inline size");
1012 NS_ASSERTION(metrics.BSize(lineWM) >= 0,"bad block size");
1013 if (metrics.ISize(lineWM) < 0) {
1014 metrics.ISize(lineWM) = 0;
1016 if (metrics.BSize(lineWM) < 0) {
1017 metrics.BSize(lineWM) = 0;
1020 #ifdef DEBUG
1021 // Note: break-before means ignore the reflow metrics since the
1022 // frame will be reflowed another time.
1023 if (!NS_INLINE_IS_BREAK_BEFORE(aReflowStatus)) {
1024 if (CRAZY_SIZE(metrics.ISize(lineWM)) ||
1025 CRAZY_SIZE(metrics.BSize(lineWM))) {
1026 printf("nsLineLayout: ");
1027 nsFrame::ListTag(stdout, aFrame);
1028 printf(" metrics=%d,%d!\n", metrics.Width(), metrics.Height());
1030 if ((metrics.Width() == nscoord(0xdeadbeef)) ||
1031 (metrics.Height() == nscoord(0xdeadbeef))) {
1032 printf("nsLineLayout: ");
1033 nsFrame::ListTag(stdout, aFrame);
1034 printf(" didn't set w/h %d,%d!\n", metrics.Width(), metrics.Height());
1037 #endif
1039 // Unlike with non-inline reflow, the overflow area here does *not*
1040 // include the accumulation of the frame's bounds and its inline
1041 // descendants' bounds. Nor does it include the outline area; it's
1042 // just the union of the bounds of any absolute children. That is
1043 // added in later by nsLineLayout::ReflowInlineFrames.
1044 pfd->mOverflowAreas = metrics.mOverflowAreas;
1046 pfd->mBounds.ISize(lineWM) = metrics.ISize(lineWM);
1047 pfd->mBounds.BSize(lineWM) = metrics.BSize(lineWM);
1049 // Size the frame, but |RelativePositionFrames| will size the view.
1050 aFrame->SetRect(lineWM, pfd->mBounds, ContainerWidthForSpan(psd));
1052 // Tell the frame that we're done reflowing it
1053 aFrame->DidReflow(mPresContext,
1054 isText ? nullptr : reflowStateHolder.ptr(),
1055 nsDidReflowStatus::FINISHED);
1057 if (aMetrics) {
1058 *aMetrics = metrics;
1061 if (!NS_INLINE_IS_BREAK_BEFORE(aReflowStatus)) {
1062 // If frame is complete and has a next-in-flow, we need to delete
1063 // them now. Do not do this when a break-before is signaled because
1064 // the frame is going to get reflowed again (and may end up wanting
1065 // a next-in-flow where it ends up).
1066 if (NS_FRAME_IS_COMPLETE(aReflowStatus)) {
1067 nsIFrame* kidNextInFlow = aFrame->GetNextInFlow();
1068 if (nullptr != kidNextInFlow) {
1069 // Remove all of the childs next-in-flows. Make sure that we ask
1070 // the right parent to do the removal (it's possible that the
1071 // parent is not this because we are executing pullup code)
1072 kidNextInFlow->GetParent()->
1073 DeleteNextInFlowChild(kidNextInFlow, true);
1077 // Check whether this frame breaks up text runs. All frames break up text
1078 // runs (hence return false here) except for text frames and inline containers.
1079 bool continuingTextRun = aFrame->CanContinueTextRun();
1081 // Clear any residual mTrimmableISize if this isn't a text frame
1082 if (!continuingTextRun && !pfd->mSkipWhenTrimmingWhitespace) {
1083 mTrimmableISize = 0;
1086 // See if we can place the frame. If we can't fit it, then we
1087 // return now.
1088 bool optionalBreakAfterFits;
1089 NS_ASSERTION(isText ||
1090 !reflowStateHolder->IsFloating(),
1091 "How'd we get a floated inline frame? "
1092 "The frame ctor should've dealt with this.");
1093 if (CanPlaceFrame(pfd, notSafeToBreak, continuingTextRun,
1094 savedOptionalBreakFrame != nullptr, metrics,
1095 aReflowStatus, &optionalBreakAfterFits)) {
1096 if (!isEmpty) {
1097 psd->mHasNonemptyContent = true;
1098 mLineIsEmpty = false;
1099 if (!pfd->mSpan) {
1100 // nonempty leaf content has been placed
1101 mLineAtStart = false;
1103 if (nsGkAtoms::rubyFrame == frameType) {
1104 mHasRuby = true;
1108 // Place the frame, updating aBounds with the final size and
1109 // location. Then apply the bottom+right margins (as
1110 // appropriate) to the frame.
1111 PlaceFrame(pfd, metrics);
1112 PerSpanData* span = pfd->mSpan;
1113 if (span) {
1114 // The frame we just finished reflowing is an inline
1115 // container. It needs its child frames aligned in the block direction,
1116 // so do most of it now.
1117 VerticalAlignFrames(span);
1120 if (!continuingTextRun) {
1121 if (!psd->mNoWrap && (!LineIsEmpty() || placedFloat)) {
1122 // record soft break opportunity after this content that can't be
1123 // part of a text run. This is not a text frame so we know
1124 // that offset INT32_MAX means "after the content".
1125 if (NotifyOptionalBreakPosition(aFrame, INT32_MAX,
1126 optionalBreakAfterFits,
1127 gfxBreakPriority::eNormalBreak)) {
1128 // If this returns true then we are being told to actually break here.
1129 aReflowStatus = NS_INLINE_LINE_BREAK_AFTER(aReflowStatus);
1134 else {
1135 PushFrame(aFrame);
1136 aPushedFrame = true;
1137 // Undo any saved break positions that the frame might have told us about,
1138 // since we didn't end up placing it
1139 RestoreSavedBreakPosition(savedOptionalBreakFrame,
1140 savedOptionalBreakOffset,
1141 savedOptionalBreakPriority);
1144 else {
1145 PushFrame(aFrame);
1148 #ifdef REALLY_NOISY_REFLOW
1149 nsFrame::IndentBy(stdout, mSpanDepth);
1150 printf("End ReflowFrame ");
1151 nsFrame::ListTag(stdout, aFrame);
1152 printf(" status=%x\n", aReflowStatus);
1153 #endif
1156 void
1157 nsLineLayout::AllowForStartMargin(PerFrameData* pfd,
1158 nsHTMLReflowState& aReflowState)
1160 NS_ASSERTION(!aReflowState.IsFloating(),
1161 "How'd we get a floated inline frame? "
1162 "The frame ctor should've dealt with this.");
1164 WritingMode lineWM = mRootSpan->mWritingMode;
1166 // Only apply start-margin on the first-in flow for inline frames,
1167 // and make sure to not apply it to any inline other than the first
1168 // in an ib split. Note that the ib sibling (block-in-inline
1169 // sibling) annotations only live on the first continuation, but we
1170 // don't want to apply the start margin for later continuations
1171 // anyway. For box-decoration-break:clone we apply the start-margin
1172 // on all continuations.
1173 if ((pfd->mFrame->GetPrevContinuation() ||
1174 pfd->mFrame->FrameIsNonFirstInIBSplit()) &&
1175 aReflowState.mStyleBorder->mBoxDecorationBreak ==
1176 NS_STYLE_BOX_DECORATION_BREAK_SLICE) {
1177 // Zero this out so that when we compute the max-element-width of
1178 // the frame we will properly avoid adding in the starting margin.
1179 pfd->mMargin.IStart(lineWM) = 0;
1180 } else {
1181 NS_WARN_IF_FALSE(NS_UNCONSTRAINEDSIZE != aReflowState.AvailableISize(),
1182 "have unconstrained inline-size; this should only result "
1183 "from very large sizes, not attempts at intrinsic "
1184 "inline-size calculation");
1185 if (NS_UNCONSTRAINEDSIZE == aReflowState.ComputedISize()) {
1186 // For inline-ish and text-ish things (which don't compute widths
1187 // in the reflow state), adjust available inline-size to account for the
1188 // start margin. The end margin will be accounted for when we
1189 // finish flowing the frame.
1190 WritingMode wm = aReflowState.GetWritingMode();
1191 aReflowState.AvailableISize() -=
1192 pfd->mMargin.ConvertTo(wm, lineWM).IStart(wm);
1197 nscoord
1198 nsLineLayout::GetCurrentFrameInlineDistanceFromBlock()
1200 PerSpanData* psd;
1201 nscoord x = 0;
1202 for (psd = mCurrentSpan; psd; psd = psd->mParent) {
1203 x += psd->mICoord;
1205 return x;
1209 * This method syncs all ruby annotation containers' bounds in their
1210 * PerFrameData from their rect. It is necessary to do so because the
1211 * containers are not part of line in their levels, which means their
1212 * bounds are not set properly before.
1214 void
1215 nsLineLayout::SyncAnnotationContainersBounds(PerFrameData* aRubyFrame)
1217 MOZ_ASSERT(aRubyFrame->mFrame->GetType() == nsGkAtoms::rubyFrame);
1218 MOZ_ASSERT(aRubyFrame->mSpan);
1220 PerSpanData* span = aRubyFrame->mSpan;
1221 WritingMode lineWM = mRootSpan->mWritingMode;
1222 nscoord containerWidth = ContainerWidthForSpan(span);
1223 for (PerFrameData* pfd = span->mFirstFrame; pfd; pfd = pfd->mNext) {
1224 for (PerFrameData* annotation = pfd->mNextAnnotation;
1225 annotation; annotation = annotation->mNextAnnotation) {
1226 LogicalRect bounds(lineWM, annotation->mFrame->GetRect(), containerWidth);
1227 annotation->mBounds = bounds;
1233 * See if the frame can be placed now that we know it's desired size.
1234 * We can always place the frame if the line is empty. Note that we
1235 * know that the reflow-status is not a break-before because if it was
1236 * ReflowFrame above would have returned false, preventing this method
1237 * from being called. The logic in this method assumes that.
1239 * Note that there is no check against the Y coordinate because we
1240 * assume that the caller will take care of that.
1242 bool
1243 nsLineLayout::CanPlaceFrame(PerFrameData* pfd,
1244 bool aNotSafeToBreak,
1245 bool aFrameCanContinueTextRun,
1246 bool aCanRollBackBeforeFrame,
1247 nsHTMLReflowMetrics& aMetrics,
1248 nsReflowStatus& aStatus,
1249 bool* aOptionalBreakAfterFits)
1251 NS_PRECONDITION(pfd && pfd->mFrame, "bad args, null pointers for frame data");
1253 *aOptionalBreakAfterFits = true;
1255 WritingMode lineWM = mRootSpan->mWritingMode;
1257 * We want to only apply the end margin if we're the last continuation and
1258 * either not in an {ib} split or the last inline in it. In all other
1259 * cases we want to zero it out. That means zeroing it out if any of these
1260 * conditions hold:
1261 * 1) The frame is not complete (in this case it will get a next-in-flow)
1262 * 2) The frame is complete but has a non-fluid continuation on its
1263 * continuation chain. Note that if it has a fluid continuation, that
1264 * continuation will get destroyed later, so we don't want to drop the
1265 * end-margin in that case.
1266 * 3) The frame is in an {ib} split and is not the last part.
1268 * However, none of that applies if this is a letter frame (XXXbz why?)
1270 * For box-decoration-break:clone we apply the end margin on all
1271 * continuations (that are not letter frames).
1273 if ((NS_FRAME_IS_NOT_COMPLETE(aStatus) ||
1274 pfd->mFrame->LastInFlow()->GetNextContinuation() ||
1275 pfd->mFrame->FrameIsNonLastInIBSplit()) &&
1276 !pfd->mIsLetterFrame &&
1277 pfd->mFrame->StyleBorder()->mBoxDecorationBreak ==
1278 NS_STYLE_BOX_DECORATION_BREAK_SLICE) {
1279 pfd->mMargin.IEnd(lineWM) = 0;
1282 // Apply the start margin to the frame bounds.
1283 nscoord startMargin = pfd->mMargin.IStart(lineWM);
1284 nscoord endMargin = pfd->mMargin.IEnd(lineWM);
1286 pfd->mBounds.IStart(lineWM) += startMargin;
1288 PerSpanData* psd = mCurrentSpan;
1289 if (psd->mNoWrap) {
1290 // When wrapping is off, everything fits.
1291 return true;
1294 #ifdef NOISY_CAN_PLACE_FRAME
1295 if (nullptr != psd->mFrame) {
1296 nsFrame::ListTag(stdout, psd->mFrame->mFrame);
1298 printf(": aNotSafeToBreak=%s frame=", aNotSafeToBreak ? "true" : "false");
1299 nsFrame::ListTag(stdout, pfd->mFrame);
1300 printf(" frameWidth=%d, margins=%d,%d\n",
1301 pfd->mBounds.IEnd(lineWM) + endMargin - psd->mICoord,
1302 startMargin, endMargin);
1303 #endif
1305 // Set outside to true if the result of the reflow leads to the
1306 // frame sticking outside of our available area.
1307 bool outside = pfd->mBounds.IEnd(lineWM) - mTrimmableISize + endMargin >
1308 psd->mIEnd;
1309 if (!outside) {
1310 // If it fits, it fits
1311 #ifdef NOISY_CAN_PLACE_FRAME
1312 printf(" ==> inside\n");
1313 #endif
1314 return true;
1316 *aOptionalBreakAfterFits = false;
1318 // When it doesn't fit, check for a few special conditions where we
1319 // allow it to fit anyway.
1320 if (0 == startMargin + pfd->mBounds.ISize(lineWM) + endMargin) {
1321 // Empty frames always fit right where they are
1322 #ifdef NOISY_CAN_PLACE_FRAME
1323 printf(" ==> empty frame fits\n");
1324 #endif
1325 return true;
1328 #ifdef FIX_BUG_50257
1329 // another special case: always place a BR
1330 if (nsGkAtoms::brFrame == pfd->mFrame->GetType()) {
1331 #ifdef NOISY_CAN_PLACE_FRAME
1332 printf(" ==> BR frame fits\n");
1333 #endif
1334 return true;
1336 #endif
1338 if (aNotSafeToBreak) {
1339 // There are no frames on the line that take up width and the line is
1340 // not impacted by floats, so we must allow the current frame to be
1341 // placed on the line
1342 #ifdef NOISY_CAN_PLACE_FRAME
1343 printf(" ==> not-safe and not-impacted fits: ");
1344 while (nullptr != psd) {
1345 printf("<psd=%p x=%d left=%d> ", psd, psd->mICoord, psd->mIStart);
1346 psd = psd->mParent;
1348 printf("\n");
1349 #endif
1350 return true;
1353 // Special check for span frames
1354 if (pfd->mSpan && pfd->mSpan->mContainsFloat) {
1355 // If the span either directly or indirectly contains a float then
1356 // it fits. Why? It's kind of complicated, but here goes:
1358 // 1. CanPlaceFrame is used for all frame placements on a line,
1359 // and in a span. This includes recursively placement of frames
1360 // inside of spans, and the span itself. Because the logic always
1361 // checks for room before proceeding (the code above here), the
1362 // only things on a line will be those things that "fit".
1364 // 2. Before a float is placed on a line, the line has to be empty
1365 // (otherwise it's a "below current line" float and will be placed
1366 // after the line).
1368 // Therefore, if the span directly or indirectly has a float
1369 // then it means that at the time of the placement of the float
1370 // the line was empty. Because of #1, only the frames that fit can
1371 // be added after that point, therefore we can assume that the
1372 // current span being placed has fit.
1374 // So how do we get here and have a span that should already fit
1375 // and yet doesn't: Simple: span's that have the no-wrap attribute
1376 // set on them and contain a float and are placed where they
1377 // don't naturally fit.
1378 return true;
1381 if (aFrameCanContinueTextRun) {
1382 // Let it fit, but we reserve the right to roll back.
1383 // Note that we usually won't get here because a text frame will break
1384 // itself to avoid exceeding the available width.
1385 // We'll only get here for text frames that couldn't break early enough.
1386 #ifdef NOISY_CAN_PLACE_FRAME
1387 printf(" ==> placing overflowing textrun, requesting backup\n");
1388 #endif
1390 // We will want to try backup.
1391 mNeedBackup = true;
1392 return true;
1395 #ifdef NOISY_CAN_PLACE_FRAME
1396 printf(" ==> didn't fit\n");
1397 #endif
1398 aStatus = NS_INLINE_LINE_BREAK_BEFORE();
1399 return false;
1403 * Place the frame. Update running counters.
1405 void
1406 nsLineLayout::PlaceFrame(PerFrameData* pfd, nsHTMLReflowMetrics& aMetrics)
1408 WritingMode lineWM = mRootSpan->mWritingMode;
1410 // Record ascent and update max-ascent and max-descent values
1411 if (aMetrics.BlockStartAscent() == nsHTMLReflowMetrics::ASK_FOR_BASELINE) {
1412 pfd->mAscent = pfd->mFrame->GetLogicalBaseline(lineWM);
1413 } else {
1414 pfd->mAscent = aMetrics.BlockStartAscent();
1417 // Advance to next inline coordinate
1418 mCurrentSpan->mICoord = pfd->mBounds.IEnd(lineWM) +
1419 pfd->mMargin.IEnd(lineWM);
1421 // Count the number of non-placeholder frames on the line...
1422 if (pfd->mFrame->GetType() == nsGkAtoms::placeholderFrame) {
1423 NS_ASSERTION(pfd->mBounds.ISize(lineWM) == 0 &&
1424 pfd->mBounds.BSize(lineWM) == 0,
1425 "placeholders should have 0 width/height (checking "
1426 "placeholders were never counted by the old code in "
1427 "this function)");
1428 } else {
1429 mTotalPlacedFrames++;
1433 void
1434 nsLineLayout::AddBulletFrame(nsIFrame* aFrame,
1435 const nsHTMLReflowMetrics& aMetrics)
1437 NS_ASSERTION(mCurrentSpan == mRootSpan, "bad linelayout user");
1438 NS_ASSERTION(mGotLineBox, "must have line box");
1440 nsIFrame *blockFrame = mBlockReflowState->frame;
1441 NS_ASSERTION(blockFrame->IsFrameOfType(nsIFrame::eBlockFrame),
1442 "must be for block");
1443 if (!static_cast<nsBlockFrame*>(blockFrame)->BulletIsEmpty()) {
1444 mHasBullet = true;
1445 mLineBox->SetHasBullet();
1448 WritingMode lineWM = mRootSpan->mWritingMode;
1449 PerFrameData* pfd = NewPerFrameData(aFrame);
1450 mRootSpan->AppendFrame(pfd);
1451 pfd->mIsBullet = true;
1452 if (aMetrics.BlockStartAscent() == nsHTMLReflowMetrics::ASK_FOR_BASELINE) {
1453 pfd->mAscent = aFrame->GetLogicalBaseline(lineWM);
1454 } else {
1455 pfd->mAscent = aMetrics.BlockStartAscent();
1458 // Note: block-coord value will be updated during block-direction alignment
1459 pfd->mBounds = LogicalRect(lineWM, aFrame->GetRect(), mContainerWidth);
1460 pfd->mOverflowAreas = aMetrics.mOverflowAreas;
1463 #ifdef DEBUG
1464 void
1465 nsLineLayout::DumpPerSpanData(PerSpanData* psd, int32_t aIndent)
1467 nsFrame::IndentBy(stdout, aIndent);
1468 printf("%p: left=%d x=%d right=%d\n", static_cast<void*>(psd),
1469 psd->mIStart, psd->mICoord, psd->mIEnd);
1470 PerFrameData* pfd = psd->mFirstFrame;
1471 while (nullptr != pfd) {
1472 nsFrame::IndentBy(stdout, aIndent+1);
1473 nsFrame::ListTag(stdout, pfd->mFrame);
1474 nsRect rect = pfd->mBounds.GetPhysicalRect(psd->mWritingMode,
1475 mContainerWidth);
1476 printf(" %d,%d,%d,%d\n", rect.x, rect.y, rect.width, rect.height);
1477 if (pfd->mSpan) {
1478 DumpPerSpanData(pfd->mSpan, aIndent + 1);
1480 pfd = pfd->mNext;
1483 #endif
1485 #define VALIGN_OTHER 0
1486 #define VALIGN_TOP 1
1487 #define VALIGN_BOTTOM 2
1489 void
1490 nsLineLayout::VerticalAlignLine()
1492 // Partially place the children of the block frame. The baseline for
1493 // this operation is set to zero so that the y coordinates for all
1494 // of the placed children will be relative to there.
1495 PerSpanData* psd = mRootSpan;
1496 VerticalAlignFrames(psd);
1498 // *** Note that comments here still use the anachronistic term
1499 // "line-height" when we really mean "size of the line in the block
1500 // direction", "vertical-align" when we really mean "alignment in
1501 // the block direction", and "top" and "bottom" when we really mean
1502 // "block start" and "block end". This is partly for brevity and
1503 // partly to retain the association with the CSS line-height and
1504 // vertical-align properties.
1506 // Compute the line-height. The line-height will be the larger of:
1508 // [1] maxBCoord - minBCoord (the distance between the first child's
1509 // block-start edge and the last child's block-end edge)
1511 // [2] the maximum logical box block size (since not every frame may have
1512 // participated in #1; for example: "top" and "botttom" aligned frames)
1514 // [3] the minimum line height ("line-height" property set on the
1515 // block frame)
1516 nscoord lineBSize = psd->mMaxBCoord - psd->mMinBCoord;
1518 // Now that the line-height is computed, we need to know where the
1519 // baseline is in the line. Position baseline so that mMinBCoord is just
1520 // inside the start of the line box.
1521 nscoord baselineBCoord;
1522 if (psd->mMinBCoord < 0) {
1523 baselineBCoord = mBStartEdge - psd->mMinBCoord;
1525 else {
1526 baselineBCoord = mBStartEdge;
1529 // It's also possible that the line block-size isn't tall enough because
1530 // of "top" and "bottom" aligned elements that were not accounted for in
1531 // min/max BCoord.
1533 // The CSS2 spec doesn't really say what happens when to the
1534 // baseline in this situations. What we do is if the largest start
1535 // aligned box block size is greater than the line block-size then we leave
1536 // the baseline alone. If the largest end aligned box is greater
1537 // than the line block-size then we slide the baseline forward by the extra
1538 // amount.
1540 // Navigator 4 gives precedence to the first top/bottom aligned
1541 // object. We just let block end aligned objects win.
1542 if (lineBSize < mMaxEndBoxBSize) {
1543 // When the line is shorter than the maximum block start aligned box
1544 nscoord extra = mMaxEndBoxBSize - lineBSize;
1545 baselineBCoord += extra;
1546 lineBSize = mMaxEndBoxBSize;
1548 if (lineBSize < mMaxStartBoxBSize) {
1549 lineBSize = mMaxStartBoxBSize;
1551 #ifdef NOISY_BLOCKDIR_ALIGN
1552 printf(" [line]==> lineBSize=%d baselineBCoord=%d\n", lineBSize, baselineBCoord);
1553 #endif
1555 // Now position all of the frames in the root span. We will also
1556 // recurse over the child spans and place any frames we find with
1557 // vertical-align: top or bottom.
1558 // XXX PERFORMANCE: set a bit per-span to avoid the extra work
1559 // (propagate it upward too)
1560 WritingMode lineWM = psd->mWritingMode;
1561 for (PerFrameData* pfd = psd->mFirstFrame; pfd; pfd = pfd->mNext) {
1562 if (pfd->mBlockDirAlign == VALIGN_OTHER) {
1563 pfd->mBounds.BStart(lineWM) += baselineBCoord;
1564 pfd->mFrame->SetRect(lineWM, pfd->mBounds, mContainerWidth);
1567 PlaceTopBottomFrames(psd, -mBStartEdge, lineBSize);
1569 mFinalLineBSize = lineBSize;
1570 if (mGotLineBox) {
1571 // Fill in returned line-box and max-element-width data
1572 mLineBox->SetBounds(lineWM,
1573 psd->mIStart, mBStartEdge,
1574 psd->mICoord - psd->mIStart, lineBSize,
1575 mContainerWidth);
1577 mLineBox->SetLogicalAscent(baselineBCoord - mBStartEdge);
1578 #ifdef NOISY_BLOCKDIR_ALIGN
1579 printf(
1580 " [line]==> bounds{x,y,w,h}={%d,%d,%d,%d} lh=%d a=%d\n",
1581 mLineBox->GetBounds().IStart(lineWM), mLineBox->GetBounds().BStart(lineWM),
1582 mLineBox->GetBounds().ISize(lineWM), mLineBox->GetBounds().BSize(lineWM),
1583 mFinalLineBSize, mLineBox->GetLogicalAscent());
1584 #endif
1588 // Place frames with CSS property vertical-align: top or bottom.
1589 void
1590 nsLineLayout::PlaceTopBottomFrames(PerSpanData* psd,
1591 nscoord aDistanceFromStart,
1592 nscoord aLineBSize)
1594 for (PerFrameData* pfd = psd->mFirstFrame; pfd; pfd = pfd->mNext) {
1595 PerSpanData* span = pfd->mSpan;
1596 #ifdef DEBUG
1597 NS_ASSERTION(0xFF != pfd->mBlockDirAlign, "umr");
1598 #endif
1599 WritingMode lineWM = mRootSpan->mWritingMode;
1600 nscoord containerWidth = ContainerWidthForSpan(psd);
1601 switch (pfd->mBlockDirAlign) {
1602 case VALIGN_TOP:
1603 if (span) {
1604 pfd->mBounds.BStart(lineWM) = -aDistanceFromStart - span->mMinBCoord;
1606 else {
1607 pfd->mBounds.BStart(lineWM) =
1608 -aDistanceFromStart + pfd->mMargin.BStart(lineWM);
1610 pfd->mFrame->SetRect(lineWM, pfd->mBounds, containerWidth);
1611 #ifdef NOISY_BLOCKDIR_ALIGN
1612 printf(" ");
1613 nsFrame::ListTag(stdout, pfd->mFrame);
1614 printf(": y=%d dTop=%d [bp.top=%d topLeading=%d]\n",
1615 pfd->mBounds.BStart(lineWM), aDistanceFromStart,
1616 span ? pfd->mBorderPadding.BStart(lineWM) : 0,
1617 span ? span->mBStartLeading : 0);
1618 #endif
1619 break;
1620 case VALIGN_BOTTOM:
1621 if (span) {
1622 // Compute bottom leading
1623 pfd->mBounds.BStart(lineWM) =
1624 -aDistanceFromStart + aLineBSize - span->mMaxBCoord;
1626 else {
1627 pfd->mBounds.BStart(lineWM) = -aDistanceFromStart + aLineBSize -
1628 pfd->mMargin.BEnd(lineWM) - pfd->mBounds.BSize(lineWM);
1630 pfd->mFrame->SetRect(lineWM, pfd->mBounds, containerWidth);
1631 #ifdef NOISY_BLOCKDIR_ALIGN
1632 printf(" ");
1633 nsFrame::ListTag(stdout, pfd->mFrame);
1634 printf(": y=%d\n", pfd->mBounds.BStart(lineWM));
1635 #endif
1636 break;
1638 if (span) {
1639 nscoord fromStart = aDistanceFromStart + pfd->mBounds.BStart(lineWM);
1640 PlaceTopBottomFrames(span, fromStart, aLineBSize);
1645 static float
1646 GetInflationForBlockDirAlignment(nsIFrame* aFrame,
1647 nscoord aInflationMinFontSize)
1649 if (aFrame->IsSVGText()) {
1650 const nsIFrame* container =
1651 nsLayoutUtils::GetClosestFrameOfType(aFrame, nsGkAtoms::svgTextFrame);
1652 NS_ASSERTION(container, "expected to find an ancestor SVGTextFrame");
1653 return
1654 static_cast<const SVGTextFrame*>(container)->GetFontSizeScaleFactor();
1656 return nsLayoutUtils::FontSizeInflationInner(aFrame, aInflationMinFontSize);
1659 #define BLOCKDIR_ALIGN_FRAMES_NO_MINIMUM nscoord_MAX
1660 #define BLOCKDIR_ALIGN_FRAMES_NO_MAXIMUM nscoord_MIN
1662 // Place frames in the block direction within a given span (CSS property
1663 // vertical-align) Note: this doesn't place frames with vertical-align:
1664 // top or bottom as those have to wait until the entire line box block
1665 // size is known. This is called after the span frame has finished being
1666 // reflowed so that we know its block size.
1667 void
1668 nsLineLayout::VerticalAlignFrames(PerSpanData* psd)
1670 // Get parent frame info
1671 PerFrameData* spanFramePFD = psd->mFrame;
1672 nsIFrame* spanFrame = spanFramePFD->mFrame;
1674 // Get the parent frame's font for all of the frames in this span
1675 nsRefPtr<nsFontMetrics> fm;
1676 float inflation =
1677 GetInflationForBlockDirAlignment(spanFrame, mInflationMinFontSize);
1678 nsLayoutUtils::GetFontMetricsForFrame(spanFrame, getter_AddRefs(fm),
1679 inflation);
1681 bool preMode = mStyleText->WhiteSpaceIsSignificant();
1683 // See if the span is an empty continuation. It's an empty continuation iff:
1684 // - it has a prev-in-flow
1685 // - it has no next in flow
1686 // - it's zero sized
1687 WritingMode lineWM = mRootSpan->mWritingMode;
1688 bool emptyContinuation = psd != mRootSpan &&
1689 spanFrame->GetPrevInFlow() && !spanFrame->GetNextInFlow() &&
1690 spanFramePFD->mBounds.IsZeroSize();
1692 #ifdef NOISY_BLOCKDIR_ALIGN
1693 printf("[%sSpan]", (psd == mRootSpan)?"Root":"");
1694 nsFrame::ListTag(stdout, spanFrame);
1695 printf(": preMode=%s strictMode=%s w/h=%d,%d emptyContinuation=%s",
1696 preMode ? "yes" : "no",
1697 mPresContext->CompatibilityMode() != eCompatibility_NavQuirks ? "yes" : "no",
1698 spanFramePFD->mBounds.ISize(lineWM),
1699 spanFramePFD->mBounds.BSize(lineWM),
1700 emptyContinuation ? "yes" : "no");
1701 if (psd != mRootSpan) {
1702 WritingMode frameWM = spanFramePFD->mFrame->GetWritingMode();
1703 printf(" bp=%d,%d,%d,%d margin=%d,%d,%d,%d",
1704 spanFramePFD->mBorderPadding.Top(lineWM),
1705 spanFramePFD->mBorderPadding.Right(lineWM),
1706 spanFramePFD->mBorderPadding.Bottom(lineWM),
1707 spanFramePFD->mBorderPadding.Left(lineWM),
1708 spanFramePFD->mMargin.Top(lineWM),
1709 spanFramePFD->mMargin.Right(lineWM),
1710 spanFramePFD->mMargin.Bottom(lineWM),
1711 spanFramePFD->mMargin.Left(lineWM));
1713 printf("\n");
1714 #endif
1716 // Compute the span's mZeroEffectiveSpanBox flag. What we are trying
1717 // to determine is how we should treat the span: should it act
1718 // "normally" according to css2 or should it effectively
1719 // "disappear".
1721 // In general, if the document being processed is in full standards
1722 // mode then it should act normally (with one exception). The
1723 // exception case is when a span is continued and yet the span is
1724 // empty (e.g. compressed whitespace). For this kind of span we treat
1725 // it as if it were not there so that it doesn't impact the
1726 // line block-size.
1728 // In almost standards mode or quirks mode, we should sometimes make
1729 // it disappear. The cases that matter are those where the span
1730 // contains no real text elements that would provide an ascent and
1731 // descent and height. However, if css style elements have been
1732 // applied to the span (border/padding/margin) so that it's clear the
1733 // document author is intending css2 behavior then we act as if strict
1734 // mode is set.
1736 // This code works correctly for preMode, because a blank line
1737 // in PRE mode is encoded as a text node with a LF in it, since
1738 // text nodes with only whitespace are considered in preMode.
1740 // Much of this logic is shared with the various implementations of
1741 // nsIFrame::IsEmpty since they need to duplicate the way it makes
1742 // some lines empty. However, nsIFrame::IsEmpty can't be reused here
1743 // since this code sets zeroEffectiveSpanBox even when there are
1744 // non-empty children.
1745 bool zeroEffectiveSpanBox = false;
1746 // XXXldb If we really have empty continuations, then all these other
1747 // checks don't make sense for them.
1748 // XXXldb This should probably just use nsIFrame::IsSelfEmpty, assuming that
1749 // it agrees with this code. (If it doesn't agree, it probably should.)
1750 if ((emptyContinuation ||
1751 mPresContext->CompatibilityMode() != eCompatibility_FullStandards) &&
1752 ((psd == mRootSpan) ||
1753 (spanFramePFD->mBorderPadding.IsAllZero() &&
1754 spanFramePFD->mMargin.IsAllZero()))) {
1755 // This code handles an issue with compatibility with non-css
1756 // conformant browsers. In particular, there are some cases
1757 // where the font-size and line-height for a span must be
1758 // ignored and instead the span must *act* as if it were zero
1759 // sized. In general, if the span contains any non-compressed
1760 // text then we don't use this logic.
1761 // However, this is not propagated outwards, since (in compatibility
1762 // mode) we don't want big line heights for things like
1763 // <p><font size="-1">Text</font></p>
1765 // We shouldn't include any whitespace that collapses, unless we're
1766 // preformatted (in which case it shouldn't, but the width=0 test is
1767 // perhaps incorrect). This includes whitespace at the beginning of
1768 // a line and whitespace preceded (?) by other whitespace.
1769 // See bug 134580 and bug 155333.
1770 zeroEffectiveSpanBox = true;
1771 for (PerFrameData* pfd = psd->mFirstFrame; pfd; pfd = pfd->mNext) {
1772 if (pfd->mIsTextFrame &&
1773 (pfd->mIsNonWhitespaceTextFrame || preMode ||
1774 pfd->mBounds.ISize(mRootSpan->mWritingMode) != 0)) {
1775 zeroEffectiveSpanBox = false;
1776 break;
1780 psd->mZeroEffectiveSpanBox = zeroEffectiveSpanBox;
1782 // Setup baselineBCoord, minBCoord, and maxBCoord
1783 nscoord baselineBCoord, minBCoord, maxBCoord;
1784 if (psd == mRootSpan) {
1785 // Use a zero baselineBCoord since we don't yet know where the baseline
1786 // will be (until we know how tall the line is; then we will
1787 // know). In addition, use extreme values for the minBCoord and maxBCoord
1788 // values so that only the child frames will impact their values
1789 // (since these are children of the block, there is no span box to
1790 // provide initial values).
1791 baselineBCoord = 0;
1792 minBCoord = BLOCKDIR_ALIGN_FRAMES_NO_MINIMUM;
1793 maxBCoord = BLOCKDIR_ALIGN_FRAMES_NO_MAXIMUM;
1794 #ifdef NOISY_BLOCKDIR_ALIGN
1795 printf("[RootSpan]");
1796 nsFrame::ListTag(stdout, spanFrame);
1797 printf(": pass1 valign frames: topEdge=%d minLineBSize=%d zeroEffectiveSpanBox=%s\n",
1798 mBStartEdge, mMinLineBSize,
1799 zeroEffectiveSpanBox ? "yes" : "no");
1800 #endif
1802 else {
1803 // Compute the logical block size for this span. The logical block size
1804 // is based on the "line-height" value, not the font-size. Also
1805 // compute the top leading.
1806 float inflation =
1807 GetInflationForBlockDirAlignment(spanFrame, mInflationMinFontSize);
1808 nscoord logicalBSize = nsHTMLReflowState::
1809 CalcLineHeight(spanFrame->GetContent(), spanFrame->StyleContext(),
1810 mBlockReflowState->ComputedHeight(),
1811 inflation);
1812 nscoord contentBSize = spanFramePFD->mBounds.BSize(lineWM) -
1813 spanFramePFD->mBorderPadding.BStartEnd(lineWM);
1815 // Special-case for a ::first-letter frame, set the line height to
1816 // the frame block size if the user has left line-height == normal
1817 if (spanFramePFD->mIsLetterFrame &&
1818 !spanFrame->GetPrevInFlow() &&
1819 spanFrame->StyleText()->mLineHeight.GetUnit() == eStyleUnit_Normal) {
1820 logicalBSize = spanFramePFD->mBounds.BSize(lineWM);
1823 nscoord leading = logicalBSize - contentBSize;
1824 psd->mBStartLeading = leading / 2;
1825 psd->mBEndLeading = leading - psd->mBStartLeading;
1826 psd->mLogicalBSize = logicalBSize;
1827 if (spanFrame->GetType() == nsGkAtoms::rubyFrame) {
1828 // We may need to extend leadings here for ruby annotations as
1829 // required by section Line Spacing in the CSS Ruby spec.
1830 // See http://dev.w3.org/csswg/css-ruby/#line-height
1831 auto rubyFrame = static_cast<nsRubyFrame*>(spanFrame);
1832 nscoord startLeading, endLeading;
1833 rubyFrame->GetBlockLeadings(startLeading, endLeading);
1834 nscoord deltaLeading = startLeading + endLeading - leading;
1835 if (deltaLeading > 0) {
1836 // If the total leading is not wide enough for ruby annotations,
1837 // extend the side which is not enough. If both sides are not
1838 // wide enough, replace the leadings with the requested values.
1839 if (startLeading < psd->mBStartLeading) {
1840 psd->mBEndLeading += deltaLeading;
1841 } else if (endLeading < psd->mBEndLeading) {
1842 psd->mBStartLeading += deltaLeading;
1843 } else {
1844 psd->mBStartLeading = startLeading;
1845 psd->mBEndLeading = endLeading;
1847 psd->mLogicalBSize += deltaLeading;
1851 if (zeroEffectiveSpanBox) {
1852 // When the span-box is to be ignored, zero out the initial
1853 // values so that the span doesn't impact the final line
1854 // height. The contents of the span can impact the final line
1855 // height.
1857 // Note that things are readjusted for this span after its children
1858 // are reflowed
1859 minBCoord = BLOCKDIR_ALIGN_FRAMES_NO_MINIMUM;
1860 maxBCoord = BLOCKDIR_ALIGN_FRAMES_NO_MAXIMUM;
1862 else {
1864 // The initial values for the min and max block coord values are in the
1865 // span's coordinate space, and cover the logical block size of the span.
1866 // If there are child frames in this span that stick out of this area
1867 // then the minBCoord and maxBCoord are updated by the amount of logical
1868 // blockSize that is outside this range.
1869 minBCoord = spanFramePFD->mBorderPadding.BStart(lineWM) -
1870 psd->mBStartLeading;
1871 maxBCoord = minBCoord + psd->mLogicalBSize;
1874 // This is the distance from the top edge of the parents visual
1875 // box to the baseline. The span already computed this for us,
1876 // so just use it.
1877 *psd->mBaseline = baselineBCoord = spanFramePFD->mAscent;
1880 #ifdef NOISY_BLOCKDIR_ALIGN
1881 printf("[%sSpan]", (psd == mRootSpan)?"Root":"");
1882 nsFrame::ListTag(stdout, spanFrame);
1883 printf(": baseLine=%d logicalBSize=%d topLeading=%d h=%d bp=%d,%d zeroEffectiveSpanBox=%s\n",
1884 baselineBCoord, psd->mLogicalBSize, psd->mBStartLeading,
1885 spanFramePFD->mBounds.BSize(lineWM),
1886 spanFramePFD->mBorderPadding.Top(lineWM),
1887 spanFramePFD->mBorderPadding.Bottom(lineWM),
1888 zeroEffectiveSpanBox ? "yes" : "no");
1889 #endif
1892 nscoord maxStartBoxBSize = 0;
1893 nscoord maxEndBoxBSize = 0;
1894 PerFrameData* pfd = psd->mFirstFrame;
1895 while (nullptr != pfd) {
1896 nsIFrame* frame = pfd->mFrame;
1898 // sanity check (see bug 105168, non-reproducible crashes from null frame)
1899 NS_ASSERTION(frame, "null frame in PerFrameData - something is very very bad");
1900 if (!frame) {
1901 return;
1904 // Compute the logical block size of the frame
1905 nscoord logicalBSize;
1906 PerSpanData* frameSpan = pfd->mSpan;
1907 if (frameSpan) {
1908 // For span frames the logical-block-size and start-leading were
1909 // pre-computed when the span was reflowed.
1910 logicalBSize = frameSpan->mLogicalBSize;
1912 else {
1913 // For other elements the logical block size is the same as the
1914 // frame's block size plus its margins.
1915 logicalBSize = pfd->mBounds.BSize(lineWM) +
1916 pfd->mMargin.BStartEnd(lineWM);
1917 if (logicalBSize < 0 &&
1918 mPresContext->CompatibilityMode() == eCompatibility_NavQuirks) {
1919 pfd->mAscent -= logicalBSize;
1920 logicalBSize = 0;
1924 // Get vertical-align property ("vertical-align" is the CSS name for
1925 // block-direction align)
1926 const nsStyleCoord& verticalAlign =
1927 frame->StyleTextReset()->mVerticalAlign;
1928 uint8_t verticalAlignEnum = frame->VerticalAlignEnum();
1929 #ifdef NOISY_BLOCKDIR_ALIGN
1930 printf(" [frame]");
1931 nsFrame::ListTag(stdout, frame);
1932 printf(": verticalAlignUnit=%d (enum == %d",
1933 verticalAlign.GetUnit(),
1934 ((eStyleUnit_Enumerated == verticalAlign.GetUnit())
1935 ? verticalAlign.GetIntValue()
1936 : -1));
1937 if (verticalAlignEnum != nsIFrame::eInvalidVerticalAlign) {
1938 printf(", after SVG dominant-baseline conversion == %d",
1939 verticalAlignEnum);
1941 printf(")\n");
1942 #endif
1944 if (verticalAlignEnum != nsIFrame::eInvalidVerticalAlign) {
1945 if (lineWM.IsVertical()) {
1946 if (verticalAlignEnum == NS_STYLE_VERTICAL_ALIGN_MIDDLE) {
1947 // For vertical writing mode where the dominant baseline is centered
1948 // (i.e. text-orientation is not sideways-*), we remap 'middle' to
1949 // 'middle-with-baseline' so that images align sensibly with the
1950 // center-baseline-aligned text.
1951 if (!lineWM.IsSideways()) {
1952 verticalAlignEnum = NS_STYLE_VERTICAL_ALIGN_MIDDLE_WITH_BASELINE;
1954 } else if (lineWM.IsLineInverted()) {
1955 // Swap the meanings of top and bottom when line is inverted
1956 // relative to block direction.
1957 switch (verticalAlignEnum) {
1958 case NS_STYLE_VERTICAL_ALIGN_TOP:
1959 verticalAlignEnum = NS_STYLE_VERTICAL_ALIGN_BOTTOM;
1960 break;
1961 case NS_STYLE_VERTICAL_ALIGN_BOTTOM:
1962 verticalAlignEnum = NS_STYLE_VERTICAL_ALIGN_TOP;
1963 break;
1964 case NS_STYLE_VERTICAL_ALIGN_TEXT_TOP:
1965 verticalAlignEnum = NS_STYLE_VERTICAL_ALIGN_TEXT_BOTTOM;
1966 break;
1967 case NS_STYLE_VERTICAL_ALIGN_TEXT_BOTTOM:
1968 verticalAlignEnum = NS_STYLE_VERTICAL_ALIGN_TEXT_TOP;
1969 break;
1974 // baseline coord that may be adjusted for script offset
1975 nscoord revisedBaselineBCoord = baselineBCoord;
1977 // For superscript and subscript, raise or lower the baseline of the box
1978 // to the proper offset of the parent's box, then proceed as for BASELINE
1979 if (verticalAlignEnum == NS_STYLE_VERTICAL_ALIGN_SUB ||
1980 verticalAlignEnum == NS_STYLE_VERTICAL_ALIGN_SUPER) {
1981 revisedBaselineBCoord += lineWM.FlowRelativeToLineRelativeFactor() *
1982 (verticalAlignEnum == NS_STYLE_VERTICAL_ALIGN_SUB
1983 ? fm->SubscriptOffset() : -fm->SuperscriptOffset());
1984 verticalAlignEnum = NS_STYLE_VERTICAL_ALIGN_BASELINE;
1987 switch (verticalAlignEnum) {
1988 default:
1989 case NS_STYLE_VERTICAL_ALIGN_BASELINE:
1990 if (lineWM.IsVertical() && !lineWM.IsSideways()) {
1991 if (frameSpan) {
1992 pfd->mBounds.BStart(lineWM) = revisedBaselineBCoord -
1993 pfd->mBounds.BSize(lineWM)/2;
1994 } else {
1995 pfd->mBounds.BStart(lineWM) = revisedBaselineBCoord -
1996 logicalBSize/2 +
1997 pfd->mMargin.BStart(lineWM);
1999 } else {
2000 pfd->mBounds.BStart(lineWM) = revisedBaselineBCoord - pfd->mAscent;
2002 pfd->mBlockDirAlign = VALIGN_OTHER;
2003 break;
2005 case NS_STYLE_VERTICAL_ALIGN_TOP:
2007 pfd->mBlockDirAlign = VALIGN_TOP;
2008 nscoord subtreeBSize = logicalBSize;
2009 if (frameSpan) {
2010 subtreeBSize = frameSpan->mMaxBCoord - frameSpan->mMinBCoord;
2011 NS_ASSERTION(subtreeBSize >= logicalBSize,
2012 "unexpected subtree block size");
2014 if (subtreeBSize > maxStartBoxBSize) {
2015 maxStartBoxBSize = subtreeBSize;
2017 break;
2020 case NS_STYLE_VERTICAL_ALIGN_BOTTOM:
2022 pfd->mBlockDirAlign = VALIGN_BOTTOM;
2023 nscoord subtreeBSize = logicalBSize;
2024 if (frameSpan) {
2025 subtreeBSize = frameSpan->mMaxBCoord - frameSpan->mMinBCoord;
2026 NS_ASSERTION(subtreeBSize >= logicalBSize,
2027 "unexpected subtree block size");
2029 if (subtreeBSize > maxEndBoxBSize) {
2030 maxEndBoxBSize = subtreeBSize;
2032 break;
2035 case NS_STYLE_VERTICAL_ALIGN_MIDDLE:
2037 // Align the midpoint of the frame with 1/2 the parents
2038 // x-height above the baseline.
2039 nscoord parentXHeight =
2040 lineWM.FlowRelativeToLineRelativeFactor() * fm->XHeight();
2041 if (frameSpan) {
2042 pfd->mBounds.BStart(lineWM) = baselineBCoord -
2043 (parentXHeight + pfd->mBounds.BSize(lineWM))/2;
2045 else {
2046 pfd->mBounds.BStart(lineWM) = baselineBCoord -
2047 (parentXHeight + logicalBSize)/2 +
2048 pfd->mMargin.BStart(lineWM);
2050 pfd->mBlockDirAlign = VALIGN_OTHER;
2051 break;
2054 case NS_STYLE_VERTICAL_ALIGN_TEXT_TOP:
2056 // The top of the logical box is aligned with the top of
2057 // the parent element's text.
2058 // XXX For vertical text we will need a new API to get the logical
2059 // max-ascent here
2060 nscoord parentAscent =
2061 lineWM.IsLineInverted() ? fm->MaxDescent() : fm->MaxAscent();
2062 if (frameSpan) {
2063 pfd->mBounds.BStart(lineWM) = baselineBCoord - parentAscent -
2064 pfd->mBorderPadding.BStart(lineWM) + frameSpan->mBStartLeading;
2066 else {
2067 pfd->mBounds.BStart(lineWM) = baselineBCoord - parentAscent +
2068 pfd->mMargin.BStart(lineWM);
2070 pfd->mBlockDirAlign = VALIGN_OTHER;
2071 break;
2074 case NS_STYLE_VERTICAL_ALIGN_TEXT_BOTTOM:
2076 // The bottom of the logical box is aligned with the
2077 // bottom of the parent elements text.
2078 nscoord parentDescent =
2079 lineWM.IsLineInverted() ? fm->MaxAscent() : fm->MaxDescent();
2080 if (frameSpan) {
2081 pfd->mBounds.BStart(lineWM) = baselineBCoord + parentDescent -
2082 pfd->mBounds.BSize(lineWM) +
2083 pfd->mBorderPadding.BEnd(lineWM) -
2084 frameSpan->mBEndLeading;
2086 else {
2087 pfd->mBounds.BStart(lineWM) = baselineBCoord + parentDescent -
2088 pfd->mBounds.BSize(lineWM) -
2089 pfd->mMargin.BEnd(lineWM);
2091 pfd->mBlockDirAlign = VALIGN_OTHER;
2092 break;
2095 case NS_STYLE_VERTICAL_ALIGN_MIDDLE_WITH_BASELINE:
2097 // Align the midpoint of the frame with the baseline of the parent.
2098 if (frameSpan) {
2099 pfd->mBounds.BStart(lineWM) = baselineBCoord -
2100 pfd->mBounds.BSize(lineWM)/2;
2102 else {
2103 pfd->mBounds.BStart(lineWM) = baselineBCoord - logicalBSize/2 +
2104 pfd->mMargin.BStart(lineWM);
2106 pfd->mBlockDirAlign = VALIGN_OTHER;
2107 break;
2110 } else {
2111 // We have either a coord, a percent, or a calc().
2112 nscoord pctBasis = 0;
2113 if (verticalAlign.HasPercent()) {
2114 // Percentages are like lengths, except treated as a percentage
2115 // of the elements line block size value.
2116 float inflation =
2117 GetInflationForBlockDirAlignment(frame, mInflationMinFontSize);
2118 pctBasis = nsHTMLReflowState::CalcLineHeight(frame->GetContent(),
2119 frame->StyleContext(), mBlockReflowState->ComputedBSize(),
2120 inflation);
2122 nscoord offset =
2123 nsRuleNode::ComputeCoordPercentCalc(verticalAlign, pctBasis);
2124 // According to the CSS2 spec (10.8.1), a positive value
2125 // "raises" the box by the given distance while a negative value
2126 // "lowers" the box by the given distance (with zero being the
2127 // baseline). Since Y coordinates increase towards the bottom of
2128 // the screen we reverse the sign.
2129 nscoord revisedBaselineBCoord = baselineBCoord - offset;
2130 pfd->mBounds.BStart(lineWM) = revisedBaselineBCoord - pfd->mAscent;
2131 pfd->mBlockDirAlign = VALIGN_OTHER;
2134 // Update minBCoord/maxBCoord for frames that we just placed. Do not factor
2135 // text into the equation.
2136 if (pfd->mBlockDirAlign == VALIGN_OTHER) {
2137 // Text frames do not contribute to the min/max Y values for the
2138 // line (instead their parent frame's font-size contributes).
2139 // XXXrbs -- relax this restriction because it causes text frames
2140 // to jam together when 'font-size-adjust' is enabled
2141 // and layout is using dynamic font heights (bug 20394)
2142 // -- Note #1: With this code enabled and with the fact that we are not
2143 // using Em[Ascent|Descent] as nsDimensions for text metrics in
2144 // GFX mean that the discussion in bug 13072 cannot hold.
2145 // -- Note #2: We still don't want empty-text frames to interfere.
2146 // For example in quirks mode, avoiding empty text frames prevents
2147 // "tall" lines around elements like <hr> since the rules of <hr>
2148 // in quirks.css have pseudo text contents with LF in them.
2149 #if 0
2150 if (!pfd->mIsTextFrame) {
2151 #else
2152 // Only consider non empty text frames when line-height=normal
2153 bool canUpdate = !pfd->mIsTextFrame;
2154 if (!canUpdate && pfd->mIsNonWhitespaceTextFrame) {
2155 canUpdate =
2156 frame->StyleText()->mLineHeight.GetUnit() == eStyleUnit_Normal;
2158 if (canUpdate) {
2159 #endif
2160 nscoord blockStart, blockEnd;
2161 if (frameSpan) {
2162 // For spans that were are now placing, use their position
2163 // plus their already computed min-Y and max-Y values for
2164 // computing blockStart and blockEnd.
2165 blockStart = pfd->mBounds.BStart(lineWM) + frameSpan->mMinBCoord;
2166 blockEnd = pfd->mBounds.BStart(lineWM) + frameSpan->mMaxBCoord;
2168 else {
2169 blockStart = pfd->mBounds.BStart(lineWM) -
2170 pfd->mMargin.BStart(lineWM);
2171 blockEnd = blockStart + logicalBSize;
2173 if (!preMode &&
2174 mPresContext->CompatibilityMode() != eCompatibility_FullStandards &&
2175 !logicalBSize) {
2176 // Check if it's a BR frame that is not alone on its line (it
2177 // is given a block size of zero to indicate this), and if so reset
2178 // blockStart and blockEnd so that BR frames don't influence the line.
2179 if (nsGkAtoms::brFrame == frame->GetType()) {
2180 blockStart = BLOCKDIR_ALIGN_FRAMES_NO_MINIMUM;
2181 blockEnd = BLOCKDIR_ALIGN_FRAMES_NO_MAXIMUM;
2184 if (blockStart < minBCoord) minBCoord = blockStart;
2185 if (blockEnd > maxBCoord) maxBCoord = blockEnd;
2186 #ifdef NOISY_BLOCKDIR_ALIGN
2187 printf(" [frame]raw: a=%d h=%d bp=%d,%d logical: h=%d leading=%d y=%d minBCoord=%d maxBCoord=%d\n",
2188 pfd->mAscent, pfd->mBounds.BSize(lineWM),
2189 pfd->mBorderPadding.Top(lineWM),
2190 pfd->mBorderPadding.Bottom(lineWM),
2191 logicalBSize,
2192 frameSpan ? frameSpan->mBStartLeading : 0,
2193 pfd->mBounds.BStart(lineWM), minBCoord, maxBCoord);
2194 #endif
2196 if (psd != mRootSpan) {
2197 frame->SetRect(lineWM, pfd->mBounds, ContainerWidthForSpan(psd));
2200 pfd = pfd->mNext;
2203 // Factor in the minimum line block-size when handling the root-span for
2204 // the block.
2205 if (psd == mRootSpan) {
2206 // We should factor in the block element's minimum line-height (as
2207 // defined in section 10.8.1 of the css2 spec) assuming that
2208 // mZeroEffectiveSpanBox is not set on the root span. This only happens
2209 // in some cases in quirks mode:
2210 // (1) if the root span contains non-whitespace text directly (this
2211 // is handled by mZeroEffectiveSpanBox
2212 // (2) if this line has a bullet
2213 // (3) if this is the last line of an LI, DT, or DD element
2214 // (The last line before a block also counts, but not before a
2215 // BR) (NN4/IE5 quirk)
2217 // (1) and (2) above
2218 bool applyMinLH = !psd->mZeroEffectiveSpanBox || mHasBullet;
2219 bool isLastLine = !mGotLineBox ||
2220 (!mLineBox->IsLineWrapped() && !mLineEndsInBR);
2221 if (!applyMinLH && isLastLine) {
2222 nsIContent* blockContent = mRootSpan->mFrame->mFrame->GetContent();
2223 if (blockContent) {
2224 nsIAtom *blockTagAtom = blockContent->Tag();
2225 // (3) above, if the last line of LI, DT, or DD
2226 if (blockTagAtom == nsGkAtoms::li ||
2227 blockTagAtom == nsGkAtoms::dt ||
2228 blockTagAtom == nsGkAtoms::dd) {
2229 applyMinLH = true;
2233 if (applyMinLH) {
2234 if (psd->mHasNonemptyContent || preMode || mHasBullet) {
2235 #ifdef NOISY_BLOCKDIR_ALIGN
2236 printf(" [span]==> adjusting min/maxBCoord: currentValues: %d,%d", minBCoord, maxBCoord);
2237 #endif
2238 nscoord minimumLineBSize = mMinLineBSize;
2239 nscoord blockStart =
2240 -nsLayoutUtils::GetCenteredFontBaseline(fm, minimumLineBSize,
2241 lineWM.IsLineInverted());
2242 nscoord blockEnd = blockStart + minimumLineBSize;
2244 if (blockStart < minBCoord) minBCoord = blockStart;
2245 if (blockEnd > maxBCoord) maxBCoord = blockEnd;
2247 #ifdef NOISY_BLOCKDIR_ALIGN
2248 printf(" new values: %d,%d\n", minBCoord, maxBCoord);
2249 #endif
2250 #ifdef NOISY_BLOCKDIR_ALIGN
2251 printf(" Used mMinLineBSize: %d, blockStart: %d, blockEnd: %d\n", mMinLineBSize, blockStart, blockEnd);
2252 #endif
2254 else {
2255 // XXX issues:
2256 // [1] BR's on empty lines stop working
2257 // [2] May not honor css2's notion of handling empty elements
2258 // [3] blank lines in a pre-section ("\n") (handled with preMode)
2260 // XXX Are there other problems with this?
2261 #ifdef NOISY_BLOCKDIR_ALIGN
2262 printf(" [span]==> zapping min/maxBCoord: currentValues: %d,%d newValues: 0,0\n",
2263 minBCoord, maxBCoord);
2264 #endif
2265 minBCoord = maxBCoord = 0;
2270 if ((minBCoord == BLOCKDIR_ALIGN_FRAMES_NO_MINIMUM) ||
2271 (maxBCoord == BLOCKDIR_ALIGN_FRAMES_NO_MAXIMUM)) {
2272 minBCoord = maxBCoord = baselineBCoord;
2275 if ((psd != mRootSpan) && (psd->mZeroEffectiveSpanBox)) {
2276 #ifdef NOISY_BLOCKDIR_ALIGN
2277 printf(" [span]adjusting for zeroEffectiveSpanBox\n");
2278 printf(" Original: minBCoord=%d, maxBCoord=%d, bSize=%d, ascent=%d, logicalBSize=%d, topLeading=%d, bottomLeading=%d\n",
2279 minBCoord, maxBCoord, spanFramePFD->mBounds.BSize(frameWM),
2280 spanFramePFD->mAscent,
2281 psd->mLogicalBSize, psd->mBStartLeading, psd->mBEndLeading);
2282 #endif
2283 nscoord goodMinBCoord =
2284 spanFramePFD->mBorderPadding.BStart(lineWM) - psd->mBStartLeading;
2285 nscoord goodMaxBCoord = goodMinBCoord + psd->mLogicalBSize;
2287 // For cases like the one in bug 714519 (text-decoration placement
2288 // or making nsLineLayout::IsZeroBSize() handle
2289 // vertical-align:top/bottom on a descendant of the line that's not
2290 // a child of it), we want to treat elements that are
2291 // vertical-align: top or bottom somewhat like children for the
2292 // purposes of this quirk. To some extent, this is guessing, since
2293 // they might end up being aligned anywhere. However, we'll guess
2294 // that they'll be placed aligned with the top or bottom of this
2295 // frame (as though this frame is the only thing in the line).
2296 // (Guessing isn't crazy, since all we're doing is reducing the
2297 // scope of a quirk and making the behavior more standards-like.)
2298 if (maxStartBoxBSize > maxBCoord - minBCoord) {
2299 // Distribute maxStartBoxBSize to ascent (baselineBCoord - minBCoord), and
2300 // then to descent (maxBCoord - baselineBCoord) by adjusting minBCoord or
2301 // maxBCoord, but not to exceed goodMinBCoord and goodMaxBCoord.
2302 nscoord distribute = maxStartBoxBSize - (maxBCoord - minBCoord);
2303 nscoord ascentSpace = std::max(minBCoord - goodMinBCoord, 0);
2304 if (distribute > ascentSpace) {
2305 distribute -= ascentSpace;
2306 minBCoord -= ascentSpace;
2307 nscoord descentSpace = std::max(goodMaxBCoord - maxBCoord, 0);
2308 if (distribute > descentSpace) {
2309 maxBCoord += descentSpace;
2310 } else {
2311 maxBCoord += distribute;
2313 } else {
2314 minBCoord -= distribute;
2317 if (maxEndBoxBSize > maxBCoord - minBCoord) {
2318 // Likewise, but preferring descent to ascent.
2319 nscoord distribute = maxEndBoxBSize - (maxBCoord - minBCoord);
2320 nscoord descentSpace = std::max(goodMaxBCoord - maxBCoord, 0);
2321 if (distribute > descentSpace) {
2322 distribute -= descentSpace;
2323 maxBCoord += descentSpace;
2324 nscoord ascentSpace = std::max(minBCoord - goodMinBCoord, 0);
2325 if (distribute > ascentSpace) {
2326 minBCoord -= ascentSpace;
2327 } else {
2328 minBCoord -= distribute;
2330 } else {
2331 maxBCoord += distribute;
2335 if (minBCoord > goodMinBCoord) {
2336 nscoord adjust = minBCoord - goodMinBCoord; // positive
2338 // shrink the logical extents
2339 psd->mLogicalBSize -= adjust;
2340 psd->mBStartLeading -= adjust;
2342 if (maxBCoord < goodMaxBCoord) {
2343 nscoord adjust = goodMaxBCoord - maxBCoord;
2344 psd->mLogicalBSize -= adjust;
2345 psd->mBEndLeading -= adjust;
2347 if (minBCoord > 0) {
2349 // shrink the content by moving its block start down. This is tricky,
2350 // since the block start is the 0 for many coordinates, so what we do is
2351 // move everything else up.
2352 spanFramePFD->mAscent -= minBCoord; // move the baseline up
2353 spanFramePFD->mBounds.BSize(lineWM) -= minBCoord; // move the block end up
2354 psd->mBStartLeading += minBCoord;
2355 *psd->mBaseline -= minBCoord;
2357 pfd = psd->mFirstFrame;
2358 while (nullptr != pfd) {
2359 pfd->mBounds.BStart(lineWM) -= minBCoord; // move all the children
2360 // back up
2361 pfd->mFrame->SetRect(lineWM, pfd->mBounds, ContainerWidthForSpan(psd));
2362 pfd = pfd->mNext;
2364 maxBCoord -= minBCoord; // since minBCoord is in the frame's own
2365 // coordinate system
2366 minBCoord = 0;
2368 if (maxBCoord < spanFramePFD->mBounds.BSize(lineWM)) {
2369 nscoord adjust = spanFramePFD->mBounds.BSize(lineWM) - maxBCoord;
2370 spanFramePFD->mBounds.BSize(lineWM) -= adjust; // move the bottom up
2371 psd->mBEndLeading += adjust;
2373 #ifdef NOISY_BLOCKDIR_ALIGN
2374 printf(" New: minBCoord=%d, maxBCoord=%d, bSize=%d, ascent=%d, logicalBSize=%d, topLeading=%d, bottomLeading=%d\n",
2375 minBCoord, maxBCoord, spanFramePFD->mBounds.BSize(lineWM),
2376 spanFramePFD->mAscent,
2377 psd->mLogicalBSize, psd->mBStartLeading, psd->mBEndLeading);
2378 #endif
2381 psd->mMinBCoord = minBCoord;
2382 psd->mMaxBCoord = maxBCoord;
2383 #ifdef NOISY_BLOCKDIR_ALIGN
2384 printf(" [span]==> minBCoord=%d maxBCoord=%d delta=%d maxStartBoxBSize=%d maxEndBoxBSize=%d\n",
2385 minBCoord, maxBCoord, maxBCoord - minBCoord, maxStartBoxBSize, maxEndBoxBSize);
2386 #endif
2387 if (maxStartBoxBSize > mMaxStartBoxBSize) {
2388 mMaxStartBoxBSize = maxStartBoxBSize;
2390 if (maxEndBoxBSize > mMaxEndBoxBSize) {
2391 mMaxEndBoxBSize = maxEndBoxBSize;
2395 static void SlideSpanFrameRect(nsIFrame* aFrame, nscoord aDeltaWidth)
2397 // This should not use nsIFrame::MovePositionBy because it happens
2398 // prior to relative positioning. In particular, because
2399 // nsBlockFrame::PlaceLine calls aLineLayout.TrimTrailingWhiteSpace()
2400 // prior to calling aLineLayout.RelativePositionFrames().
2401 nsPoint p = aFrame->GetPosition();
2402 p.x -= aDeltaWidth;
2403 aFrame->SetPosition(p);
2406 bool
2407 nsLineLayout::TrimTrailingWhiteSpaceIn(PerSpanData* psd,
2408 nscoord* aDeltaISize)
2410 PerFrameData* pfd = psd->mFirstFrame;
2411 if (!pfd) {
2412 *aDeltaISize = 0;
2413 return false;
2415 pfd = pfd->Last();
2416 while (nullptr != pfd) {
2417 #ifdef REALLY_NOISY_TRIM
2418 nsFrame::ListTag(stdout, psd->mFrame->mFrame);
2419 printf(": attempting trim of ");
2420 nsFrame::ListTag(stdout, pfd->mFrame);
2421 printf("\n");
2422 #endif
2423 PerSpanData* childSpan = pfd->mSpan;
2424 WritingMode lineWM = mRootSpan->mWritingMode;
2425 if (childSpan) {
2426 // Maybe the child span has the trailing white-space in it?
2427 if (TrimTrailingWhiteSpaceIn(childSpan, aDeltaISize)) {
2428 nscoord deltaISize = *aDeltaISize;
2429 if (deltaISize) {
2430 // Adjust the child spans frame size
2431 pfd->mBounds.ISize(lineWM) -= deltaISize;
2432 if (psd != mRootSpan) {
2433 // When the child span is not a direct child of the block
2434 // we need to update the child spans frame rectangle
2435 // because it most likely will not be done again. Spans
2436 // that are direct children of the block will be updated
2437 // later, however, because the VerticalAlignFrames method
2438 // will be run after this method.
2439 nscoord containerWidth = ContainerWidthForSpan(childSpan);
2440 nsIFrame* f = pfd->mFrame;
2441 LogicalRect r(lineWM, f->GetRect(), containerWidth);
2442 r.ISize(lineWM) -= deltaISize;
2443 f->SetRect(lineWM, r, containerWidth);
2446 // Adjust the inline end edge of the span that contains the child span
2447 psd->mICoord -= deltaISize;
2449 // Slide any frames that follow the child span over by the
2450 // correct amount. The only thing that can follow the child
2451 // span is empty stuff, so we are just making things
2452 // sensible (keeping the combined area honest).
2453 while (pfd->mNext) {
2454 pfd = pfd->mNext;
2455 pfd->mBounds.IStart(lineWM) -= deltaISize;
2456 if (psd != mRootSpan) {
2457 // When the child span is not a direct child of the block
2458 // we need to update the child span's frame rectangle
2459 // because it most likely will not be done again. Spans
2460 // that are direct children of the block will be updated
2461 // later, however, because the VerticalAlignFrames method
2462 // will be run after this method.
2463 SlideSpanFrameRect(pfd->mFrame, deltaISize);
2467 return true;
2470 else if (!pfd->mIsTextFrame && !pfd->mSkipWhenTrimmingWhitespace) {
2471 // If we hit a frame on the end that's not text and not a placeholder,
2472 // then there is no trailing whitespace to trim. Stop the search.
2473 *aDeltaISize = 0;
2474 return true;
2476 else if (pfd->mIsTextFrame) {
2477 // Call TrimTrailingWhiteSpace even on empty textframes because they
2478 // might have a soft hyphen which should now appear, changing the frame's
2479 // width
2480 nsTextFrame::TrimOutput trimOutput = static_cast<nsTextFrame*>(pfd->mFrame)->
2481 TrimTrailingWhiteSpace(mBlockReflowState->rendContext);
2482 #ifdef NOISY_TRIM
2483 nsFrame::ListTag(stdout, psd->mFrame->mFrame);
2484 printf(": trim of ");
2485 nsFrame::ListTag(stdout, pfd->mFrame);
2486 printf(" returned %d\n", trimOutput.mDeltaWidth);
2487 #endif
2489 if (trimOutput.mChanged) {
2490 pfd->mRecomputeOverflow = true;
2493 // Delta width not being zero means that
2494 // there is trimmed space in the frame.
2495 if (trimOutput.mDeltaWidth) {
2496 pfd->mBounds.ISize(lineWM) -= trimOutput.mDeltaWidth;
2498 // If any trailing space is trimmed, the justification opportunity
2499 // generated by the space should be removed as well.
2500 pfd->mJustificationInfo.CancelOpportunityForTrimmedSpace();
2502 // See if the text frame has already been placed in its parent
2503 if (psd != mRootSpan) {
2504 // The frame was already placed during psd's
2505 // reflow. Update the frames rectangle now.
2506 pfd->mFrame->SetRect(lineWM, pfd->mBounds,
2507 ContainerWidthForSpan(psd));
2510 // Adjust containing span's right edge
2511 psd->mICoord -= trimOutput.mDeltaWidth;
2513 // Slide any frames that follow the text frame over by the
2514 // right amount. The only thing that can follow the text
2515 // frame is empty stuff, so we are just making things
2516 // sensible (keeping the combined area honest).
2517 while (pfd->mNext) {
2518 pfd = pfd->mNext;
2519 pfd->mBounds.IStart(lineWM) -= trimOutput.mDeltaWidth;
2520 if (psd != mRootSpan) {
2521 // When the child span is not a direct child of the block
2522 // we need to update the child spans frame rectangle
2523 // because it most likely will not be done again. Spans
2524 // that are direct children of the block will be updated
2525 // later, however, because the VerticalAlignFrames method
2526 // will be run after this method.
2527 SlideSpanFrameRect(pfd->mFrame, trimOutput.mDeltaWidth);
2532 if (pfd->mIsNonEmptyTextFrame || trimOutput.mChanged) {
2533 // Pass up to caller so they can shrink their span
2534 *aDeltaISize = trimOutput.mDeltaWidth;
2535 return true;
2538 pfd = pfd->mPrev;
2541 *aDeltaISize = 0;
2542 return false;
2545 bool
2546 nsLineLayout::TrimTrailingWhiteSpace()
2548 PerSpanData* psd = mRootSpan;
2549 nscoord deltaISize;
2550 TrimTrailingWhiteSpaceIn(psd, &deltaISize);
2551 return 0 != deltaISize;
2554 bool
2555 nsLineLayout::PerFrameData::ParticipatesInJustification() const
2557 if (mIsBullet || mIsEmpty || mSkipWhenTrimmingWhitespace) {
2558 // Skip bullets, empty frames, and placeholders
2559 return false;
2561 if (mIsTextFrame && !mIsNonWhitespaceTextFrame &&
2562 static_cast<nsTextFrame*>(mFrame)->IsAtEndOfLine()) {
2563 // Skip trimmed whitespaces
2564 return false;
2566 return true;
2569 struct nsLineLayout::JustificationComputationState
2571 PerFrameData* mFirstParticipant;
2572 PerFrameData* mLastParticipant;
2573 // Whether we are going across a boundary of ruby base, i.e.
2574 // entering one, leaving one, or both.
2575 bool mCrossingRubyBaseBoundary;
2577 JustificationComputationState()
2578 : mFirstParticipant(nullptr)
2579 , mLastParticipant(nullptr)
2580 , mCrossingRubyBaseBoundary(false) { }
2584 * Compute the justification info of the given span, and store the
2585 * number of inner opportunities into the frame's justification info.
2586 * It returns the number of non-inner opportunities it detects.
2588 int32_t
2589 nsLineLayout::ComputeFrameJustification(PerSpanData* aPSD,
2590 JustificationComputationState& aState)
2592 NS_ASSERTION(aPSD, "null arg");
2593 NS_ASSERTION(!aState.mLastParticipant || !aState.mLastParticipant->mSpan,
2594 "Last participant shall always be a leaf frame");
2595 bool firstChild = true;
2596 int32_t& innerOpportunities =
2597 aPSD->mFrame->mJustificationInfo.mInnerOpportunities;
2598 MOZ_ASSERT(innerOpportunities == 0,
2599 "Justification info should not have been set yet.");
2600 int32_t outerOpportunities = 0;
2602 for (PerFrameData* pfd = aPSD->mFirstFrame; pfd; pfd = pfd->mNext) {
2603 if (!pfd->ParticipatesInJustification()) {
2604 continue;
2607 bool isRubyBase = pfd->mFrame->GetType() == nsGkAtoms::rubyBaseFrame;
2608 if (isRubyBase) {
2609 aState.mCrossingRubyBaseBoundary = true;
2612 int extraOpportunities = 0;
2613 if (pfd->mSpan) {
2614 PerSpanData* span = pfd->mSpan;
2615 extraOpportunities = ComputeFrameJustification(span, aState);
2616 innerOpportunities += pfd->mJustificationInfo.mInnerOpportunities;
2617 } else {
2618 const auto& info = pfd->mJustificationInfo;
2619 if (pfd->mIsTextFrame) {
2620 innerOpportunities += info.mInnerOpportunities;
2623 PerFrameData* prev = aState.mLastParticipant;
2624 if (!prev) {
2625 aState.mFirstParticipant = pfd;
2626 } else {
2627 auto& assign = pfd->mJustificationAssignment;
2628 auto& prevAssign = prev->mJustificationAssignment;
2629 const auto& prevInfo = prev->mJustificationInfo;
2631 if (info.mIsStartJustifiable ||
2632 prevInfo.mIsEndJustifiable ||
2633 aState.mCrossingRubyBaseBoundary) {
2634 extraOpportunities = 1;
2635 if (aState.mCrossingRubyBaseBoundary) {
2636 // For ruby alignment with value space-around, there is
2637 // always an expansion opportunity at the boundary of a ruby
2638 // base, and it always generates one gap at each side. If we
2639 // don't do it here, the interaction between text align and
2640 // and ruby align could be strange.
2641 prevAssign.mGapsAtEnd = 1;
2642 assign.mGapsAtStart = 1;
2643 aState.mCrossingRubyBaseBoundary = false;
2644 } else if (!info.mIsStartJustifiable) {
2645 prevAssign.mGapsAtEnd = 2;
2646 assign.mGapsAtStart = 0;
2647 } else if (!prevInfo.mIsEndJustifiable) {
2648 prevAssign.mGapsAtEnd = 0;
2649 assign.mGapsAtStart = 2;
2650 } else {
2651 prevAssign.mGapsAtEnd = 1;
2652 assign.mGapsAtStart = 1;
2657 aState.mLastParticipant = pfd;
2660 if (isRubyBase) {
2661 aState.mCrossingRubyBaseBoundary = true;
2664 if (firstChild) {
2665 outerOpportunities = extraOpportunities;
2666 firstChild = false;
2667 } else {
2668 innerOpportunities += extraOpportunities;
2672 return outerOpportunities;
2675 void
2676 nsLineLayout::AdvanceAnnotationInlineBounds(PerFrameData* aPFD,
2677 nscoord aContainerWidth,
2678 nscoord aDeltaICoord,
2679 nscoord aDeltaISize)
2681 nsIFrame* frame = aPFD->mFrame;
2682 nsIAtom* frameType = frame->GetType();
2683 MOZ_ASSERT(frameType == nsGkAtoms::rubyTextFrame ||
2684 frameType == nsGkAtoms::rubyTextContainerFrame);
2685 MOZ_ASSERT(aPFD->mSpan, "rt and rtc should have span.");
2687 PerSpanData* psd = aPFD->mSpan;
2688 WritingMode lineWM = mRootSpan->mWritingMode;
2689 aPFD->mBounds.IStart(lineWM) += aDeltaICoord;
2691 // Check whether this expansion should be counted into the reserved
2692 // isize or not. When it is a ruby text container, and it has some
2693 // children linked to the base, it must not have reserved isize,
2694 // or its children won't align with their bases. Otherwise, this
2695 // expansion should be reserved. There are two cases a ruby text
2696 // container does not have children linked to the base:
2697 // 1. it is a container for span; 2. its children are collapsed.
2698 // See bug 1055674 for the second case.
2699 if (frameType == nsGkAtoms::rubyTextFrame ||
2700 // This ruby text container is a span.
2701 (psd->mFirstFrame == psd->mLastFrame && psd->mFirstFrame &&
2702 !psd->mFirstFrame->mIsLinkedToBase)) {
2703 nscoord reservedISize = RubyUtils::GetReservedISize(frame);
2704 RubyUtils::SetReservedISize(frame, reservedISize + aDeltaISize);
2705 } else {
2706 // It is a normal ruby text container. Its children will expand
2707 // themselves properly. We only need to expand its own size here.
2708 aPFD->mBounds.ISize(lineWM) += aDeltaISize;
2710 aPFD->mFrame->SetRect(lineWM, aPFD->mBounds, aContainerWidth);
2714 * This function applies the changes of icoord and isize caused by
2715 * justification to annotations of the given frame.
2716 * aPFD must be one of the frames in aContainingSpan.
2718 void
2719 nsLineLayout::ApplyLineJustificationToAnnotations(PerFrameData* aPFD,
2720 PerSpanData* aContainingSpan,
2721 nscoord aDeltaICoord,
2722 nscoord aDeltaISize)
2724 PerFrameData* pfd = aPFD->mNextAnnotation;
2725 nscoord containerWidth = ContainerWidthForSpan(aContainingSpan);
2726 while (pfd) {
2727 AdvanceAnnotationInlineBounds(pfd, containerWidth,
2728 aDeltaICoord, aDeltaISize);
2730 // There are two cases where an annotation frame has siblings which
2731 // do not attached to a ruby base-level frame:
2732 // 1. there's an intra-annotation whitespace which has no intra-base
2733 // white-space to pair with;
2734 // 2. there are not enough ruby bases to be paired with annotations.
2735 // In these cases, their size should not be affected, but we still
2736 // need to move them so that they won't overlap other frames.
2737 PerFrameData* sibling = pfd->mNext;
2738 while (sibling && !sibling->mIsLinkedToBase) {
2739 AdvanceAnnotationInlineBounds(sibling, containerWidth,
2740 aDeltaICoord + aDeltaISize, 0);
2741 sibling = sibling->mNext;
2744 pfd = pfd->mNextAnnotation;
2748 nscoord
2749 nsLineLayout::ApplyFrameJustification(PerSpanData* aPSD,
2750 JustificationApplicationState& aState)
2752 NS_ASSERTION(aPSD, "null arg");
2754 nscoord deltaICoord = 0;
2755 for (PerFrameData* pfd = aPSD->mFirstFrame; pfd != nullptr; pfd = pfd->mNext) {
2756 // Don't reposition bullets (and other frames that occur out of X-order?)
2757 if (!pfd->mIsBullet) {
2758 nscoord dw = 0;
2759 WritingMode lineWM = mRootSpan->mWritingMode;
2760 const auto& assign = pfd->mJustificationAssignment;
2762 if (true == pfd->mIsTextFrame) {
2763 if (aState.IsJustifiable()) {
2764 // Set corresponding justification gaps here, so that the
2765 // text frame knows how it should add gaps at its sides.
2766 const auto& info = pfd->mJustificationInfo;
2767 auto textFrame = static_cast<nsTextFrame*>(pfd->mFrame);
2768 textFrame->AssignJustificationGaps(assign);
2769 dw = aState.Consume(JustificationUtils::CountGaps(info, assign));
2772 if (dw) {
2773 pfd->mRecomputeOverflow = true;
2776 else {
2777 if (nullptr != pfd->mSpan) {
2778 dw = ApplyFrameJustification(pfd->mSpan, aState);
2782 pfd->mBounds.ISize(lineWM) += dw;
2783 if (!pfd->mIsTextFrame && assign.TotalGaps()) {
2784 // It is possible that we assign gaps to non-text frame.
2785 // Apply the gaps as margin around the frame.
2786 deltaICoord += aState.Consume(assign.mGapsAtStart);
2787 dw += aState.Consume(assign.mGapsAtEnd);
2789 pfd->mBounds.IStart(lineWM) += deltaICoord;
2791 ApplyLineJustificationToAnnotations(pfd, aPSD, deltaICoord, dw);
2792 deltaICoord += dw;
2793 pfd->mFrame->SetRect(lineWM, pfd->mBounds, ContainerWidthForSpan(aPSD));
2796 return deltaICoord;
2800 * This method expands the given frame by the given reserved isize.
2802 void
2803 nsLineLayout::ExpandRubyBox(PerFrameData* aFrame, nscoord aReservedISize,
2804 nscoord aContainerWidth)
2806 int32_t opportunities = aFrame->mJustificationInfo.mInnerOpportunities;
2807 // Each expandable ruby box has an gap at each of its sides. For
2808 // rb/rbc, see comment in ComputeFrameJustification; for rt/rtc,
2809 // see comment in this method below.
2810 int32_t gaps = opportunities * 2 + 2;
2811 JustificationApplicationState state(gaps, aReservedISize);
2812 ApplyFrameJustification(aFrame->mSpan, state);
2814 WritingMode lineWM = mRootSpan->mWritingMode;
2815 aFrame->mBounds.ISize(lineWM) += aReservedISize;
2816 aFrame->mFrame->SetRect(lineWM, aFrame->mBounds, aContainerWidth);
2820 * This method expands the given frame by the reserved inline size.
2821 * It also expands its annotations if they are expandable and have
2822 * reserved isize larger than zero.
2824 void
2825 nsLineLayout::ExpandRubyBoxWithAnnotations(PerFrameData* aFrame,
2826 nscoord aContainerWidth)
2828 nscoord reservedISize = RubyUtils::GetReservedISize(aFrame->mFrame);
2829 if (reservedISize) {
2830 ExpandRubyBox(aFrame, reservedISize, aContainerWidth);
2833 for (PerFrameData* annotation = aFrame->mNextAnnotation;
2834 annotation; annotation = annotation->mNextAnnotation) {
2835 nscoord reservedISize = RubyUtils::GetReservedISize(annotation->mFrame);
2836 if (!reservedISize) {
2837 continue;
2840 MOZ_ASSERT(annotation->mSpan);
2841 JustificationComputationState computeState;
2842 ComputeFrameJustification(annotation->mSpan, computeState);
2843 if (!computeState.mFirstParticipant) {
2844 continue;
2846 // Add one gap at each side of this annotation.
2847 computeState.mFirstParticipant->mJustificationAssignment.mGapsAtStart = 1;
2848 computeState.mLastParticipant->mJustificationAssignment.mGapsAtEnd = 1;
2849 ExpandRubyBox(annotation, reservedISize, aContainerWidth);
2850 ExpandInlineRubyBoxes(annotation->mSpan);
2855 * This method looks for all expandable ruby box in the given span, and
2856 * calls ExpandRubyBox to expand them in depth-first preorder.
2858 void
2859 nsLineLayout::ExpandInlineRubyBoxes(PerSpanData* aSpan)
2861 nscoord containerWidth = ContainerWidthForSpan(aSpan);
2862 for (PerFrameData* pfd = aSpan->mFirstFrame; pfd; pfd = pfd->mNext) {
2863 if (RubyUtils::IsExpandableRubyBox(pfd->mFrame)) {
2864 ExpandRubyBoxWithAnnotations(pfd, containerWidth);
2866 if (pfd->mSpan) {
2867 ExpandInlineRubyBoxes(pfd->mSpan);
2872 // Align inline frames within the line according to the CSS text-align
2873 // property.
2874 void
2875 nsLineLayout::TextAlignLine(nsLineBox* aLine,
2876 bool aIsLastLine)
2879 * NOTE: aIsLastLine ain't necessarily so: it is correctly set by caller
2880 * only in cases where the last line needs special handling.
2882 PerSpanData* psd = mRootSpan;
2883 WritingMode lineWM = psd->mWritingMode;
2884 NS_WARN_IF_FALSE(psd->mIEnd != NS_UNCONSTRAINEDSIZE,
2885 "have unconstrained width; this should only result from "
2886 "very large sizes, not attempts at intrinsic width "
2887 "calculation");
2888 nscoord availISize = psd->mIEnd - psd->mIStart;
2889 nscoord remainingISize = availISize - aLine->ISize();
2890 #ifdef NOISY_INLINEDIR_ALIGN
2891 nsFrame::ListTag(stdout, mBlockReflowState->frame);
2892 printf(": availISize=%d lineBounds.IStart=%d lineISize=%d delta=%d\n",
2893 availISize, aLine->IStart(), aLine->ISize(), remainingISize);
2894 #endif
2896 // 'text-align-last: auto' is equivalent to the value of the 'text-align'
2897 // property except when 'text-align' is set to 'justify', in which case it
2898 // is 'justify' when 'text-justify' is 'distribute' and 'start' otherwise.
2900 // XXX: the code below will have to change when we implement text-justify
2902 nscoord dx = 0;
2903 uint8_t textAlign = mStyleText->mTextAlign;
2904 bool textAlignTrue = mStyleText->mTextAlignTrue;
2905 if (aIsLastLine) {
2906 textAlignTrue = mStyleText->mTextAlignLastTrue;
2907 if (mStyleText->mTextAlignLast == NS_STYLE_TEXT_ALIGN_AUTO) {
2908 if (textAlign == NS_STYLE_TEXT_ALIGN_JUSTIFY) {
2909 textAlign = NS_STYLE_TEXT_ALIGN_DEFAULT;
2911 } else {
2912 textAlign = mStyleText->mTextAlignLast;
2916 bool isSVG = mBlockReflowState->frame->IsSVGText();
2917 bool doTextAlign = remainingISize > 0 || textAlignTrue;
2919 int32_t additionalGaps = 0;
2920 if (!isSVG && (mHasRuby || (doTextAlign &&
2921 textAlign == NS_STYLE_TEXT_ALIGN_JUSTIFY))) {
2922 JustificationComputationState computeState;
2923 ComputeFrameJustification(psd, computeState);
2924 if (mHasRuby && computeState.mFirstParticipant) {
2925 PerFrameData* firstFrame = computeState.mFirstParticipant;
2926 if (firstFrame->mFrame->StyleContext()->IsInlineDescendantOfRuby()) {
2927 MOZ_ASSERT(!firstFrame->mJustificationAssignment.mGapsAtStart);
2928 firstFrame->mJustificationAssignment.mGapsAtStart = 1;
2929 additionalGaps++;
2931 PerFrameData* lastFrame = computeState.mLastParticipant;
2932 if (lastFrame->mFrame->StyleContext()->IsInlineDescendantOfRuby()) {
2933 MOZ_ASSERT(!lastFrame->mJustificationAssignment.mGapsAtEnd);
2934 lastFrame->mJustificationAssignment.mGapsAtEnd = 1;
2935 additionalGaps++;
2940 if (!isSVG && doTextAlign) {
2941 switch (textAlign) {
2942 case NS_STYLE_TEXT_ALIGN_JUSTIFY: {
2943 int32_t opportunities =
2944 psd->mFrame->mJustificationInfo.mInnerOpportunities;
2945 if (opportunities > 0) {
2946 int32_t gaps = opportunities * 2 + additionalGaps;
2947 JustificationApplicationState applyState(gaps, remainingISize);
2949 // Apply the justification, and make sure to update our linebox
2950 // width to account for it.
2951 aLine->ExpandBy(ApplyFrameJustification(psd, applyState),
2952 ContainerWidthForSpan(psd));
2954 MOZ_ASSERT(applyState.mGaps.mHandled == applyState.mGaps.mCount,
2955 "Unprocessed justification gaps");
2956 MOZ_ASSERT(applyState.mWidth.mConsumed == applyState.mWidth.mAvailable,
2957 "Unprocessed justification width");
2958 break;
2960 // Fall through to the default case if we could not justify to fill
2961 // the space.
2964 case NS_STYLE_TEXT_ALIGN_DEFAULT:
2965 // default alignment is to start edge so do nothing
2966 break;
2968 case NS_STYLE_TEXT_ALIGN_LEFT:
2969 case NS_STYLE_TEXT_ALIGN_MOZ_LEFT:
2970 if (!lineWM.IsBidiLTR()) {
2971 dx = remainingISize;
2973 break;
2975 case NS_STYLE_TEXT_ALIGN_RIGHT:
2976 case NS_STYLE_TEXT_ALIGN_MOZ_RIGHT:
2977 if (lineWM.IsBidiLTR()) {
2978 dx = remainingISize;
2980 break;
2982 case NS_STYLE_TEXT_ALIGN_END:
2983 dx = remainingISize;
2984 break;
2986 case NS_STYLE_TEXT_ALIGN_CENTER:
2987 case NS_STYLE_TEXT_ALIGN_MOZ_CENTER:
2988 dx = remainingISize / 2;
2989 break;
2993 if (mHasRuby) {
2994 ExpandInlineRubyBoxes(mRootSpan);
2997 if (mPresContext->BidiEnabled() &&
2998 (!mPresContext->IsVisualMode() || !lineWM.IsBidiLTR())) {
2999 nsBidiPresUtils::ReorderFrames(psd->mFirstFrame->mFrame,
3000 aLine->GetChildCount(),
3001 lineWM, mContainerWidth,
3002 psd->mIStart + mTextIndent + dx);
3003 if (dx) {
3004 aLine->IndentBy(dx, mContainerWidth);
3006 } else if (dx) {
3007 for (PerFrameData* pfd = psd->mFirstFrame; pfd; pfd = pfd->mNext) {
3008 pfd->mBounds.IStart(lineWM) += dx;
3009 pfd->mFrame->SetRect(lineWM, pfd->mBounds, ContainerWidthForSpan(psd));
3011 aLine->IndentBy(dx, mContainerWidth);
3015 void
3016 nsLineLayout::RelativePositionFrames(nsOverflowAreas& aOverflowAreas)
3018 RelativePositionFrames(mRootSpan, aOverflowAreas);
3021 void
3022 nsLineLayout::RelativePositionFrames(PerSpanData* psd, nsOverflowAreas& aOverflowAreas)
3024 nsOverflowAreas overflowAreas;
3025 WritingMode wm = psd->mWritingMode;
3026 if (psd != mRootSpan) {
3027 // The span's overflow areas come in three parts:
3028 // -- this frame's width and height
3029 // -- pfd->mOverflowAreas, which is the area of a bullet or the union
3030 // of a relatively positioned frame's absolute children
3031 // -- the bounds of all inline descendants
3032 // The former two parts are computed right here, we gather the descendants
3033 // below.
3034 // At this point psd->mFrame->mBounds might be out of date since
3035 // bidi reordering can move and resize the frames. So use the frame's
3036 // rect instead of mBounds.
3037 nsRect adjustedBounds(nsPoint(0, 0), psd->mFrame->mFrame->GetSize());
3039 overflowAreas.ScrollableOverflow().UnionRect(
3040 psd->mFrame->mOverflowAreas.ScrollableOverflow(), adjustedBounds);
3041 overflowAreas.VisualOverflow().UnionRect(
3042 psd->mFrame->mOverflowAreas.VisualOverflow(), adjustedBounds);
3044 else {
3045 LogicalRect rect(wm, psd->mIStart, mBStartEdge,
3046 psd->mICoord - psd->mIStart, mFinalLineBSize);
3047 // The minimum combined area for the frames that are direct
3048 // children of the block starts at the upper left corner of the
3049 // line and is sized to match the size of the line's bounding box
3050 // (the same size as the values returned from VerticalAlignFrames)
3051 overflowAreas.VisualOverflow() = rect.GetPhysicalRect(wm, mContainerWidth);
3052 overflowAreas.ScrollableOverflow() = overflowAreas.VisualOverflow();
3055 for (PerFrameData* pfd = psd->mFirstFrame; pfd; pfd = pfd->mNext) {
3056 nsIFrame* frame = pfd->mFrame;
3057 nsPoint origin = frame->GetPosition();
3059 // Adjust the origin of the frame
3060 if (pfd->mRelativePos) {
3061 //XXX temporary until ApplyRelativePositioning can handle logical offsets
3062 nsMargin physicalOffsets =
3063 pfd->mOffsets.GetPhysicalMargin(pfd->mFrame->GetWritingMode());
3064 // right and bottom are handled by
3065 // nsHTMLReflowState::ComputeRelativeOffsets
3066 nsHTMLReflowState::ApplyRelativePositioning(pfd->mFrame,
3067 physicalOffsets,
3068 &origin);
3069 frame->SetPosition(origin);
3072 // We must position the view correctly before positioning its
3073 // descendants so that widgets are positioned properly (since only
3074 // some views have widgets).
3075 if (frame->HasView())
3076 nsContainerFrame::SyncFrameViewAfterReflow(mPresContext, frame,
3077 frame->GetView(), pfd->mOverflowAreas.VisualOverflow(),
3078 NS_FRAME_NO_SIZE_VIEW);
3080 // Note: the combined area of a child is in its coordinate
3081 // system. We adjust the childs combined area into our coordinate
3082 // system before computing the aggregated value by adding in
3083 // <b>x</b> and <b>y</b> which were computed above.
3084 nsOverflowAreas r;
3085 if (pfd->mSpan) {
3086 // Compute a new combined area for the child span before
3087 // aggregating it into our combined area.
3088 RelativePositionFrames(pfd->mSpan, r);
3089 } else {
3090 r = pfd->mOverflowAreas;
3091 if (pfd->mIsTextFrame) {
3092 // We need to recompute overflow areas in two cases:
3093 // (1) When PFD_RECOMPUTEOVERFLOW is set due to trimming
3094 // (2) When there are text decorations, since we can't recompute the
3095 // overflow area until Reflow and VerticalAlignLine have finished
3096 if (pfd->mRecomputeOverflow ||
3097 frame->StyleContext()->HasTextDecorationLines()) {
3098 nsTextFrame* f = static_cast<nsTextFrame*>(frame);
3099 r = f->RecomputeOverflow(*mBlockReflowState);
3101 frame->FinishAndStoreOverflow(r, frame->GetSize());
3104 // If we have something that's not an inline but with a complex frame
3105 // hierarchy inside that contains views, they need to be
3106 // positioned.
3107 // All descendant views must be repositioned even if this frame
3108 // does have a view in case this frame's view does not have a
3109 // widget and some of the descendant views do have widgets --
3110 // otherwise the widgets won't be repositioned.
3111 nsContainerFrame::PositionChildViews(frame);
3114 // Do this here (rather than along with setting the overflow rect
3115 // below) so we get leaf frames as well. No need to worry
3116 // about the root span, since it doesn't have a frame.
3117 if (frame->HasView())
3118 nsContainerFrame::SyncFrameViewAfterReflow(mPresContext, frame,
3119 frame->GetView(),
3120 r.VisualOverflow(),
3121 NS_FRAME_NO_MOVE_VIEW);
3123 overflowAreas.UnionWith(r + origin);
3126 // If we just computed a spans combined area, we need to update its
3127 // overflow rect...
3128 if (psd != mRootSpan) {
3129 PerFrameData* spanPFD = psd->mFrame;
3130 nsIFrame* frame = spanPFD->mFrame;
3131 frame->FinishAndStoreOverflow(overflowAreas, frame->GetSize());
3133 aOverflowAreas = overflowAreas;
3136 void
3137 nsLineLayout::AdvanceICoord(nscoord aAmount)
3139 mCurrentSpan->mICoord += aAmount;
3142 WritingMode
3143 nsLineLayout::GetWritingMode()
3145 return mRootSpan->mWritingMode;
3148 nscoord
3149 nsLineLayout::GetCurrentICoord()
3151 return mCurrentSpan->mICoord;