1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 /* state and methods used while laying out a single line of a block frame */
9 #include "nsLineLayout.h"
11 #include "mozilla/ComputedStyle.h"
12 #include "mozilla/SVGTextFrame.h"
13 #include "mozilla/SVGUtils.h"
15 #include "LayoutLogging.h"
16 #include "nsBlockFrame.h"
17 #include "nsFontMetrics.h"
18 #include "nsStyleConsts.h"
19 #include "nsContainerFrame.h"
20 #include "nsFloatManager.h"
21 #include "nsPresContext.h"
22 #include "nsGkAtoms.h"
23 #include "nsIContent.h"
24 #include "nsLayoutUtils.h"
25 #include "nsTextFrame.h"
26 #include "nsStyleStructInlines.h"
27 #include "nsBidiPresUtils.h"
28 #include "nsRubyFrame.h"
29 #include "nsRubyTextFrame.h"
30 #include "RubyUtils.h"
34 # undef NOISY_INLINEDIR_ALIGN
35 # undef NOISY_BLOCKDIR_ALIGN
37 # undef REALLY_NOISY_REFLOW
39 # undef REALLY_NOISY_PUSHING
40 # undef NOISY_CAN_PLACE_FRAME
42 # undef REALLY_NOISY_TRIM
45 using namespace mozilla
;
47 //----------------------------------------------------------------------
49 nsLineLayout::nsLineLayout(nsPresContext
* aPresContext
,
50 nsFloatManager
* aFloatManager
,
51 const ReflowInput
* aOuterReflowInput
,
52 const nsLineList::iterator
* aLine
,
53 nsLineLayout
* aBaseLineLayout
)
54 : mPresContext(aPresContext
),
55 mFloatManager(aFloatManager
),
56 mBlockReflowInput(aOuterReflowInput
),
57 mBaseLineLayout(aBaseLineLayout
),
58 mLastOptionalBreakFrame(nullptr),
59 mForceBreakFrame(nullptr),
60 mBlockRI(nullptr), /* XXX temporary */
61 mLastOptionalBreakPriority(gfxBreakPriority::eNoBreak
),
62 mLastOptionalBreakFrameOffset(-1),
63 mForceBreakFrameOffset(-1),
69 mFirstLetterStyleOK(false),
71 mImpactedByFloats(false),
72 mLastFloatWasLetterFrame(false),
78 mInFirstLetter(false),
80 mDirtyNextLine(false),
83 mSuppressLineWrap(SVGUtils::IsInSVGTextSubtree(aOuterReflowInput
->mFrame
))
92 MOZ_ASSERT(aOuterReflowInput
, "aOuterReflowInput must not be null");
93 NS_ASSERTION(aFloatManager
|| aOuterReflowInput
->mFrame
->IsLetterFrame(),
94 "float manager should be present");
95 MOZ_ASSERT((!!mBaseLineLayout
) ==
96 aOuterReflowInput
->mFrame
->IsRubyTextContainerFrame(),
97 "Only ruby text container frames have "
98 "a different base line layout");
99 MOZ_COUNT_CTOR(nsLineLayout
);
101 // Stash away some style data that we need
102 nsBlockFrame
* blockFrame
= do_QueryFrame(aOuterReflowInput
->mFrame
);
104 mStyleText
= blockFrame
->StyleTextForLineLayout();
106 mStyleText
= aOuterReflowInput
->mFrame
->StyleText();
109 mTotalPlacedFrames
= 0;
113 mInflationMinFontSize
=
114 nsLayoutUtils::InflationMinFontSizeFor(aOuterReflowInput
->mFrame
);
116 // Instead of always pre-initializing the free-lists for frames and
117 // spans, we do it on demand so that situations that only use a few
118 // frames and spans won't waste a lot of time in unneeded
120 mFrameFreeList
= nullptr;
121 mSpanFreeList
= nullptr;
123 mCurrentSpan
= mRootSpan
= nullptr;
132 nsLineLayout::~nsLineLayout() {
133 MOZ_COUNT_DTOR(nsLineLayout
);
135 NS_ASSERTION(nullptr == mRootSpan
, "bad line-layout user");
138 // Find out if the frame has a non-null prev-in-flow, i.e., whether it
139 // is a continuation.
140 inline bool HasPrevInFlow(nsIFrame
* aFrame
) {
141 nsIFrame
* prevInFlow
= aFrame
->GetPrevInFlow();
142 return prevInFlow
!= nullptr;
145 void nsLineLayout::BeginLineReflow(nscoord aICoord
, nscoord aBCoord
,
146 nscoord aISize
, nscoord aBSize
,
147 bool aImpactedByFloats
, bool aIsTopOfPage
,
148 WritingMode aWritingMode
,
149 const nsSize
& aContainerSize
) {
150 NS_ASSERTION(nullptr == mRootSpan
, "bad linelayout user");
151 LAYOUT_WARN_IF_FALSE(aISize
!= NS_UNCONSTRAINEDSIZE
,
152 "have unconstrained width; this should only result from "
153 "very large sizes, not attempts at intrinsic width "
156 if ((aISize
!= NS_UNCONSTRAINEDSIZE
) && ABSURD_SIZE(aISize
) &&
157 !LineContainerFrame()->GetParent()->IsAbsurdSizeAssertSuppressed()) {
158 mBlockReflowInput
->mFrame
->ListTag(stdout
);
159 printf(": Init: bad caller: width WAS %d(0x%x)\n", aISize
, aISize
);
161 if ((aBSize
!= NS_UNCONSTRAINEDSIZE
) && ABSURD_SIZE(aBSize
) &&
162 !LineContainerFrame()->GetParent()->IsAbsurdSizeAssertSuppressed()) {
163 mBlockReflowInput
->mFrame
->ListTag(stdout
);
164 printf(": Init: bad caller: height WAS %d(0x%x)\n", aBSize
, aBSize
);
168 mBlockReflowInput
->mFrame
->ListTag(stdout
);
169 printf(": BeginLineReflow: %d,%d,%d,%d impacted=%s %s\n", aICoord
, aBCoord
,
170 aISize
, aBSize
, aImpactedByFloats
? "true" : "false",
171 aIsTopOfPage
? "top-of-page" : "");
174 mSpansAllocated
= mSpansFreed
= mFramesAllocated
= mFramesFreed
= 0;
177 mFirstLetterStyleOK
= false;
178 mIsTopOfPage
= aIsTopOfPage
;
179 mImpactedByFloats
= aImpactedByFloats
;
180 mTotalPlacedFrames
= 0;
181 if (!mBaseLineLayout
) {
185 mLineIsEmpty
= false;
186 mLineAtStart
= false;
188 mLineEndsInBR
= false;
190 mMaxStartBoxBSize
= mMaxEndBoxBSize
= 0;
193 mLineBox
->ClearHasMarker();
196 PerSpanData
* psd
= NewPerSpanData();
197 mCurrentSpan
= mRootSpan
= psd
;
198 psd
->mReflowInput
= mBlockReflowInput
;
199 psd
->mIStart
= aICoord
;
200 psd
->mICoord
= aICoord
;
201 psd
->mIEnd
= aICoord
+ aISize
;
202 mContainerSize
= aContainerSize
;
204 mBStartEdge
= aBCoord
;
206 psd
->mNoWrap
= !mStyleText
->WhiteSpaceCanWrapStyle() || mSuppressLineWrap
;
207 psd
->mWritingMode
= aWritingMode
;
209 // If this is the first line of a block then see if the text-indent
210 // property amounts to anything.
212 if (0 == mLineNumber
&& !HasPrevInFlow(mBlockReflowInput
->mFrame
)) {
213 nscoord pctBasis
= mBlockReflowInput
->ComputedISize();
214 mTextIndent
= mStyleText
->mTextIndent
.Resolve(pctBasis
);
215 psd
->mICoord
+= mTextIndent
;
218 PerFrameData
* pfd
= NewPerFrameData(mBlockReflowInput
->mFrame
);
222 nsIFrame
* frame
= mBlockReflowInput
->mFrame
;
223 if (frame
->IsRubyTextContainerFrame()) {
224 // Ruby text container won't be reflowed via ReflowFrame, hence the
225 // relative positioning information should be recorded here.
226 MOZ_ASSERT(mBaseLineLayout
!= this);
228 mBlockReflowInput
->mStyleDisplay
->IsRelativelyPositionedStyle();
229 if (pfd
->mRelativePos
) {
230 MOZ_ASSERT(mBlockReflowInput
->GetWritingMode() == pfd
->mWritingMode
,
231 "mBlockReflowInput->frame == frame, "
232 "hence they should have identical writing mode");
234 mBlockReflowInput
->ComputedLogicalOffsets(pfd
->mWritingMode
);
239 void nsLineLayout::EndLineReflow() {
241 mBlockReflowInput
->mFrame
->ListTag(stdout
);
242 printf(": EndLineReflow: width=%d\n",
243 mRootSpan
->mICoord
- mRootSpan
->mIStart
);
246 NS_ASSERTION(!mBaseLineLayout
||
247 (!mSpansAllocated
&& !mSpansFreed
&& !mSpanFreeList
&&
248 !mFramesAllocated
&& !mFramesFreed
&& !mFrameFreeList
),
249 "Allocated frames or spans on non-base line layout?");
250 MOZ_ASSERT(mRootSpan
== mCurrentSpan
);
252 UnlinkFrame(mRootSpan
->mFrame
);
253 mCurrentSpan
= mRootSpan
= nullptr;
255 NS_ASSERTION(mSpansAllocated
== mSpansFreed
, "leak");
256 NS_ASSERTION(mFramesAllocated
== mFramesFreed
, "leak");
259 static int32_t maxSpansAllocated
= NS_LINELAYOUT_NUM_SPANS
;
260 static int32_t maxFramesAllocated
= NS_LINELAYOUT_NUM_FRAMES
;
261 if (mSpansAllocated
> maxSpansAllocated
) {
262 printf("XXX: saw a line with %d spans\n", mSpansAllocated
);
263 maxSpansAllocated
= mSpansAllocated
;
265 if (mFramesAllocated
> maxFramesAllocated
) {
266 printf("XXX: saw a line with %d frames\n", mFramesAllocated
);
267 maxFramesAllocated
= mFramesAllocated
;
272 // XXX swtich to a single mAvailLineWidth that we adjust as each frame
273 // on the line is placed. Each span can still have a per-span mICoord that
274 // tracks where a child frame is going in its span; they don't need a
277 void nsLineLayout::UpdateBand(WritingMode aWM
,
278 const LogicalRect
& aNewAvailSpace
,
279 nsIFrame
* aFloatFrame
) {
280 WritingMode lineWM
= mRootSpan
->mWritingMode
;
281 // need to convert to our writing mode, because we might have a different
282 // mode from the caller due to dir: auto
283 LogicalRect availSpace
=
284 aNewAvailSpace
.ConvertTo(lineWM
, aWM
, ContainerSize());
285 #ifdef REALLY_NOISY_REFLOW
287 "nsLL::UpdateBand %d, %d, %d, %d, (converted to %d, %d, %d, %d); "
288 "frame=%p\n will set mImpacted to true\n",
289 aNewAvailSpace
.IStart(aWM
), aNewAvailSpace
.BStart(aWM
),
290 aNewAvailSpace
.ISize(aWM
), aNewAvailSpace
.BSize(aWM
),
291 availSpace
.IStart(lineWM
), availSpace
.BStart(lineWM
),
292 availSpace
.ISize(lineWM
), availSpace
.BSize(lineWM
), aFloatFrame
);
295 if ((availSpace
.ISize(lineWM
) != NS_UNCONSTRAINEDSIZE
) &&
296 ABSURD_SIZE(availSpace
.ISize(lineWM
)) &&
297 !LineContainerFrame()->GetParent()->IsAbsurdSizeAssertSuppressed()) {
298 mBlockReflowInput
->mFrame
->ListTag(stdout
);
299 printf(": UpdateBand: bad caller: ISize WAS %d(0x%x)\n",
300 availSpace
.ISize(lineWM
), availSpace
.ISize(lineWM
));
302 if ((availSpace
.BSize(lineWM
) != NS_UNCONSTRAINEDSIZE
) &&
303 ABSURD_SIZE(availSpace
.BSize(lineWM
)) &&
304 !LineContainerFrame()->GetParent()->IsAbsurdSizeAssertSuppressed()) {
305 mBlockReflowInput
->mFrame
->ListTag(stdout
);
306 printf(": UpdateBand: bad caller: BSize WAS %d(0x%x)\n",
307 availSpace
.BSize(lineWM
), availSpace
.BSize(lineWM
));
311 // Compute the difference between last times width and the new width
312 NS_WARNING_ASSERTION(
313 mRootSpan
->mIEnd
!= NS_UNCONSTRAINEDSIZE
&&
314 availSpace
.ISize(lineWM
) != NS_UNCONSTRAINEDSIZE
,
315 "have unconstrained inline size; this should only result from very large "
316 "sizes, not attempts at intrinsic width calculation");
317 // The root span's mIStart moves to aICoord
318 nscoord deltaICoord
= availSpace
.IStart(lineWM
) - mRootSpan
->mIStart
;
319 // The inline size of all spans changes by this much (the root span's
320 // mIEnd moves to aICoord + aISize, its new inline size is aISize)
322 availSpace
.ISize(lineWM
) - (mRootSpan
->mIEnd
- mRootSpan
->mIStart
);
324 mBlockReflowInput
->mFrame
->ListTag(stdout
);
325 printf(": UpdateBand: %d,%d,%d,%d deltaISize=%d deltaICoord=%d\n",
326 availSpace
.IStart(lineWM
), availSpace
.BStart(lineWM
),
327 availSpace
.ISize(lineWM
), availSpace
.BSize(lineWM
), deltaISize
,
331 // Update the root span position
332 mRootSpan
->mIStart
+= deltaICoord
;
333 mRootSpan
->mIEnd
+= deltaICoord
;
334 mRootSpan
->mICoord
+= deltaICoord
;
336 // Now update the right edges of the open spans to account for any
337 // change in available space width
338 for (PerSpanData
* psd
= mCurrentSpan
; psd
; psd
= psd
->mParent
) {
339 psd
->mIEnd
+= deltaISize
;
340 psd
->mContainsFloat
= true;
342 printf(" span %p: oldIEnd=%d newIEnd=%d\n", psd
, psd
->mIEnd
- deltaISize
,
346 NS_ASSERTION(mRootSpan
->mContainsFloat
&&
347 mRootSpan
->mIStart
== availSpace
.IStart(lineWM
) &&
348 mRootSpan
->mIEnd
== availSpace
.IEnd(lineWM
),
349 "root span was updated incorrectly?");
351 // Update frame bounds
352 // Note: Only adjust the outermost frames (the ones that are direct
353 // children of the block), not the ones in the child spans. The reason
354 // is simple: the frames in the spans have coordinates local to their
355 // parent therefore they are moved when their parent span is moved.
356 if (deltaICoord
!= 0) {
357 for (PerFrameData
* pfd
= mRootSpan
->mFirstFrame
; pfd
; pfd
= pfd
->mNext
) {
358 pfd
->mBounds
.IStart(lineWM
) += deltaICoord
;
362 mBStartEdge
= availSpace
.BStart(lineWM
);
363 mImpactedByFloats
= true;
365 mLastFloatWasLetterFrame
= aFloatFrame
->IsLetterFrame();
368 nsLineLayout::PerSpanData
* nsLineLayout::NewPerSpanData() {
369 nsLineLayout
* outerLineLayout
= GetOutermostLineLayout();
370 PerSpanData
* psd
= outerLineLayout
->mSpanFreeList
;
372 void* mem
= outerLineLayout
->mArena
.Allocate(sizeof(PerSpanData
));
373 psd
= reinterpret_cast<PerSpanData
*>(mem
);
375 outerLineLayout
->mSpanFreeList
= psd
->mNextFreeSpan
;
377 psd
->mParent
= nullptr;
378 psd
->mFrame
= nullptr;
379 psd
->mFirstFrame
= nullptr;
380 psd
->mLastFrame
= nullptr;
381 psd
->mContainsFloat
= false;
382 psd
->mHasNonemptyContent
= false;
385 outerLineLayout
->mSpansAllocated
++;
390 void nsLineLayout::BeginSpan(nsIFrame
* aFrame
,
391 const ReflowInput
* aSpanReflowInput
,
392 nscoord aIStart
, nscoord aIEnd
,
393 nscoord
* aBaseline
) {
394 NS_ASSERTION(aIEnd
!= NS_UNCONSTRAINEDSIZE
,
395 "should no longer be using unconstrained sizes");
397 nsIFrame::IndentBy(stdout
, mSpanDepth
+ 1);
398 aFrame
->ListTag(stdout
);
399 printf(": BeginSpan leftEdge=%d rightEdge=%d\n", aIStart
, aIEnd
);
402 PerSpanData
* psd
= NewPerSpanData();
403 // Link up span frame's pfd to point to its child span data
404 PerFrameData
* pfd
= mCurrentSpan
->mLastFrame
;
405 NS_ASSERTION(pfd
->mFrame
== aFrame
, "huh?");
410 psd
->mParent
= mCurrentSpan
;
411 psd
->mReflowInput
= aSpanReflowInput
;
412 psd
->mIStart
= aIStart
;
413 psd
->mICoord
= aIStart
;
415 psd
->mBaseline
= aBaseline
;
417 nsIFrame
* frame
= aSpanReflowInput
->mFrame
;
418 psd
->mNoWrap
= !frame
->StyleText()->WhiteSpaceCanWrap(frame
) ||
419 mSuppressLineWrap
|| frame
->Style()->ShouldSuppressLineBreak();
420 psd
->mWritingMode
= aSpanReflowInput
->GetWritingMode();
422 // Switch to new span
427 nscoord
nsLineLayout::EndSpan(nsIFrame
* aFrame
) {
428 NS_ASSERTION(mSpanDepth
> 0, "end-span without begin-span");
430 nsIFrame::IndentBy(stdout
, mSpanDepth
);
431 aFrame
->ListTag(stdout
);
432 printf(": EndSpan width=%d\n", mCurrentSpan
->mICoord
- mCurrentSpan
->mIStart
);
434 PerSpanData
* psd
= mCurrentSpan
;
435 MOZ_ASSERT(psd
->mParent
, "We never call this on the root");
437 if (psd
->mNoWrap
&& !psd
->mParent
->mNoWrap
) {
441 nscoord iSizeResult
= psd
->mLastFrame
? (psd
->mICoord
- psd
->mIStart
) : 0;
444 mCurrentSpan
->mReflowInput
= nullptr; // no longer valid so null it out!
445 mCurrentSpan
= mCurrentSpan
->mParent
;
449 void nsLineLayout::AttachFrameToBaseLineLayout(PerFrameData
* aFrame
) {
450 MOZ_ASSERT(mBaseLineLayout
,
451 "This method must not be called in a base line layout.");
453 PerFrameData
* baseFrame
= mBaseLineLayout
->LastFrame();
454 MOZ_ASSERT(aFrame
&& baseFrame
);
455 MOZ_ASSERT(!aFrame
->mIsLinkedToBase
,
456 "The frame must not have been linked with the base");
458 LayoutFrameType baseType
= baseFrame
->mFrame
->Type();
459 LayoutFrameType annotationType
= aFrame
->mFrame
->Type();
460 MOZ_ASSERT((baseType
== LayoutFrameType::RubyBaseContainer
&&
461 annotationType
== LayoutFrameType::RubyTextContainer
) ||
462 (baseType
== LayoutFrameType::RubyBase
&&
463 annotationType
== LayoutFrameType::RubyText
));
466 aFrame
->mNextAnnotation
= baseFrame
->mNextAnnotation
;
467 baseFrame
->mNextAnnotation
= aFrame
;
468 aFrame
->mIsLinkedToBase
= true;
471 int32_t nsLineLayout::GetCurrentSpanCount() const {
472 NS_ASSERTION(mCurrentSpan
== mRootSpan
, "bad linelayout user");
474 PerFrameData
* pfd
= mRootSpan
->mFirstFrame
;
475 while (nullptr != pfd
) {
482 void nsLineLayout::SplitLineTo(int32_t aNewCount
) {
483 NS_ASSERTION(mCurrentSpan
== mRootSpan
, "bad linelayout user");
485 #ifdef REALLY_NOISY_PUSHING
486 printf("SplitLineTo %d (current count=%d); before:\n", aNewCount
,
487 GetCurrentSpanCount());
488 DumpPerSpanData(mRootSpan
, 1);
490 PerSpanData
* psd
= mRootSpan
;
491 PerFrameData
* pfd
= psd
->mFirstFrame
;
492 while (nullptr != pfd
) {
493 if (--aNewCount
== 0) {
494 // Truncate list at pfd (we keep pfd, but anything following is freed)
495 PerFrameData
* next
= pfd
->mNext
;
496 pfd
->mNext
= nullptr;
497 psd
->mLastFrame
= pfd
;
499 // Now unlink all of the frames following pfd
506 printf("SplitLineTo %d (current count=%d); after:\n", aNewCount
,
507 GetCurrentSpanCount());
508 DumpPerSpanData(mRootSpan
, 1);
512 void nsLineLayout::PushFrame(nsIFrame
* aFrame
) {
513 PerSpanData
* psd
= mCurrentSpan
;
514 NS_ASSERTION(psd
->mLastFrame
->mFrame
== aFrame
, "pushing non-last frame");
516 #ifdef REALLY_NOISY_PUSHING
517 nsIFrame::IndentBy(stdout
, mSpanDepth
);
518 printf("PushFrame %p, before:\n", psd
);
519 DumpPerSpanData(psd
, 1);
522 // Take the last frame off of the span's frame list
523 PerFrameData
* pfd
= psd
->mLastFrame
;
524 if (pfd
== psd
->mFirstFrame
) {
525 // We are pushing away the only frame...empty the list
526 psd
->mFirstFrame
= nullptr;
527 psd
->mLastFrame
= nullptr;
529 PerFrameData
* prevFrame
= pfd
->mPrev
;
530 prevFrame
->mNext
= nullptr;
531 psd
->mLastFrame
= prevFrame
;
534 // Now unlink the frame
535 MOZ_ASSERT(!pfd
->mNext
);
538 nsIFrame::IndentBy(stdout
, mSpanDepth
);
539 printf("PushFrame: %p after:\n", psd
);
540 DumpPerSpanData(psd
, 1);
544 void nsLineLayout::UnlinkFrame(PerFrameData
* pfd
) {
545 while (nullptr != pfd
) {
546 PerFrameData
* next
= pfd
->mNext
;
547 if (pfd
->mIsLinkedToBase
) {
548 // This frame is linked to a ruby base, and should not be freed
549 // now. Just unlink it from the span. It will be freed when its
550 // base frame gets unlinked.
551 pfd
->mNext
= pfd
->mPrev
= nullptr;
556 // It is a ruby base frame. If there are any annotations
557 // linked to this frame, free them first.
558 PerFrameData
* annotationPFD
= pfd
->mNextAnnotation
;
559 while (annotationPFD
) {
560 PerFrameData
* nextAnnotation
= annotationPFD
->mNextAnnotation
;
562 annotationPFD
->mNext
== nullptr && annotationPFD
->mPrev
== nullptr,
563 "PFD in annotations should have been unlinked.");
564 FreeFrame(annotationPFD
);
565 annotationPFD
= nextAnnotation
;
573 void nsLineLayout::FreeFrame(PerFrameData
* pfd
) {
574 if (nullptr != pfd
->mSpan
) {
575 FreeSpan(pfd
->mSpan
);
577 nsLineLayout
* outerLineLayout
= GetOutermostLineLayout();
578 pfd
->mNext
= outerLineLayout
->mFrameFreeList
;
579 outerLineLayout
->mFrameFreeList
= pfd
;
581 outerLineLayout
->mFramesFreed
++;
585 void nsLineLayout::FreeSpan(PerSpanData
* psd
) {
587 UnlinkFrame(psd
->mFirstFrame
);
589 nsLineLayout
* outerLineLayout
= GetOutermostLineLayout();
590 // Now put the span on the free list since it's free too
591 psd
->mNextFreeSpan
= outerLineLayout
->mSpanFreeList
;
592 outerLineLayout
->mSpanFreeList
= psd
;
594 outerLineLayout
->mSpansFreed
++;
598 bool nsLineLayout::IsZeroBSize() {
599 PerSpanData
* psd
= mCurrentSpan
;
600 PerFrameData
* pfd
= psd
->mFirstFrame
;
601 while (nullptr != pfd
) {
602 if (0 != pfd
->mBounds
.BSize(psd
->mWritingMode
)) {
610 nsLineLayout::PerFrameData
* nsLineLayout::NewPerFrameData(nsIFrame
* aFrame
) {
611 nsLineLayout
* outerLineLayout
= GetOutermostLineLayout();
612 PerFrameData
* pfd
= outerLineLayout
->mFrameFreeList
;
614 void* mem
= outerLineLayout
->mArena
.Allocate(sizeof(PerFrameData
));
615 pfd
= reinterpret_cast<PerFrameData
*>(mem
);
617 outerLineLayout
->mFrameFreeList
= pfd
->mNext
;
619 pfd
->mSpan
= nullptr;
620 pfd
->mNext
= nullptr;
621 pfd
->mPrev
= nullptr;
622 pfd
->mNextAnnotation
= nullptr;
623 pfd
->mFrame
= aFrame
;
625 // all flags default to false
626 pfd
->mRelativePos
= false;
627 pfd
->mIsTextFrame
= false;
628 pfd
->mIsNonEmptyTextFrame
= false;
629 pfd
->mIsNonWhitespaceTextFrame
= false;
630 pfd
->mIsLetterFrame
= false;
631 pfd
->mRecomputeOverflow
= false;
632 pfd
->mIsMarker
= false;
633 pfd
->mSkipWhenTrimmingWhitespace
= false;
634 pfd
->mIsEmpty
= false;
635 pfd
->mIsPlaceholder
= false;
636 pfd
->mIsLinkedToBase
= false;
638 pfd
->mWritingMode
= aFrame
->GetWritingMode();
639 WritingMode lineWM
= mRootSpan
->mWritingMode
;
640 pfd
->mBounds
= LogicalRect(lineWM
);
641 pfd
->mOverflowAreas
.Clear();
642 pfd
->mMargin
= LogicalMargin(lineWM
);
643 pfd
->mBorderPadding
= LogicalMargin(lineWM
);
644 pfd
->mOffsets
= LogicalMargin(pfd
->mWritingMode
);
646 pfd
->mJustificationInfo
= JustificationInfo();
647 pfd
->mJustificationAssignment
= JustificationAssignment();
650 pfd
->mBlockDirAlign
= 0xFF;
651 outerLineLayout
->mFramesAllocated
++;
656 bool nsLineLayout::LineIsBreakable() const {
657 // XXX mTotalPlacedFrames should go away and we should just use
658 // mLineIsEmpty here instead
659 if ((0 != mTotalPlacedFrames
) || mImpactedByFloats
) {
665 // Checks all four sides for percentage units. This means it should
666 // only be used for things (margin, padding) where percentages on top
667 // and bottom depend on the *width* just like percentages on left and
669 template <typename T
>
670 static bool HasPercentageUnitSide(const StyleRect
<T
>& aSides
) {
671 return aSides
.Any([](const auto& aLength
) { return aLength
.HasPercent(); });
674 static bool IsPercentageAware(const nsIFrame
* aFrame
, WritingMode aWM
) {
675 NS_ASSERTION(aFrame
, "null frame is not allowed");
677 LayoutFrameType fType
= aFrame
->Type();
678 if (fType
== LayoutFrameType::Text
) {
679 // None of these things can ever be true for text frames.
683 // Some of these things don't apply to non-replaced inline frames
684 // (that is, fType == LayoutFrameType::Inline), but we won't bother making
685 // things unnecessarily complicated, since they'll probably be set
688 const nsStyleMargin
* margin
= aFrame
->StyleMargin();
689 if (HasPercentageUnitSide(margin
->mMargin
)) {
693 const nsStylePadding
* padding
= aFrame
->StylePadding();
694 if (HasPercentageUnitSide(padding
->mPadding
)) {
698 // Note that borders can't be aware of percentages
700 const nsStylePosition
* pos
= aFrame
->StylePosition();
702 if ((pos
->ISizeDependsOnContainer(aWM
) && !pos
->ISize(aWM
).IsAuto()) ||
703 pos
->MaxISizeDependsOnContainer(aWM
) ||
704 pos
->MinISizeDependsOnContainer(aWM
) ||
705 pos
->mOffset
.GetIStart(aWM
).HasPercent() ||
706 pos
->mOffset
.GetIEnd(aWM
).HasPercent()) {
710 if (pos
->ISize(aWM
).IsAuto()) {
711 // We need to check for frames that shrink-wrap when they're auto
713 const nsStyleDisplay
* disp
= aFrame
->StyleDisplay();
714 if ((disp
->DisplayOutside() == StyleDisplayOutside::Inline
&&
715 (disp
->DisplayInside() == StyleDisplayInside::FlowRoot
||
716 disp
->DisplayInside() == StyleDisplayInside::Table
)) ||
717 fType
== LayoutFrameType::HTMLButtonControl
||
718 fType
== LayoutFrameType::GfxButtonControl
||
719 fType
== LayoutFrameType::FieldSet
||
720 fType
== LayoutFrameType::ComboboxDisplay
) {
724 // Per CSS 2.1, section 10.3.2:
725 // If 'height' and 'width' both have computed values of 'auto' and
726 // the element has an intrinsic ratio but no intrinsic height or
727 // width and the containing block's width does not itself depend
728 // on the replaced element's width, then the used value of 'width'
729 // is calculated from the constraint equation used for
730 // block-level, non-replaced elements in normal flow.
731 nsIFrame
* f
= const_cast<nsIFrame
*>(aFrame
);
732 if (f
->GetAspectRatio() &&
733 // Some percents are treated like 'auto', so check != coord
734 !pos
->BSize(aWM
).ConvertsToLength()) {
735 const IntrinsicSize
& intrinsicSize
= f
->GetIntrinsicSize();
736 if (!intrinsicSize
.width
&& !intrinsicSize
.height
) {
745 void nsLineLayout::ReflowFrame(nsIFrame
* aFrame
, nsReflowStatus
& aReflowStatus
,
746 ReflowOutput
* aMetrics
, bool& aPushedFrame
) {
747 // Initialize OUT parameter
748 aPushedFrame
= false;
750 PerFrameData
* pfd
= NewPerFrameData(aFrame
);
751 PerSpanData
* psd
= mCurrentSpan
;
752 psd
->AppendFrame(pfd
);
754 #ifdef REALLY_NOISY_REFLOW
755 nsIFrame::IndentBy(stdout
, mSpanDepth
);
756 printf("%p: Begin ReflowFrame pfd=%p ", psd
, pfd
);
757 aFrame
->ListTag(stdout
);
761 if (mCurrentSpan
== mRootSpan
) {
762 pfd
->mFrame
->RemoveProperty(nsIFrame::LineBaselineOffset());
766 pfd
->mFrame
->GetProperty(nsIFrame::LineBaselineOffset(), &hasLineOffset
);
767 NS_ASSERTION(!hasLineOffset
,
768 "LineBaselineOffset was set but was not expected");
772 mJustificationInfo
= JustificationInfo();
774 // Stash copies of some of the computed state away for later
775 // (block-direction alignment, for example)
776 WritingMode frameWM
= pfd
->mWritingMode
;
777 WritingMode lineWM
= mRootSpan
->mWritingMode
;
779 // NOTE: While the inline direction coordinate remains relative to the
780 // parent span, the block direction coordinate is fixed at the top
781 // edge for the line. During VerticalAlignFrames we will repair this
782 // so that the block direction coordinate is properly set and relative
783 // to the appropriate span.
784 pfd
->mBounds
.IStart(lineWM
) = psd
->mICoord
;
785 pfd
->mBounds
.BStart(lineWM
) = mBStartEdge
;
787 // We want to guarantee that we always make progress when
788 // formatting. Therefore, if the object being placed on the line is
789 // too big for the line, but it is the only thing on the line and is not
790 // impacted by a float, then we go ahead and place it anyway. (If the line
791 // is impacted by one or more floats, then it is safe to break because
792 // we can move the line down below float(s).)
794 // Capture this state *before* we reflow the frame in case it clears
795 // the state out. We need to know how to treat the current frame
797 bool notSafeToBreak
= LineIsEmpty() && !mImpactedByFloats
;
799 // Figure out whether we're talking about a textframe here
800 LayoutFrameType frameType
= aFrame
->Type();
801 const bool isText
= frameType
== LayoutFrameType::Text
;
803 // Inline-ish and text-ish things don't compute their width;
804 // everything else does. We need to give them an available width that
805 // reflects the space left on the line.
806 LAYOUT_WARN_IF_FALSE(psd
->mIEnd
!= NS_UNCONSTRAINEDSIZE
,
807 "have unconstrained width; this should only result from "
808 "very large sizes, not attempts at intrinsic width "
810 nscoord availableSpaceOnLine
= psd
->mIEnd
- psd
->mICoord
;
812 // Setup reflow input for reflowing the frame
813 Maybe
<ReflowInput
> reflowInputHolder
;
815 // Compute the available size for the frame. This available width
816 // includes room for the side margins.
817 // For now, set the available block-size to unconstrained always.
818 LogicalSize availSize
= mBlockReflowInput
->ComputedSize(frameWM
);
819 availSize
.BSize(frameWM
) = NS_UNCONSTRAINEDSIZE
;
820 reflowInputHolder
.emplace(mPresContext
, *psd
->mReflowInput
, aFrame
,
822 ReflowInput
& reflowInput
= *reflowInputHolder
;
823 reflowInput
.mLineLayout
= this;
824 reflowInput
.mFlags
.mIsTopOfPage
= mIsTopOfPage
;
825 if (reflowInput
.ComputedISize() == NS_UNCONSTRAINEDSIZE
) {
826 reflowInput
.AvailableISize() = availableSpaceOnLine
;
828 pfd
->mMargin
= reflowInput
.ComputedLogicalMargin(lineWM
);
829 pfd
->mBorderPadding
= reflowInput
.ComputedLogicalBorderPadding(lineWM
);
831 reflowInput
.mStyleDisplay
->IsRelativelyPositionedStyle();
832 if (pfd
->mRelativePos
) {
833 pfd
->mOffsets
= reflowInput
.ComputedLogicalOffsets(frameWM
);
836 // Calculate whether the the frame should have a start margin and
837 // subtract the margin from the available width if necessary.
838 // The margin will be applied to the starting inline coordinates of
839 // the frame in CanPlaceFrame() after reflowing the frame.
840 AllowForStartMargin(pfd
, reflowInput
);
842 // if isText(), no need to propagate NS_FRAME_IS_DIRTY from the parent,
843 // because reflow doesn't look at the dirty bits on the frame being reflowed.
845 // See if this frame depends on the inline-size of its containing block.
846 // If so, disable resize reflow optimizations for the line. (Note that,
847 // to be conservative, we do this if we *try* to fit a frame on a
848 // line, even if we don't succeed.) (Note also that we can only make
849 // this IsPercentageAware check *after* we've constructed our
850 // ReflowInput, because that construction may be what forces aFrame
851 // to lazily initialize its (possibly-percent-valued) intrinsic size.)
852 if (mGotLineBox
&& IsPercentageAware(aFrame
, lineWM
)) {
853 mLineBox
->DisableResizeReflowOptimization();
856 // Note that we don't bother positioning the frame yet, because we're probably
857 // going to end up moving it when we do the block-direction alignment.
859 // Adjust float manager coordinate system for the frame.
860 ReflowOutput
reflowOutput(lineWM
);
862 reflowOutput
.ISize(lineWM
) = nscoord(0xdeadbeef);
863 reflowOutput
.BSize(lineWM
) = nscoord(0xdeadbeef);
865 nscoord tI
= pfd
->mBounds
.LineLeft(lineWM
, ContainerSize());
866 nscoord tB
= pfd
->mBounds
.BStart(lineWM
);
867 mFloatManager
->Translate(tI
, tB
);
869 int32_t savedOptionalBreakOffset
;
870 gfxBreakPriority savedOptionalBreakPriority
;
871 nsIFrame
* savedOptionalBreakFrame
= GetLastOptionalBreakPosition(
872 &savedOptionalBreakOffset
, &savedOptionalBreakPriority
);
875 aFrame
->Reflow(mPresContext
, reflowOutput
, *reflowInputHolder
,
878 static_cast<nsTextFrame
*>(aFrame
)->ReflowText(
879 *this, availableSpaceOnLine
,
880 psd
->mReflowInput
->mRenderingContext
->GetDrawTarget(), reflowOutput
,
884 pfd
->mJustificationInfo
= mJustificationInfo
;
885 mJustificationInfo
= JustificationInfo();
887 // See if the frame is a placeholderFrame and if it is process
888 // the float. At the same time, check if the frame has any non-collapsed-away
890 bool placedFloat
= false;
892 if (frameType
== LayoutFrameType::None
) {
893 isEmpty
= pfd
->mFrame
->IsEmpty();
895 if (LayoutFrameType::Placeholder
== frameType
) {
897 pfd
->mIsPlaceholder
= true;
898 pfd
->mSkipWhenTrimmingWhitespace
= true;
899 nsIFrame
* outOfFlowFrame
= nsLayoutUtils::GetFloatFromPlaceholder(aFrame
);
900 if (outOfFlowFrame
) {
902 // We can always place floats in an empty line.
904 // We always place floating letter frames. This kinda sucks. They'd
905 // usually fall into the LineIsEmpty() check anyway, except when
906 // there's something like a ::marker before or what not. We actually
907 // need to place them now, because they're pretty nasty and they
908 // create continuations that are in flow and not a kid of the
909 // previous continuation's parent. We don't want the deferred reflow
910 // of the letter frame to kill a continuation after we've stored it
911 // in the line layout data structures. See bug 1490281 to fix the
912 // underlying issue. When that's fixed this check should be removed.
913 !outOfFlowFrame
->IsLetterFrame() &&
914 !GetOutermostLineLayout()
915 ->mBlockRI
->mFlags
.mCanHaveOverflowMarkers
) {
916 // We'll do this at the next break opportunity.
917 RecordNoWrapFloat(outOfFlowFrame
);
919 placedFloat
= TryToPlaceFloat(outOfFlowFrame
);
923 // Note non-empty text-frames for inline frame compatibility hackery
924 pfd
->mIsTextFrame
= true;
925 nsTextFrame
* textFrame
= static_cast<nsTextFrame
*>(pfd
->mFrame
);
926 isEmpty
= !textFrame
->HasNoncollapsedCharacters();
928 pfd
->mIsNonEmptyTextFrame
= true;
929 nsIContent
* content
= textFrame
->GetContent();
931 const nsTextFragment
* frag
= content
->GetText();
933 pfd
->mIsNonWhitespaceTextFrame
= !content
->TextIsOnlyWhitespace();
936 } else if (LayoutFrameType::Br
== frameType
) {
937 pfd
->mSkipWhenTrimmingWhitespace
= true;
940 if (LayoutFrameType::Letter
== frameType
) {
941 pfd
->mIsLetterFrame
= true;
945 !pfd
->mSpan
->mHasNonemptyContent
&& pfd
->mFrame
->IsSelfEmpty();
947 isEmpty
= pfd
->mFrame
->IsEmpty();
951 pfd
->mIsEmpty
= isEmpty
;
953 mFloatManager
->Translate(-tI
, -tB
);
955 NS_ASSERTION(reflowOutput
.ISize(lineWM
) >= 0, "bad inline size");
956 NS_ASSERTION(reflowOutput
.BSize(lineWM
) >= 0, "bad block size");
957 if (reflowOutput
.ISize(lineWM
) < 0) {
958 reflowOutput
.ISize(lineWM
) = 0;
960 if (reflowOutput
.BSize(lineWM
) < 0) {
961 reflowOutput
.BSize(lineWM
) = 0;
965 // Note: break-before means ignore the reflow metrics since the
966 // frame will be reflowed another time.
967 if (!aReflowStatus
.IsInlineBreakBefore()) {
968 if ((ABSURD_SIZE(reflowOutput
.ISize(lineWM
)) ||
969 ABSURD_SIZE(reflowOutput
.BSize(lineWM
))) &&
970 !LineContainerFrame()->GetParent()->IsAbsurdSizeAssertSuppressed()) {
971 printf("nsLineLayout: ");
972 aFrame
->ListTag(stdout
);
973 printf(" metrics=%d,%d!\n", reflowOutput
.Width(), reflowOutput
.Height());
975 if ((reflowOutput
.Width() == nscoord(0xdeadbeef)) ||
976 (reflowOutput
.Height() == nscoord(0xdeadbeef))) {
977 printf("nsLineLayout: ");
978 aFrame
->ListTag(stdout
);
979 printf(" didn't set w/h %d,%d!\n", reflowOutput
.Width(),
980 reflowOutput
.Height());
985 // Unlike with non-inline reflow, the overflow area here does *not*
986 // include the accumulation of the frame's bounds and its inline
987 // descendants' bounds. Nor does it include the outline area; it's
988 // just the union of the bounds of any absolute children. That is
989 // added in later by nsLineLayout::ReflowInlineFrames.
990 pfd
->mOverflowAreas
= reflowOutput
.mOverflowAreas
;
992 pfd
->mBounds
.ISize(lineWM
) = reflowOutput
.ISize(lineWM
);
993 pfd
->mBounds
.BSize(lineWM
) = reflowOutput
.BSize(lineWM
);
995 // Size the frame, but |RelativePositionFrames| will size the view.
996 aFrame
->SetRect(lineWM
, pfd
->mBounds
, ContainerSizeForSpan(psd
));
998 // Tell the frame that we're done reflowing it
999 aFrame
->DidReflow(mPresContext
, isText
? nullptr : reflowInputHolder
.ptr());
1002 *aMetrics
= reflowOutput
;
1005 if (!aReflowStatus
.IsInlineBreakBefore()) {
1006 // If frame is complete and has a next-in-flow, we need to delete
1007 // them now. Do not do this when a break-before is signaled because
1008 // the frame is going to get reflowed again (and may end up wanting
1009 // a next-in-flow where it ends up).
1010 if (aReflowStatus
.IsComplete()) {
1011 nsIFrame
* kidNextInFlow
= aFrame
->GetNextInFlow();
1012 if (nullptr != kidNextInFlow
) {
1013 // Remove all of the childs next-in-flows. Make sure that we ask
1014 // the right parent to do the removal (it's possible that the
1015 // parent is not this because we are executing pullup code)
1016 kidNextInFlow
->GetParent()->DeleteNextInFlowChild(kidNextInFlow
, true);
1020 // Check whether this frame breaks up text runs. All frames break up text
1021 // runs (hence return false here) except for text frames and inline
1023 bool continuingTextRun
= aFrame
->CanContinueTextRun();
1025 // Clear any residual mTrimmableISize if this isn't a text frame
1026 if (!continuingTextRun
&& !pfd
->mSkipWhenTrimmingWhitespace
) {
1027 mTrimmableISize
= 0;
1030 // See if we can place the frame. If we can't fit it, then we
1032 bool optionalBreakAfterFits
;
1033 NS_ASSERTION(isText
|| !reflowInputHolder
->mStyleDisplay
->IsFloating(
1034 reflowInputHolder
->mFrame
),
1035 "How'd we get a floated inline frame? "
1036 "The frame ctor should've dealt with this.");
1037 if (CanPlaceFrame(pfd
, notSafeToBreak
, continuingTextRun
,
1038 savedOptionalBreakFrame
!= nullptr, reflowOutput
,
1039 aReflowStatus
, &optionalBreakAfterFits
)) {
1041 psd
->mHasNonemptyContent
= true;
1042 mLineIsEmpty
= false;
1044 // nonempty leaf content has been placed
1045 mLineAtStart
= false;
1047 if (LayoutFrameType::Ruby
== frameType
) {
1049 SyncAnnotationBounds(pfd
);
1053 // Place the frame, updating aBounds with the final size and
1054 // location. Then apply the bottom+right margins (as
1055 // appropriate) to the frame.
1056 PlaceFrame(pfd
, reflowOutput
);
1057 PerSpanData
* span
= pfd
->mSpan
;
1059 // The frame we just finished reflowing is an inline
1060 // container. It needs its child frames aligned in the block direction,
1061 // so do most of it now.
1062 VerticalAlignFrames(span
);
1065 if (!continuingTextRun
&& !psd
->mNoWrap
) {
1066 if (!LineIsEmpty() || placedFloat
) {
1067 // record soft break opportunity after this content that can't be
1068 // part of a text run. This is not a text frame so we know
1069 // that offset INT32_MAX means "after the content".
1070 if ((!aFrame
->IsPlaceholderFrame() || LineIsEmpty()) &&
1071 NotifyOptionalBreakPosition(aFrame
, INT32_MAX
,
1072 optionalBreakAfterFits
,
1073 gfxBreakPriority::eNormalBreak
)) {
1074 // If this returns true then we are being told to actually break
1076 aReflowStatus
.SetInlineLineBreakAfter();
1082 aPushedFrame
= true;
1083 // Undo any saved break positions that the frame might have told us about,
1084 // since we didn't end up placing it
1085 RestoreSavedBreakPosition(savedOptionalBreakFrame
,
1086 savedOptionalBreakOffset
,
1087 savedOptionalBreakPriority
);
1091 aPushedFrame
= true;
1094 #ifdef REALLY_NOISY_REFLOW
1095 nsIFrame::IndentBy(stdout
, mSpanDepth
);
1096 printf("End ReflowFrame ");
1097 aFrame
->ListTag(stdout
);
1098 printf(" status=%x\n", aReflowStatus
);
1102 void nsLineLayout::AllowForStartMargin(PerFrameData
* pfd
,
1103 ReflowInput
& aReflowInput
) {
1104 NS_ASSERTION(!aReflowInput
.mStyleDisplay
->IsFloating(aReflowInput
.mFrame
),
1105 "How'd we get a floated inline frame? "
1106 "The frame ctor should've dealt with this.");
1108 WritingMode lineWM
= mRootSpan
->mWritingMode
;
1110 // Only apply start-margin on the first-in flow for inline frames,
1111 // and make sure to not apply it to any inline other than the first
1112 // in an ib split. Note that the ib sibling (block-in-inline
1113 // sibling) annotations only live on the first continuation, but we
1114 // don't want to apply the start margin for later continuations
1115 // anyway. For box-decoration-break:clone we apply the start-margin
1116 // on all continuations.
1117 if ((pfd
->mFrame
->GetPrevContinuation() ||
1118 pfd
->mFrame
->FrameIsNonFirstInIBSplit()) &&
1119 aReflowInput
.mStyleBorder
->mBoxDecorationBreak
==
1120 StyleBoxDecorationBreak::Slice
) {
1121 // Zero this out so that when we compute the max-element-width of
1122 // the frame we will properly avoid adding in the starting margin.
1123 pfd
->mMargin
.IStart(lineWM
) = 0;
1124 } else if (NS_UNCONSTRAINEDSIZE
== aReflowInput
.ComputedISize()) {
1125 NS_WARNING_ASSERTION(
1126 NS_UNCONSTRAINEDSIZE
!= aReflowInput
.AvailableISize(),
1127 "have unconstrained inline-size; this should only result from very "
1128 "large sizes, not attempts at intrinsic inline-size calculation");
1129 // For inline-ish and text-ish things (which don't compute widths
1130 // in the reflow input), adjust available inline-size to account
1131 // for the start margin. The end margin will be accounted for when
1132 // we finish flowing the frame.
1133 WritingMode wm
= aReflowInput
.GetWritingMode();
1134 aReflowInput
.AvailableISize() -=
1135 pfd
->mMargin
.ConvertTo(wm
, lineWM
).IStart(wm
);
1139 nscoord
nsLineLayout::GetCurrentFrameInlineDistanceFromBlock() {
1142 for (psd
= mCurrentSpan
; psd
; psd
= psd
->mParent
) {
1149 * This method syncs bounds of ruby annotations and ruby annotation
1150 * containers from their rect. It is necessary because:
1151 * Containers are not part of the line in their levels, which means
1152 * their bounds are not set properly before.
1153 * Ruby annotations' position may have been changed when reflowing
1156 void nsLineLayout::SyncAnnotationBounds(PerFrameData
* aRubyFrame
) {
1157 MOZ_ASSERT(aRubyFrame
->mFrame
->IsRubyFrame());
1158 MOZ_ASSERT(aRubyFrame
->mSpan
);
1160 PerSpanData
* span
= aRubyFrame
->mSpan
;
1161 WritingMode lineWM
= mRootSpan
->mWritingMode
;
1162 for (PerFrameData
* pfd
= span
->mFirstFrame
; pfd
; pfd
= pfd
->mNext
) {
1163 for (PerFrameData
* rtc
= pfd
->mNextAnnotation
; rtc
;
1164 rtc
= rtc
->mNextAnnotation
) {
1165 if (lineWM
.IsOrthogonalTo(rtc
->mFrame
->GetWritingMode())) {
1166 // Inter-character case: don't attempt to sync annotation bounds.
1169 // When the annotation container is reflowed, the width of the
1170 // ruby container is unknown so we use a dummy container size;
1171 // in the case of RTL block direction, the final position will be
1173 const nsSize dummyContainerSize
;
1174 LogicalRect
rtcBounds(lineWM
, rtc
->mFrame
->GetRect(), dummyContainerSize
);
1175 rtc
->mBounds
= rtcBounds
;
1176 nsSize rtcSize
= rtcBounds
.Size(lineWM
).GetPhysicalSize(lineWM
);
1177 for (PerFrameData
* rt
= rtc
->mSpan
->mFirstFrame
; rt
; rt
= rt
->mNext
) {
1178 LogicalRect rtBounds
= rt
->mFrame
->GetLogicalRect(lineWM
, rtcSize
);
1179 MOZ_ASSERT(rt
->mBounds
.Size(lineWM
) == rtBounds
.Size(lineWM
),
1180 "Size of the annotation should not have been changed");
1181 rt
->mBounds
.SetOrigin(lineWM
, rtBounds
.Origin(lineWM
));
1188 * See if the frame can be placed now that we know it's desired size.
1189 * We can always place the frame if the line is empty. Note that we
1190 * know that the reflow-status is not a break-before because if it was
1191 * ReflowFrame above would have returned false, preventing this method
1192 * from being called. The logic in this method assumes that.
1194 * Note that there is no check against the Y coordinate because we
1195 * assume that the caller will take care of that.
1197 bool nsLineLayout::CanPlaceFrame(PerFrameData
* pfd
, bool aNotSafeToBreak
,
1198 bool aFrameCanContinueTextRun
,
1199 bool aCanRollBackBeforeFrame
,
1200 ReflowOutput
& aMetrics
,
1201 nsReflowStatus
& aStatus
,
1202 bool* aOptionalBreakAfterFits
) {
1203 MOZ_ASSERT(pfd
&& pfd
->mFrame
, "bad args, null pointers for frame data");
1205 *aOptionalBreakAfterFits
= true;
1207 WritingMode lineWM
= mRootSpan
->mWritingMode
;
1209 * We want to only apply the end margin if we're the last continuation and
1210 * either not in an {ib} split or the last inline in it. In all other
1211 * cases we want to zero it out. That means zeroing it out if any of these
1213 * 1) The frame is not complete (in this case it will get a next-in-flow)
1214 * 2) The frame is complete but has a non-fluid continuation on its
1215 * continuation chain. Note that if it has a fluid continuation, that
1216 * continuation will get destroyed later, so we don't want to drop the
1217 * end-margin in that case.
1218 * 3) The frame is in an {ib} split and is not the last part.
1220 * However, none of that applies if this is a letter frame (XXXbz why?)
1222 * For box-decoration-break:clone we apply the end margin on all
1223 * continuations (that are not letter frames).
1225 if ((aStatus
.IsIncomplete() ||
1226 pfd
->mFrame
->LastInFlow()->GetNextContinuation() ||
1227 pfd
->mFrame
->FrameIsNonLastInIBSplit()) &&
1228 !pfd
->mIsLetterFrame
&&
1229 pfd
->mFrame
->StyleBorder()->mBoxDecorationBreak
==
1230 StyleBoxDecorationBreak::Slice
) {
1231 pfd
->mMargin
.IEnd(lineWM
) = 0;
1234 // Apply the start margin to the frame bounds.
1235 nscoord startMargin
= pfd
->mMargin
.IStart(lineWM
);
1236 nscoord endMargin
= pfd
->mMargin
.IEnd(lineWM
);
1238 pfd
->mBounds
.IStart(lineWM
) += startMargin
;
1240 PerSpanData
* psd
= mCurrentSpan
;
1242 // When wrapping is off, everything fits.
1246 #ifdef NOISY_CAN_PLACE_FRAME
1247 if (nullptr != psd
->mFrame
) {
1248 psd
->mFrame
->mFrame
->ListTag(stdout
);
1250 printf(": aNotSafeToBreak=%s frame=", aNotSafeToBreak
? "true" : "false");
1251 pfd
->mFrame
->ListTag(stdout
);
1252 printf(" frameWidth=%d, margins=%d,%d\n",
1253 pfd
->mBounds
.IEnd(lineWM
) + endMargin
- psd
->mICoord
, startMargin
,
1257 // Set outside to true if the result of the reflow leads to the
1258 // frame sticking outside of our available area.
1260 pfd
->mBounds
.IEnd(lineWM
) - mTrimmableISize
+ endMargin
> psd
->mIEnd
;
1262 // If it fits, it fits
1263 #ifdef NOISY_CAN_PLACE_FRAME
1264 printf(" ==> inside\n");
1268 *aOptionalBreakAfterFits
= false;
1270 // When it doesn't fit, check for a few special conditions where we
1271 // allow it to fit anyway.
1272 if (0 == startMargin
+ pfd
->mBounds
.ISize(lineWM
) + endMargin
) {
1273 // Empty frames always fit right where they are
1274 #ifdef NOISY_CAN_PLACE_FRAME
1275 printf(" ==> empty frame fits\n");
1280 // another special case: always place a BR
1281 if (pfd
->mFrame
->IsBrFrame()) {
1282 #ifdef NOISY_CAN_PLACE_FRAME
1283 printf(" ==> BR frame fits\n");
1288 if (aNotSafeToBreak
) {
1289 // There are no frames on the line that take up width and the line is
1290 // not impacted by floats, so we must allow the current frame to be
1291 // placed on the line
1292 #ifdef NOISY_CAN_PLACE_FRAME
1293 printf(" ==> not-safe and not-impacted fits: ");
1294 while (nullptr != psd
) {
1295 printf("<psd=%p x=%d left=%d> ", psd
, psd
->mICoord
, psd
->mIStart
);
1303 // Special check for span frames
1304 if (pfd
->mSpan
&& pfd
->mSpan
->mContainsFloat
) {
1305 // If the span either directly or indirectly contains a float then
1306 // it fits. Why? It's kind of complicated, but here goes:
1308 // 1. CanPlaceFrame is used for all frame placements on a line,
1309 // and in a span. This includes recursively placement of frames
1310 // inside of spans, and the span itself. Because the logic always
1311 // checks for room before proceeding (the code above here), the
1312 // only things on a line will be those things that "fit".
1314 // 2. Before a float is placed on a line, the line has to be empty
1315 // (otherwise it's a "below current line" float and will be placed
1318 // Therefore, if the span directly or indirectly has a float
1319 // then it means that at the time of the placement of the float
1320 // the line was empty. Because of #1, only the frames that fit can
1321 // be added after that point, therefore we can assume that the
1322 // current span being placed has fit.
1324 // So how do we get here and have a span that should already fit
1325 // and yet doesn't: Simple: span's that have the no-wrap attribute
1326 // set on them and contain a float and are placed where they
1327 // don't naturally fit.
1331 if (aFrameCanContinueTextRun
) {
1332 // Let it fit, but we reserve the right to roll back.
1333 // Note that we usually won't get here because a text frame will break
1334 // itself to avoid exceeding the available width.
1335 // We'll only get here for text frames that couldn't break early enough.
1336 #ifdef NOISY_CAN_PLACE_FRAME
1337 printf(" ==> placing overflowing textrun, requesting backup\n");
1340 // We will want to try backup.
1345 #ifdef NOISY_CAN_PLACE_FRAME
1346 printf(" ==> didn't fit\n");
1348 aStatus
.SetInlineLineBreakBeforeAndReset();
1353 * Place the frame. Update running counters.
1355 void nsLineLayout::PlaceFrame(PerFrameData
* pfd
, ReflowOutput
& aMetrics
) {
1356 WritingMode lineWM
= mRootSpan
->mWritingMode
;
1358 // If the frame's block direction does not match the line's, we can't use
1359 // its ascent; instead, treat it as a block with baseline at the block-end
1360 // edge (or block-begin in the case of an "inverted" line).
1361 if (pfd
->mWritingMode
.GetBlockDir() != lineWM
.GetBlockDir()) {
1362 pfd
->mAscent
= lineWM
.IsLineInverted() ? 0 : aMetrics
.BSize(lineWM
);
1364 if (aMetrics
.BlockStartAscent() == ReflowOutput::ASK_FOR_BASELINE
) {
1365 pfd
->mAscent
= pfd
->mFrame
->GetLogicalBaseline(lineWM
);
1367 pfd
->mAscent
= aMetrics
.BlockStartAscent();
1371 // Advance to next inline coordinate
1372 mCurrentSpan
->mICoord
= pfd
->mBounds
.IEnd(lineWM
) + pfd
->mMargin
.IEnd(lineWM
);
1374 // Count the number of non-placeholder frames on the line...
1375 if (pfd
->mFrame
->IsPlaceholderFrame()) {
1377 pfd
->mBounds
.ISize(lineWM
) == 0 && pfd
->mBounds
.BSize(lineWM
) == 0,
1378 "placeholders should have 0 width/height (checking "
1379 "placeholders were never counted by the old code in "
1382 mTotalPlacedFrames
++;
1386 void nsLineLayout::AddMarkerFrame(nsIFrame
* aFrame
,
1387 const ReflowOutput
& aMetrics
) {
1388 NS_ASSERTION(mCurrentSpan
== mRootSpan
, "bad linelayout user");
1389 NS_ASSERTION(mGotLineBox
, "must have line box");
1391 nsBlockFrame
* blockFrame
= do_QueryFrame(mBlockReflowInput
->mFrame
);
1392 MOZ_ASSERT(blockFrame
, "must be for block");
1393 if (!blockFrame
->MarkerIsEmpty()) {
1395 mLineBox
->SetHasMarker();
1398 WritingMode lineWM
= mRootSpan
->mWritingMode
;
1399 PerFrameData
* pfd
= NewPerFrameData(aFrame
);
1400 PerSpanData
* psd
= mRootSpan
;
1402 MOZ_ASSERT(psd
->mFirstFrame
, "adding marker to an empty line?");
1403 // Prepend the marker frame to the line.
1404 psd
->mFirstFrame
->mPrev
= pfd
;
1405 pfd
->mNext
= psd
->mFirstFrame
;
1406 psd
->mFirstFrame
= pfd
;
1408 pfd
->mIsMarker
= true;
1409 if (aMetrics
.BlockStartAscent() == ReflowOutput::ASK_FOR_BASELINE
) {
1410 pfd
->mAscent
= aFrame
->GetLogicalBaseline(lineWM
);
1412 pfd
->mAscent
= aMetrics
.BlockStartAscent();
1415 // Note: block-coord value will be updated during block-direction alignment
1416 pfd
->mBounds
= LogicalRect(lineWM
, aFrame
->GetRect(), ContainerSize());
1417 pfd
->mOverflowAreas
= aMetrics
.mOverflowAreas
;
1420 void nsLineLayout::RemoveMarkerFrame(nsIFrame
* aFrame
) {
1421 PerSpanData
* psd
= mCurrentSpan
;
1422 MOZ_ASSERT(psd
== mRootSpan
, "::marker on non-root span?");
1423 MOZ_ASSERT(psd
->mFirstFrame
->mFrame
== aFrame
,
1424 "::marker is not the first frame?");
1425 PerFrameData
* pfd
= psd
->mFirstFrame
;
1426 MOZ_ASSERT(pfd
!= psd
->mLastFrame
, "::marker is the only frame?");
1427 pfd
->mNext
->mPrev
= nullptr;
1428 psd
->mFirstFrame
= pfd
->mNext
;
1433 void nsLineLayout::DumpPerSpanData(PerSpanData
* psd
, int32_t aIndent
) {
1434 nsIFrame::IndentBy(stdout
, aIndent
);
1435 printf("%p: left=%d x=%d right=%d\n", static_cast<void*>(psd
), psd
->mIStart
,
1436 psd
->mICoord
, psd
->mIEnd
);
1437 PerFrameData
* pfd
= psd
->mFirstFrame
;
1438 while (nullptr != pfd
) {
1439 nsIFrame::IndentBy(stdout
, aIndent
+ 1);
1440 pfd
->mFrame
->ListTag(stdout
);
1442 pfd
->mBounds
.GetPhysicalRect(psd
->mWritingMode
, ContainerSize());
1443 printf(" %d,%d,%d,%d\n", rect
.x
, rect
.y
, rect
.width
, rect
.height
);
1445 DumpPerSpanData(pfd
->mSpan
, aIndent
+ 1);
1452 void nsLineLayout::RecordNoWrapFloat(nsIFrame
* aFloat
) {
1453 GetOutermostLineLayout()->mBlockRI
->mNoWrapFloats
.AppendElement(aFloat
);
1456 void nsLineLayout::FlushNoWrapFloats() {
1457 auto& noWrapFloats
= GetOutermostLineLayout()->mBlockRI
->mNoWrapFloats
;
1458 for (nsIFrame
* floatedFrame
: noWrapFloats
) {
1459 TryToPlaceFloat(floatedFrame
);
1461 noWrapFloats
.Clear();
1464 bool nsLineLayout::TryToPlaceFloat(nsIFrame
* aFloat
) {
1465 // Add mTrimmableISize to the available width since if the line ends here, the
1466 // width of the inline content will be reduced by mTrimmableISize.
1467 nscoord availableISize
=
1468 mCurrentSpan
->mIEnd
- (mCurrentSpan
->mICoord
- mTrimmableISize
);
1469 NS_ASSERTION(!(aFloat
->IsLetterFrame() && GetFirstLetterStyleOK()),
1470 "FirstLetterStyle set on line with floating first letter");
1471 return GetOutermostLineLayout()->AddFloat(aFloat
, availableISize
);
1474 bool nsLineLayout::NotifyOptionalBreakPosition(nsIFrame
* aFrame
,
1475 int32_t aOffset
, bool aFits
,
1476 gfxBreakPriority aPriority
) {
1477 NS_ASSERTION(!aFits
|| !mNeedBackup
,
1478 "Shouldn't be updating the break position with a break that fits"
1479 " after we've already flagged an overrun");
1480 MOZ_ASSERT(mCurrentSpan
, "Should be doing line layout");
1481 if (mCurrentSpan
->mNoWrap
) {
1482 FlushNoWrapFloats();
1485 // Remember the last break position that fits; if there was no break that fit,
1486 // just remember the first break
1487 if ((aFits
&& aPriority
>= mLastOptionalBreakPriority
) ||
1488 !mLastOptionalBreakFrame
) {
1489 mLastOptionalBreakFrame
= aFrame
;
1490 mLastOptionalBreakFrameOffset
= aOffset
;
1491 mLastOptionalBreakPriority
= aPriority
;
1493 return aFrame
&& mForceBreakFrame
== aFrame
&&
1494 mForceBreakFrameOffset
== aOffset
;
1497 #define VALIGN_OTHER 0
1498 #define VALIGN_TOP 1
1499 #define VALIGN_BOTTOM 2
1501 void nsLineLayout::VerticalAlignLine() {
1502 // Partially place the children of the block frame. The baseline for
1503 // this operation is set to zero so that the y coordinates for all
1504 // of the placed children will be relative to there.
1505 PerSpanData
* psd
= mRootSpan
;
1506 VerticalAlignFrames(psd
);
1508 // *** Note that comments here still use the anachronistic term
1509 // "line-height" when we really mean "size of the line in the block
1510 // direction", "vertical-align" when we really mean "alignment in
1511 // the block direction", and "top" and "bottom" when we really mean
1512 // "block start" and "block end". This is partly for brevity and
1513 // partly to retain the association with the CSS line-height and
1514 // vertical-align properties.
1516 // Compute the line-height. The line-height will be the larger of:
1518 // [1] maxBCoord - minBCoord (the distance between the first child's
1519 // block-start edge and the last child's block-end edge)
1521 // [2] the maximum logical box block size (since not every frame may have
1522 // participated in #1; for example: "top" and "botttom" aligned frames)
1524 // [3] the minimum line height ("line-height" property set on the
1526 nscoord lineBSize
= psd
->mMaxBCoord
- psd
->mMinBCoord
;
1528 // Now that the line-height is computed, we need to know where the
1529 // baseline is in the line. Position baseline so that mMinBCoord is just
1530 // inside the start of the line box.
1531 nscoord baselineBCoord
;
1532 if (psd
->mMinBCoord
< 0) {
1533 baselineBCoord
= mBStartEdge
- psd
->mMinBCoord
;
1535 baselineBCoord
= mBStartEdge
;
1538 // It's also possible that the line block-size isn't tall enough because
1539 // of "top" and "bottom" aligned elements that were not accounted for in
1542 // The CSS2 spec doesn't really say what happens when to the
1543 // baseline in this situations. What we do is if the largest start
1544 // aligned box block size is greater than the line block-size then we leave
1545 // the baseline alone. If the largest end aligned box is greater
1546 // than the line block-size then we slide the baseline forward by the extra
1549 // Navigator 4 gives precedence to the first top/bottom aligned
1550 // object. We just let block end aligned objects win.
1551 if (lineBSize
< mMaxEndBoxBSize
) {
1552 // When the line is shorter than the maximum block start aligned box
1553 nscoord extra
= mMaxEndBoxBSize
- lineBSize
;
1554 baselineBCoord
+= extra
;
1555 lineBSize
= mMaxEndBoxBSize
;
1557 if (lineBSize
< mMaxStartBoxBSize
) {
1558 lineBSize
= mMaxStartBoxBSize
;
1560 #ifdef NOISY_BLOCKDIR_ALIGN
1561 printf(" [line]==> lineBSize=%d baselineBCoord=%d\n", lineBSize
,
1565 // Now position all of the frames in the root span. We will also
1566 // recurse over the child spans and place any frames we find with
1567 // vertical-align: top or bottom.
1568 // XXX PERFORMANCE: set a bit per-span to avoid the extra work
1569 // (propagate it upward too)
1570 WritingMode lineWM
= psd
->mWritingMode
;
1571 for (PerFrameData
* pfd
= psd
->mFirstFrame
; pfd
; pfd
= pfd
->mNext
) {
1572 if (pfd
->mBlockDirAlign
== VALIGN_OTHER
) {
1573 pfd
->mBounds
.BStart(lineWM
) += baselineBCoord
;
1574 pfd
->mFrame
->SetRect(lineWM
, pfd
->mBounds
, ContainerSize());
1577 PlaceTopBottomFrames(psd
, -mBStartEdge
, lineBSize
);
1579 mFinalLineBSize
= lineBSize
;
1581 // Fill in returned line-box and max-element-width data
1582 mLineBox
->SetBounds(lineWM
, psd
->mIStart
, mBStartEdge
,
1583 psd
->mICoord
- psd
->mIStart
, lineBSize
,
1586 mLineBox
->SetLogicalAscent(baselineBCoord
- mBStartEdge
);
1587 #ifdef NOISY_BLOCKDIR_ALIGN
1588 printf(" [line]==> bounds{x,y,w,h}={%d,%d,%d,%d} lh=%d a=%d\n",
1589 mLineBox
->GetBounds().IStart(lineWM
),
1590 mLineBox
->GetBounds().BStart(lineWM
),
1591 mLineBox
->GetBounds().ISize(lineWM
),
1592 mLineBox
->GetBounds().BSize(lineWM
), mFinalLineBSize
,
1593 mLineBox
->GetLogicalAscent());
1598 // Place frames with CSS property vertical-align: top or bottom.
1599 void nsLineLayout::PlaceTopBottomFrames(PerSpanData
* psd
,
1600 nscoord aDistanceFromStart
,
1601 nscoord aLineBSize
) {
1602 for (PerFrameData
* pfd
= psd
->mFirstFrame
; pfd
; pfd
= pfd
->mNext
) {
1603 PerSpanData
* span
= pfd
->mSpan
;
1605 NS_ASSERTION(0xFF != pfd
->mBlockDirAlign
, "umr");
1607 WritingMode lineWM
= mRootSpan
->mWritingMode
;
1608 nsSize containerSize
= ContainerSizeForSpan(psd
);
1609 switch (pfd
->mBlockDirAlign
) {
1612 pfd
->mBounds
.BStart(lineWM
) = -aDistanceFromStart
- span
->mMinBCoord
;
1614 pfd
->mBounds
.BStart(lineWM
) =
1615 -aDistanceFromStart
+ pfd
->mMargin
.BStart(lineWM
);
1617 pfd
->mFrame
->SetRect(lineWM
, pfd
->mBounds
, containerSize
);
1618 #ifdef NOISY_BLOCKDIR_ALIGN
1620 pfd
->mFrame
->ListTag(stdout
);
1621 printf(": y=%d dTop=%d [bp.top=%d topLeading=%d]\n",
1622 pfd
->mBounds
.BStart(lineWM
), aDistanceFromStart
,
1623 span
? pfd
->mBorderPadding
.BStart(lineWM
) : 0,
1624 span
? span
->mBStartLeading
: 0);
1629 // Compute bottom leading
1630 pfd
->mBounds
.BStart(lineWM
) =
1631 -aDistanceFromStart
+ aLineBSize
- span
->mMaxBCoord
;
1633 pfd
->mBounds
.BStart(lineWM
) = -aDistanceFromStart
+ aLineBSize
-
1634 pfd
->mMargin
.BEnd(lineWM
) -
1635 pfd
->mBounds
.BSize(lineWM
);
1637 pfd
->mFrame
->SetRect(lineWM
, pfd
->mBounds
, containerSize
);
1638 #ifdef NOISY_BLOCKDIR_ALIGN
1640 pfd
->mFrame
->ListTag(stdout
);
1641 printf(": y=%d\n", pfd
->mBounds
.BStart(lineWM
));
1646 nscoord fromStart
= aDistanceFromStart
+ pfd
->mBounds
.BStart(lineWM
);
1647 PlaceTopBottomFrames(span
, fromStart
, aLineBSize
);
1652 static nscoord
GetBSizeOfEmphasisMarks(nsIFrame
* aSpanFrame
, float aInflation
) {
1653 RefPtr
<nsFontMetrics
> fm
= nsLayoutUtils::GetFontMetricsOfEmphasisMarks(
1654 aSpanFrame
->Style(), aSpanFrame
->PresContext(), aInflation
);
1655 return fm
->MaxHeight();
1658 void nsLineLayout::AdjustLeadings(nsIFrame
* spanFrame
, PerSpanData
* psd
,
1659 const nsStyleText
* aStyleText
,
1661 bool* aZeroEffectiveSpanBox
) {
1662 MOZ_ASSERT(spanFrame
== psd
->mFrame
->mFrame
);
1663 nscoord requiredStartLeading
= 0;
1664 nscoord requiredEndLeading
= 0;
1665 if (spanFrame
->IsRubyFrame()) {
1666 // We may need to extend leadings here for ruby annotations as
1667 // required by section Line Spacing in the CSS Ruby spec.
1668 // See http://dev.w3.org/csswg/css-ruby/#line-height
1669 auto rubyFrame
= static_cast<nsRubyFrame
*>(spanFrame
);
1670 RubyBlockLeadings leadings
= rubyFrame
->GetBlockLeadings();
1671 requiredStartLeading
+= leadings
.mStart
;
1672 requiredEndLeading
+= leadings
.mEnd
;
1674 if (aStyleText
->HasEffectiveTextEmphasis()) {
1675 nscoord bsize
= GetBSizeOfEmphasisMarks(spanFrame
, aInflation
);
1676 LogicalSide side
= aStyleText
->TextEmphasisSide(mRootSpan
->mWritingMode
);
1677 if (side
== eLogicalSideBStart
) {
1678 requiredStartLeading
+= bsize
;
1680 MOZ_ASSERT(side
== eLogicalSideBEnd
,
1681 "emphasis marks must be in block axis");
1682 requiredEndLeading
+= bsize
;
1686 nscoord requiredLeading
= requiredStartLeading
+ requiredEndLeading
;
1687 // If we do not require any additional leadings, don't touch anything
1688 // here even if it is greater than the original leading, because the
1689 // latter could be negative.
1690 if (requiredLeading
!= 0) {
1691 nscoord leading
= psd
->mBStartLeading
+ psd
->mBEndLeading
;
1692 nscoord deltaLeading
= requiredLeading
- leading
;
1693 if (deltaLeading
> 0) {
1694 // If the total leading is not wide enough for ruby annotations
1695 // and/or emphasis marks, extend the side which is not enough. If
1696 // both sides are not wide enough, replace the leadings with the
1697 // requested values.
1698 if (requiredStartLeading
< psd
->mBStartLeading
) {
1699 psd
->mBEndLeading
+= deltaLeading
;
1700 } else if (requiredEndLeading
< psd
->mBEndLeading
) {
1701 psd
->mBStartLeading
+= deltaLeading
;
1703 psd
->mBStartLeading
= requiredStartLeading
;
1704 psd
->mBEndLeading
= requiredEndLeading
;
1706 psd
->mLogicalBSize
+= deltaLeading
;
1707 // We have adjusted the leadings, it is no longer a zero
1708 // effective span box.
1709 *aZeroEffectiveSpanBox
= false;
1714 static float GetInflationForBlockDirAlignment(nsIFrame
* aFrame
,
1715 nscoord aInflationMinFontSize
) {
1716 if (SVGUtils::IsInSVGTextSubtree(aFrame
)) {
1717 const nsIFrame
* container
=
1718 nsLayoutUtils::GetClosestFrameOfType(aFrame
, LayoutFrameType::SVGText
);
1719 NS_ASSERTION(container
, "expected to find an ancestor SVGTextFrame");
1720 return static_cast<const SVGTextFrame
*>(container
)
1721 ->GetFontSizeScaleFactor();
1723 return nsLayoutUtils::FontSizeInflationInner(aFrame
, aInflationMinFontSize
);
1726 #define BLOCKDIR_ALIGN_FRAMES_NO_MINIMUM nscoord_MAX
1727 #define BLOCKDIR_ALIGN_FRAMES_NO_MAXIMUM nscoord_MIN
1729 // Place frames in the block direction within a given span (CSS property
1730 // vertical-align) Note: this doesn't place frames with vertical-align:
1731 // top or bottom as those have to wait until the entire line box block
1732 // size is known. This is called after the span frame has finished being
1733 // reflowed so that we know its block size.
1734 void nsLineLayout::VerticalAlignFrames(PerSpanData
* psd
) {
1735 // Get parent frame info
1736 PerFrameData
* spanFramePFD
= psd
->mFrame
;
1737 nsIFrame
* spanFrame
= spanFramePFD
->mFrame
;
1739 // Get the parent frame's font for all of the frames in this span
1741 GetInflationForBlockDirAlignment(spanFrame
, mInflationMinFontSize
);
1742 RefPtr
<nsFontMetrics
> fm
=
1743 nsLayoutUtils::GetFontMetricsForFrame(spanFrame
, inflation
);
1745 bool preMode
= mStyleText
->WhiteSpaceIsSignificant();
1747 // See if the span is an empty continuation. It's an empty continuation iff:
1748 // - it has a prev-in-flow
1749 // - it has no next in flow
1750 // - it's zero sized
1751 WritingMode lineWM
= mRootSpan
->mWritingMode
;
1752 bool emptyContinuation
= psd
!= mRootSpan
&& spanFrame
->GetPrevInFlow() &&
1753 !spanFrame
->GetNextInFlow() &&
1754 spanFramePFD
->mBounds
.IsZeroSize();
1756 #ifdef NOISY_BLOCKDIR_ALIGN
1757 printf("[%sSpan]", (psd
== mRootSpan
) ? "Root" : "");
1758 spanFrame
->ListTag(stdout
);
1759 printf(": preMode=%s strictMode=%s w/h=%d,%d emptyContinuation=%s",
1760 preMode
? "yes" : "no",
1761 mPresContext
->CompatibilityMode() != eCompatibility_NavQuirks
? "yes"
1763 spanFramePFD
->mBounds
.ISize(lineWM
),
1764 spanFramePFD
->mBounds
.BSize(lineWM
), emptyContinuation
? "yes" : "no");
1765 if (psd
!= mRootSpan
) {
1766 printf(" bp=%d,%d,%d,%d margin=%d,%d,%d,%d",
1767 spanFramePFD
->mBorderPadding
.Top(lineWM
),
1768 spanFramePFD
->mBorderPadding
.Right(lineWM
),
1769 spanFramePFD
->mBorderPadding
.Bottom(lineWM
),
1770 spanFramePFD
->mBorderPadding
.Left(lineWM
),
1771 spanFramePFD
->mMargin
.Top(lineWM
),
1772 spanFramePFD
->mMargin
.Right(lineWM
),
1773 spanFramePFD
->mMargin
.Bottom(lineWM
),
1774 spanFramePFD
->mMargin
.Left(lineWM
));
1779 // Compute the span's zeroEffectiveSpanBox flag. What we are trying
1780 // to determine is how we should treat the span: should it act
1781 // "normally" according to css2 or should it effectively
1784 // In general, if the document being processed is in full standards
1785 // mode then it should act normally (with one exception). The
1786 // exception case is when a span is continued and yet the span is
1787 // empty (e.g. compressed whitespace). For this kind of span we treat
1788 // it as if it were not there so that it doesn't impact the
1791 // In almost standards mode or quirks mode, we should sometimes make
1792 // it disappear. The cases that matter are those where the span
1793 // contains no real text elements that would provide an ascent and
1794 // descent and height. However, if css style elements have been
1795 // applied to the span (border/padding/margin) so that it's clear the
1796 // document author is intending css2 behavior then we act as if strict
1799 // This code works correctly for preMode, because a blank line
1800 // in PRE mode is encoded as a text node with a LF in it, since
1801 // text nodes with only whitespace are considered in preMode.
1803 // Much of this logic is shared with the various implementations of
1804 // nsIFrame::IsEmpty since they need to duplicate the way it makes
1805 // some lines empty. However, nsIFrame::IsEmpty can't be reused here
1806 // since this code sets zeroEffectiveSpanBox even when there are
1807 // non-empty children.
1808 bool zeroEffectiveSpanBox
= false;
1809 // XXXldb If we really have empty continuations, then all these other
1810 // checks don't make sense for them.
1811 // XXXldb This should probably just use nsIFrame::IsSelfEmpty, assuming that
1812 // it agrees with this code. (If it doesn't agree, it probably should.)
1813 if ((emptyContinuation
||
1814 mPresContext
->CompatibilityMode() != eCompatibility_FullStandards
) &&
1815 ((psd
== mRootSpan
) || (spanFramePFD
->mBorderPadding
.IsAllZero() &&
1816 spanFramePFD
->mMargin
.IsAllZero()))) {
1817 // This code handles an issue with compatibility with non-css
1818 // conformant browsers. In particular, there are some cases
1819 // where the font-size and line-height for a span must be
1820 // ignored and instead the span must *act* as if it were zero
1821 // sized. In general, if the span contains any non-compressed
1822 // text then we don't use this logic.
1823 // However, this is not propagated outwards, since (in compatibility
1824 // mode) we don't want big line heights for things like
1825 // <p><font size="-1">Text</font></p>
1827 // We shouldn't include any whitespace that collapses, unless we're
1828 // preformatted (in which case it shouldn't, but the width=0 test is
1829 // perhaps incorrect). This includes whitespace at the beginning of
1830 // a line and whitespace preceded (?) by other whitespace.
1831 // See bug 134580 and bug 155333.
1832 zeroEffectiveSpanBox
= true;
1833 for (PerFrameData
* pfd
= psd
->mFirstFrame
; pfd
; pfd
= pfd
->mNext
) {
1834 if (pfd
->mIsTextFrame
&&
1835 (pfd
->mIsNonWhitespaceTextFrame
|| preMode
||
1836 pfd
->mBounds
.ISize(mRootSpan
->mWritingMode
) != 0)) {
1837 zeroEffectiveSpanBox
= false;
1843 // Setup baselineBCoord, minBCoord, and maxBCoord
1844 nscoord baselineBCoord
, minBCoord
, maxBCoord
;
1845 if (psd
== mRootSpan
) {
1846 // Use a zero baselineBCoord since we don't yet know where the baseline
1847 // will be (until we know how tall the line is; then we will
1848 // know). In addition, use extreme values for the minBCoord and maxBCoord
1849 // values so that only the child frames will impact their values
1850 // (since these are children of the block, there is no span box to
1851 // provide initial values).
1853 minBCoord
= BLOCKDIR_ALIGN_FRAMES_NO_MINIMUM
;
1854 maxBCoord
= BLOCKDIR_ALIGN_FRAMES_NO_MAXIMUM
;
1855 #ifdef NOISY_BLOCKDIR_ALIGN
1856 printf("[RootSpan]");
1857 spanFrame
->ListTag(stdout
);
1859 ": pass1 valign frames: topEdge=%d minLineBSize=%d "
1860 "zeroEffectiveSpanBox=%s\n",
1861 mBStartEdge
, mMinLineBSize
, zeroEffectiveSpanBox
? "yes" : "no");
1864 // Compute the logical block size for this span. The logical block size
1865 // is based on the "line-height" value, not the font-size. Also
1866 // compute the top leading.
1868 GetInflationForBlockDirAlignment(spanFrame
, mInflationMinFontSize
);
1869 nscoord logicalBSize
= ReflowInput::CalcLineHeight(
1870 spanFrame
->GetContent(), spanFrame
->Style(), spanFrame
->PresContext(),
1871 mBlockReflowInput
->ComputedHeight(), inflation
);
1872 nscoord contentBSize
= spanFramePFD
->mBounds
.BSize(lineWM
) -
1873 spanFramePFD
->mBorderPadding
.BStartEnd(lineWM
);
1875 // Special-case for a ::first-letter frame, set the line height to
1876 // the frame block size if the user has left line-height == normal
1877 const nsStyleText
* styleText
= spanFrame
->StyleText();
1878 if (spanFramePFD
->mIsLetterFrame
&& !spanFrame
->GetPrevInFlow() &&
1879 styleText
->mLineHeight
.IsNormal()) {
1880 logicalBSize
= spanFramePFD
->mBounds
.BSize(lineWM
);
1883 nscoord leading
= logicalBSize
- contentBSize
;
1884 psd
->mBStartLeading
= leading
/ 2;
1885 psd
->mBEndLeading
= leading
- psd
->mBStartLeading
;
1886 psd
->mLogicalBSize
= logicalBSize
;
1887 AdjustLeadings(spanFrame
, psd
, styleText
, inflation
, &zeroEffectiveSpanBox
);
1889 if (zeroEffectiveSpanBox
) {
1890 // When the span-box is to be ignored, zero out the initial
1891 // values so that the span doesn't impact the final line
1892 // height. The contents of the span can impact the final line
1895 // Note that things are readjusted for this span after its children
1897 minBCoord
= BLOCKDIR_ALIGN_FRAMES_NO_MINIMUM
;
1898 maxBCoord
= BLOCKDIR_ALIGN_FRAMES_NO_MAXIMUM
;
1900 // The initial values for the min and max block coord values are in the
1901 // span's coordinate space, and cover the logical block size of the span.
1902 // If there are child frames in this span that stick out of this area
1903 // then the minBCoord and maxBCoord are updated by the amount of logical
1904 // blockSize that is outside this range.
1906 spanFramePFD
->mBorderPadding
.BStart(lineWM
) - psd
->mBStartLeading
;
1907 maxBCoord
= minBCoord
+ psd
->mLogicalBSize
;
1910 // This is the distance from the top edge of the parents visual
1911 // box to the baseline. The span already computed this for us,
1913 *psd
->mBaseline
= baselineBCoord
= spanFramePFD
->mAscent
;
1915 #ifdef NOISY_BLOCKDIR_ALIGN
1916 printf("[%sSpan]", (psd
== mRootSpan
) ? "Root" : "");
1917 spanFrame
->ListTag(stdout
);
1919 ": baseLine=%d logicalBSize=%d topLeading=%d h=%d bp=%d,%d "
1920 "zeroEffectiveSpanBox=%s\n",
1921 baselineBCoord
, psd
->mLogicalBSize
, psd
->mBStartLeading
,
1922 spanFramePFD
->mBounds
.BSize(lineWM
),
1923 spanFramePFD
->mBorderPadding
.Top(lineWM
),
1924 spanFramePFD
->mBorderPadding
.Bottom(lineWM
),
1925 zeroEffectiveSpanBox
? "yes" : "no");
1929 nscoord maxStartBoxBSize
= 0;
1930 nscoord maxEndBoxBSize
= 0;
1931 PerFrameData
* pfd
= psd
->mFirstFrame
;
1932 while (nullptr != pfd
) {
1933 nsIFrame
* frame
= pfd
->mFrame
;
1935 // sanity check (see bug 105168, non-reproducible crashes from null frame)
1937 "null frame in PerFrameData - something is very very bad");
1942 // Compute the logical block size of the frame
1943 nscoord logicalBSize
;
1944 PerSpanData
* frameSpan
= pfd
->mSpan
;
1946 // For span frames the logical-block-size and start-leading were
1947 // pre-computed when the span was reflowed.
1948 logicalBSize
= frameSpan
->mLogicalBSize
;
1950 // For other elements the logical block size is the same as the
1951 // frame's block size plus its margins.
1953 pfd
->mBounds
.BSize(lineWM
) + pfd
->mMargin
.BStartEnd(lineWM
);
1954 if (logicalBSize
< 0 &&
1955 mPresContext
->CompatibilityMode() != eCompatibility_FullStandards
) {
1956 pfd
->mAscent
-= logicalBSize
;
1961 // Get vertical-align property ("vertical-align" is the CSS name for
1962 // block-direction align)
1963 const auto& verticalAlign
= frame
->StyleDisplay()->mVerticalAlign
;
1964 Maybe
<StyleVerticalAlignKeyword
> verticalAlignEnum
=
1965 frame
->VerticalAlignEnum();
1966 #ifdef NOISY_BLOCKDIR_ALIGN
1968 frame
->ListTag(stdout
);
1969 printf(": verticalAlignIsKw=%d (enum == %d", verticalAlign
.IsKeyword(),
1970 verticalAlign
.IsKeyword()
1971 ? static_cast<int>(verticalAlign
.AsKeyword())
1973 if (verticalAlignEnum
) {
1974 printf(", after SVG dominant-baseline conversion == %d",
1975 static_cast<int>(*verticalAlignEnum
));
1980 if (verticalAlignEnum
) {
1981 StyleVerticalAlignKeyword keyword
= *verticalAlignEnum
;
1982 if (lineWM
.IsVertical()) {
1983 if (keyword
== StyleVerticalAlignKeyword::Middle
) {
1984 // For vertical writing mode where the dominant baseline is centered
1985 // (i.e. text-orientation is not sideways-*), we remap 'middle' to
1986 // 'middle-with-baseline' so that images align sensibly with the
1987 // center-baseline-aligned text.
1988 if (!lineWM
.IsSideways()) {
1989 keyword
= StyleVerticalAlignKeyword::MozMiddleWithBaseline
;
1991 } else if (lineWM
.IsLineInverted()) {
1992 // Swap the meanings of top and bottom when line is inverted
1993 // relative to block direction.
1995 case StyleVerticalAlignKeyword::Top
:
1996 keyword
= StyleVerticalAlignKeyword::Bottom
;
1998 case StyleVerticalAlignKeyword::Bottom
:
1999 keyword
= StyleVerticalAlignKeyword::Top
;
2001 case StyleVerticalAlignKeyword::TextTop
:
2002 keyword
= StyleVerticalAlignKeyword::TextBottom
;
2004 case StyleVerticalAlignKeyword::TextBottom
:
2005 keyword
= StyleVerticalAlignKeyword::TextTop
;
2013 // baseline coord that may be adjusted for script offset
2014 nscoord revisedBaselineBCoord
= baselineBCoord
;
2016 // For superscript and subscript, raise or lower the baseline of the box
2017 // to the proper offset of the parent's box, then proceed as for BASELINE
2018 if (keyword
== StyleVerticalAlignKeyword::Sub
||
2019 keyword
== StyleVerticalAlignKeyword::Super
) {
2020 revisedBaselineBCoord
+= lineWM
.FlowRelativeToLineRelativeFactor() *
2021 (keyword
== StyleVerticalAlignKeyword::Sub
2022 ? fm
->SubscriptOffset()
2023 : -fm
->SuperscriptOffset());
2024 keyword
= StyleVerticalAlignKeyword::Baseline
;
2029 case StyleVerticalAlignKeyword::Baseline
:
2030 if (lineWM
.IsVertical() && !lineWM
.IsSideways()) {
2031 // FIXME: We should really use a central baseline from the
2032 // baseline table of the font, rather than assuming it's in
2035 nscoord borderBoxBSize
= pfd
->mBounds
.BSize(lineWM
);
2036 nscoord bStartBP
= pfd
->mBorderPadding
.BStart(lineWM
);
2037 nscoord bEndBP
= pfd
->mBorderPadding
.BEnd(lineWM
);
2038 nscoord contentBoxBSize
= borderBoxBSize
- bStartBP
- bEndBP
;
2039 pfd
->mBounds
.BStart(lineWM
) =
2040 revisedBaselineBCoord
- contentBoxBSize
/ 2 - bStartBP
;
2042 pfd
->mBounds
.BStart(lineWM
) = revisedBaselineBCoord
-
2044 pfd
->mMargin
.BStart(lineWM
);
2047 pfd
->mBounds
.BStart(lineWM
) = revisedBaselineBCoord
- pfd
->mAscent
;
2049 pfd
->mBlockDirAlign
= VALIGN_OTHER
;
2052 case StyleVerticalAlignKeyword::Top
: {
2053 pfd
->mBlockDirAlign
= VALIGN_TOP
;
2054 nscoord subtreeBSize
= logicalBSize
;
2056 subtreeBSize
= frameSpan
->mMaxBCoord
- frameSpan
->mMinBCoord
;
2057 NS_ASSERTION(subtreeBSize
>= logicalBSize
,
2058 "unexpected subtree block size");
2060 if (subtreeBSize
> maxStartBoxBSize
) {
2061 maxStartBoxBSize
= subtreeBSize
;
2066 case StyleVerticalAlignKeyword::Bottom
: {
2067 pfd
->mBlockDirAlign
= VALIGN_BOTTOM
;
2068 nscoord subtreeBSize
= logicalBSize
;
2070 subtreeBSize
= frameSpan
->mMaxBCoord
- frameSpan
->mMinBCoord
;
2071 NS_ASSERTION(subtreeBSize
>= logicalBSize
,
2072 "unexpected subtree block size");
2074 if (subtreeBSize
> maxEndBoxBSize
) {
2075 maxEndBoxBSize
= subtreeBSize
;
2080 case StyleVerticalAlignKeyword::Middle
: {
2081 // Align the midpoint of the frame with 1/2 the parents
2082 // x-height above the baseline.
2083 nscoord parentXHeight
=
2084 lineWM
.FlowRelativeToLineRelativeFactor() * fm
->XHeight();
2086 pfd
->mBounds
.BStart(lineWM
) =
2088 (parentXHeight
+ pfd
->mBounds
.BSize(lineWM
)) / 2;
2090 pfd
->mBounds
.BStart(lineWM
) = baselineBCoord
-
2091 (parentXHeight
+ logicalBSize
) / 2 +
2092 pfd
->mMargin
.BStart(lineWM
);
2094 pfd
->mBlockDirAlign
= VALIGN_OTHER
;
2098 case StyleVerticalAlignKeyword::TextTop
: {
2099 // The top of the logical box is aligned with the top of
2100 // the parent element's text.
2101 // XXX For vertical text we will need a new API to get the logical
2103 nscoord parentAscent
=
2104 lineWM
.IsLineInverted() ? fm
->MaxDescent() : fm
->MaxAscent();
2106 pfd
->mBounds
.BStart(lineWM
) = baselineBCoord
- parentAscent
-
2107 pfd
->mBorderPadding
.BStart(lineWM
) +
2108 frameSpan
->mBStartLeading
;
2110 pfd
->mBounds
.BStart(lineWM
) =
2111 baselineBCoord
- parentAscent
+ pfd
->mMargin
.BStart(lineWM
);
2113 pfd
->mBlockDirAlign
= VALIGN_OTHER
;
2117 case StyleVerticalAlignKeyword::TextBottom
: {
2118 // The bottom of the logical box is aligned with the
2119 // bottom of the parent elements text.
2120 nscoord parentDescent
=
2121 lineWM
.IsLineInverted() ? fm
->MaxAscent() : fm
->MaxDescent();
2123 pfd
->mBounds
.BStart(lineWM
) =
2124 baselineBCoord
+ parentDescent
- pfd
->mBounds
.BSize(lineWM
) +
2125 pfd
->mBorderPadding
.BEnd(lineWM
) - frameSpan
->mBEndLeading
;
2127 pfd
->mBounds
.BStart(lineWM
) = baselineBCoord
+ parentDescent
-
2128 pfd
->mBounds
.BSize(lineWM
) -
2129 pfd
->mMargin
.BEnd(lineWM
);
2131 pfd
->mBlockDirAlign
= VALIGN_OTHER
;
2135 case StyleVerticalAlignKeyword::MozMiddleWithBaseline
: {
2136 // Align the midpoint of the frame with the baseline of the parent.
2138 pfd
->mBounds
.BStart(lineWM
) =
2139 baselineBCoord
- pfd
->mBounds
.BSize(lineWM
) / 2;
2141 pfd
->mBounds
.BStart(lineWM
) =
2142 baselineBCoord
- logicalBSize
/ 2 + pfd
->mMargin
.BStart(lineWM
);
2144 pfd
->mBlockDirAlign
= VALIGN_OTHER
;
2149 // We have either a coord, a percent, or a calc().
2150 nscoord offset
= verticalAlign
.AsLength().Resolve([&] {
2151 // Percentages are like lengths, except treated as a percentage
2152 // of the elements line block size value.
2154 GetInflationForBlockDirAlignment(frame
, mInflationMinFontSize
);
2155 return ReflowInput::CalcLineHeight(
2156 frame
->GetContent(), frame
->Style(), frame
->PresContext(),
2157 mBlockReflowInput
->ComputedBSize(), inflation
);
2160 // According to the CSS2 spec (10.8.1), a positive value
2161 // "raises" the box by the given distance while a negative value
2162 // "lowers" the box by the given distance (with zero being the
2163 // baseline). Since Y coordinates increase towards the bottom of
2164 // the screen we reverse the sign, unless the line orientation is
2165 // inverted relative to block direction.
2166 nscoord revisedBaselineBCoord
=
2167 baselineBCoord
- offset
* lineWM
.FlowRelativeToLineRelativeFactor();
2168 if (lineWM
.IsVertical() && !lineWM
.IsSideways()) {
2169 // If we're using a dominant center baseline, we align with the center
2170 // of the frame being placed (bug 1133945).
2171 pfd
->mBounds
.BStart(lineWM
) =
2172 revisedBaselineBCoord
- pfd
->mBounds
.BSize(lineWM
) / 2;
2174 pfd
->mBounds
.BStart(lineWM
) = revisedBaselineBCoord
- pfd
->mAscent
;
2176 pfd
->mBlockDirAlign
= VALIGN_OTHER
;
2179 // Update minBCoord/maxBCoord for frames that we just placed. Do not factor
2180 // text into the equation.
2181 if (pfd
->mBlockDirAlign
== VALIGN_OTHER
) {
2182 // Text frames do not contribute to the min/max Y values for the
2183 // line (instead their parent frame's font-size contributes).
2184 // XXXrbs -- relax this restriction because it causes text frames
2185 // to jam together when 'font-size-adjust' is enabled
2186 // and layout is using dynamic font heights (bug 20394)
2187 // -- Note #1: With this code enabled and with the fact that we are
2188 // not using Em[Ascent|Descent] as nsDimensions for text
2189 // metrics in GFX mean that the discussion in bug 13072 cannot
2191 // -- Note #2: We still don't want empty-text frames to interfere.
2192 // For example in quirks mode, avoiding empty text frames
2193 // prevents "tall" lines around elements like <hr> since the
2194 // rules of <hr> in quirks.css have pseudo text contents with LF
2197 if (pfd
->mIsTextFrame
) {
2198 // Only consider text frames if they're not empty and
2199 // line-height=normal.
2200 canUpdate
= pfd
->mIsNonWhitespaceTextFrame
&&
2201 frame
->StyleText()->mLineHeight
.IsNormal();
2203 canUpdate
= !pfd
->mIsPlaceholder
;
2207 nscoord blockStart
, blockEnd
;
2209 // For spans that were are now placing, use their position
2210 // plus their already computed min-Y and max-Y values for
2211 // computing blockStart and blockEnd.
2212 blockStart
= pfd
->mBounds
.BStart(lineWM
) + frameSpan
->mMinBCoord
;
2213 blockEnd
= pfd
->mBounds
.BStart(lineWM
) + frameSpan
->mMaxBCoord
;
2216 pfd
->mBounds
.BStart(lineWM
) - pfd
->mMargin
.BStart(lineWM
);
2217 blockEnd
= blockStart
+ logicalBSize
;
2220 mPresContext
->CompatibilityMode() != eCompatibility_FullStandards
&&
2222 // Check if it's a BR frame that is not alone on its line (it
2223 // is given a block size of zero to indicate this), and if so reset
2224 // blockStart and blockEnd so that BR frames don't influence the line.
2225 if (frame
->IsBrFrame()) {
2226 blockStart
= BLOCKDIR_ALIGN_FRAMES_NO_MINIMUM
;
2227 blockEnd
= BLOCKDIR_ALIGN_FRAMES_NO_MAXIMUM
;
2230 if (blockStart
< minBCoord
) minBCoord
= blockStart
;
2231 if (blockEnd
> maxBCoord
) maxBCoord
= blockEnd
;
2232 #ifdef NOISY_BLOCKDIR_ALIGN
2234 " [frame]raw: a=%d h=%d bp=%d,%d logical: h=%d leading=%d y=%d "
2235 "minBCoord=%d maxBCoord=%d\n",
2236 pfd
->mAscent
, pfd
->mBounds
.BSize(lineWM
),
2237 pfd
->mBorderPadding
.Top(lineWM
), pfd
->mBorderPadding
.Bottom(lineWM
),
2238 logicalBSize
, frameSpan
? frameSpan
->mBStartLeading
: 0,
2239 pfd
->mBounds
.BStart(lineWM
), minBCoord
, maxBCoord
);
2242 if (psd
!= mRootSpan
) {
2243 frame
->SetRect(lineWM
, pfd
->mBounds
, ContainerSizeForSpan(psd
));
2249 // Factor in the minimum line block-size when handling the root-span for
2251 if (psd
== mRootSpan
) {
2252 // We should factor in the block element's minimum line-height (as
2253 // defined in section 10.8.1 of the css2 spec) assuming that
2254 // zeroEffectiveSpanBox is not set on the root span. This only happens
2255 // in some cases in quirks mode:
2256 // (1) if the root span contains non-whitespace text directly (this
2257 // is handled by zeroEffectiveSpanBox
2258 // (2) if this line has a ::marker
2259 // (3) if this is the last line of an LI, DT, or DD element
2260 // (The last line before a block also counts, but not before a
2261 // BR) (NN4/IE5 quirk)
2263 // (1) and (2) above
2264 bool applyMinLH
= !zeroEffectiveSpanBox
|| mHasMarker
;
2266 !mGotLineBox
|| (!mLineBox
->IsLineWrapped() && !mLineEndsInBR
);
2267 if (!applyMinLH
&& isLastLine
) {
2268 nsIContent
* blockContent
= mRootSpan
->mFrame
->mFrame
->GetContent();
2270 // (3) above, if the last line of LI, DT, or DD
2271 if (blockContent
->IsAnyOfHTMLElements(nsGkAtoms::li
, nsGkAtoms::dt
,
2278 if (psd
->mHasNonemptyContent
|| preMode
|| mHasMarker
) {
2279 #ifdef NOISY_BLOCKDIR_ALIGN
2280 printf(" [span]==> adjusting min/maxBCoord: currentValues: %d,%d",
2281 minBCoord
, maxBCoord
);
2283 nscoord minimumLineBSize
= mMinLineBSize
;
2284 nscoord blockStart
= -nsLayoutUtils::GetCenteredFontBaseline(
2285 fm
, minimumLineBSize
, lineWM
.IsLineInverted());
2286 nscoord blockEnd
= blockStart
+ minimumLineBSize
;
2288 if (mStyleText
->HasEffectiveTextEmphasis()) {
2289 nscoord fontMaxHeight
= fm
->MaxHeight();
2290 nscoord emphasisHeight
=
2291 GetBSizeOfEmphasisMarks(spanFrame
, inflation
);
2292 nscoord delta
= fontMaxHeight
+ emphasisHeight
- minimumLineBSize
;
2294 if (minimumLineBSize
< fontMaxHeight
) {
2295 // If the leadings are negative, fill them first.
2296 nscoord ascent
= fm
->MaxAscent();
2297 nscoord descent
= fm
->MaxDescent();
2298 if (lineWM
.IsLineInverted()) {
2299 std::swap(ascent
, descent
);
2301 blockStart
= -ascent
;
2303 delta
= emphasisHeight
;
2305 LogicalSide side
= mStyleText
->TextEmphasisSide(lineWM
);
2306 if (side
== eLogicalSideBStart
) {
2307 blockStart
-= delta
;
2314 if (blockStart
< minBCoord
) minBCoord
= blockStart
;
2315 if (blockEnd
> maxBCoord
) maxBCoord
= blockEnd
;
2317 #ifdef NOISY_BLOCKDIR_ALIGN
2318 printf(" new values: %d,%d\n", minBCoord
, maxBCoord
);
2320 #ifdef NOISY_BLOCKDIR_ALIGN
2322 " Used mMinLineBSize: %d, blockStart: %d, blockEnd: "
2324 mMinLineBSize
, blockStart
, blockEnd
);
2328 // [1] BR's on empty lines stop working
2329 // [2] May not honor css2's notion of handling empty elements
2330 // [3] blank lines in a pre-section ("\n") (handled with preMode)
2332 // XXX Are there other problems with this?
2333 #ifdef NOISY_BLOCKDIR_ALIGN
2335 " [span]==> zapping min/maxBCoord: currentValues: %d,%d "
2337 minBCoord
, maxBCoord
);
2339 minBCoord
= maxBCoord
= 0;
2344 if ((minBCoord
== BLOCKDIR_ALIGN_FRAMES_NO_MINIMUM
) ||
2345 (maxBCoord
== BLOCKDIR_ALIGN_FRAMES_NO_MAXIMUM
)) {
2346 minBCoord
= maxBCoord
= baselineBCoord
;
2349 if (psd
!= mRootSpan
&& zeroEffectiveSpanBox
) {
2350 #ifdef NOISY_BLOCKDIR_ALIGN
2351 printf(" [span]adjusting for zeroEffectiveSpanBox\n");
2353 " Original: minBCoord=%d, maxBCoord=%d, bSize=%d, ascent=%d, "
2354 "logicalBSize=%d, topLeading=%d, bottomLeading=%d\n",
2355 minBCoord
, maxBCoord
, spanFramePFD
->mBounds
.BSize(lineWM
),
2356 spanFramePFD
->mAscent
, psd
->mLogicalBSize
, psd
->mBStartLeading
,
2359 nscoord goodMinBCoord
=
2360 spanFramePFD
->mBorderPadding
.BStart(lineWM
) - psd
->mBStartLeading
;
2361 nscoord goodMaxBCoord
= goodMinBCoord
+ psd
->mLogicalBSize
;
2363 // For cases like the one in bug 714519 (text-decoration placement
2364 // or making nsLineLayout::IsZeroBSize() handle
2365 // vertical-align:top/bottom on a descendant of the line that's not
2366 // a child of it), we want to treat elements that are
2367 // vertical-align: top or bottom somewhat like children for the
2368 // purposes of this quirk. To some extent, this is guessing, since
2369 // they might end up being aligned anywhere. However, we'll guess
2370 // that they'll be placed aligned with the top or bottom of this
2371 // frame (as though this frame is the only thing in the line).
2372 // (Guessing isn't unreasonable, since all we're doing is reducing the
2373 // scope of a quirk and making the behavior more standards-like.)
2374 if (maxStartBoxBSize
> maxBCoord
- minBCoord
) {
2375 // Distribute maxStartBoxBSize to ascent (baselineBCoord - minBCoord), and
2376 // then to descent (maxBCoord - baselineBCoord) by adjusting minBCoord or
2377 // maxBCoord, but not to exceed goodMinBCoord and goodMaxBCoord.
2378 nscoord distribute
= maxStartBoxBSize
- (maxBCoord
- minBCoord
);
2379 nscoord ascentSpace
= std::max(minBCoord
- goodMinBCoord
, 0);
2380 if (distribute
> ascentSpace
) {
2381 distribute
-= ascentSpace
;
2382 minBCoord
-= ascentSpace
;
2383 nscoord descentSpace
= std::max(goodMaxBCoord
- maxBCoord
, 0);
2384 if (distribute
> descentSpace
) {
2385 maxBCoord
+= descentSpace
;
2387 maxBCoord
+= distribute
;
2390 minBCoord
-= distribute
;
2393 if (maxEndBoxBSize
> maxBCoord
- minBCoord
) {
2394 // Likewise, but preferring descent to ascent.
2395 nscoord distribute
= maxEndBoxBSize
- (maxBCoord
- minBCoord
);
2396 nscoord descentSpace
= std::max(goodMaxBCoord
- maxBCoord
, 0);
2397 if (distribute
> descentSpace
) {
2398 distribute
-= descentSpace
;
2399 maxBCoord
+= descentSpace
;
2400 nscoord ascentSpace
= std::max(minBCoord
- goodMinBCoord
, 0);
2401 if (distribute
> ascentSpace
) {
2402 minBCoord
-= ascentSpace
;
2404 minBCoord
-= distribute
;
2407 maxBCoord
+= distribute
;
2411 if (minBCoord
> goodMinBCoord
) {
2412 nscoord adjust
= minBCoord
- goodMinBCoord
; // positive
2414 // shrink the logical extents
2415 psd
->mLogicalBSize
-= adjust
;
2416 psd
->mBStartLeading
-= adjust
;
2418 if (maxBCoord
< goodMaxBCoord
) {
2419 nscoord adjust
= goodMaxBCoord
- maxBCoord
;
2420 psd
->mLogicalBSize
-= adjust
;
2421 psd
->mBEndLeading
-= adjust
;
2423 if (minBCoord
> 0) {
2424 // shrink the content by moving its block start down. This is tricky,
2425 // since the block start is the 0 for many coordinates, so what we do is
2426 // move everything else up.
2427 spanFramePFD
->mAscent
-= minBCoord
; // move the baseline up
2428 spanFramePFD
->mBounds
.BSize(lineWM
) -=
2429 minBCoord
; // move the block end up
2430 psd
->mBStartLeading
+= minBCoord
;
2431 *psd
->mBaseline
-= minBCoord
;
2433 pfd
= psd
->mFirstFrame
;
2434 while (nullptr != pfd
) {
2435 pfd
->mBounds
.BStart(lineWM
) -= minBCoord
; // move all the children
2437 pfd
->mFrame
->SetRect(lineWM
, pfd
->mBounds
, ContainerSizeForSpan(psd
));
2440 maxBCoord
-= minBCoord
; // since minBCoord is in the frame's own
2441 // coordinate system
2444 if (maxBCoord
< spanFramePFD
->mBounds
.BSize(lineWM
)) {
2445 nscoord adjust
= spanFramePFD
->mBounds
.BSize(lineWM
) - maxBCoord
;
2446 spanFramePFD
->mBounds
.BSize(lineWM
) -= adjust
; // move the bottom up
2447 psd
->mBEndLeading
+= adjust
;
2449 #ifdef NOISY_BLOCKDIR_ALIGN
2451 " New: minBCoord=%d, maxBCoord=%d, bSize=%d, ascent=%d, "
2452 "logicalBSize=%d, topLeading=%d, bottomLeading=%d\n",
2453 minBCoord
, maxBCoord
, spanFramePFD
->mBounds
.BSize(lineWM
),
2454 spanFramePFD
->mAscent
, psd
->mLogicalBSize
, psd
->mBStartLeading
,
2459 psd
->mMinBCoord
= minBCoord
;
2460 psd
->mMaxBCoord
= maxBCoord
;
2461 #ifdef NOISY_BLOCKDIR_ALIGN
2463 " [span]==> minBCoord=%d maxBCoord=%d delta=%d maxStartBoxBSize=%d "
2464 "maxEndBoxBSize=%d\n",
2465 minBCoord
, maxBCoord
, maxBCoord
- minBCoord
, maxStartBoxBSize
,
2468 if (maxStartBoxBSize
> mMaxStartBoxBSize
) {
2469 mMaxStartBoxBSize
= maxStartBoxBSize
;
2471 if (maxEndBoxBSize
> mMaxEndBoxBSize
) {
2472 mMaxEndBoxBSize
= maxEndBoxBSize
;
2476 static void SlideSpanFrameRect(nsIFrame
* aFrame
, nscoord aDeltaWidth
) {
2477 // This should not use nsIFrame::MovePositionBy because it happens
2478 // prior to relative positioning. In particular, because
2479 // nsBlockFrame::PlaceLine calls aLineLayout.TrimTrailingWhiteSpace()
2480 // prior to calling aLineLayout.RelativePositionFrames().
2481 nsPoint p
= aFrame
->GetPosition();
2483 aFrame
->SetPosition(p
);
2486 bool nsLineLayout::TrimTrailingWhiteSpaceIn(PerSpanData
* psd
,
2487 nscoord
* aDeltaISize
) {
2488 PerFrameData
* pfd
= psd
->mFirstFrame
;
2494 while (nullptr != pfd
) {
2495 #ifdef REALLY_NOISY_TRIM
2496 psd
->mFrame
->mFrame
->ListTag(stdout
);
2497 printf(": attempting trim of ");
2498 pfd
->mFrame
->ListTag(stdout
);
2501 PerSpanData
* childSpan
= pfd
->mSpan
;
2502 WritingMode lineWM
= mRootSpan
->mWritingMode
;
2504 // Maybe the child span has the trailing white-space in it?
2505 if (TrimTrailingWhiteSpaceIn(childSpan
, aDeltaISize
)) {
2506 nscoord deltaISize
= *aDeltaISize
;
2508 // Adjust the child spans frame size
2509 pfd
->mBounds
.ISize(lineWM
) -= deltaISize
;
2510 if (psd
!= mRootSpan
) {
2511 // When the child span is not a direct child of the block
2512 // we need to update the child spans frame rectangle
2513 // because it most likely will not be done again. Spans
2514 // that are direct children of the block will be updated
2515 // later, however, because the VerticalAlignFrames method
2516 // will be run after this method.
2517 nsSize containerSize
= ContainerSizeForSpan(childSpan
);
2518 nsIFrame
* f
= pfd
->mFrame
;
2519 LogicalRect
r(lineWM
, f
->GetRect(), containerSize
);
2520 r
.ISize(lineWM
) -= deltaISize
;
2521 f
->SetRect(lineWM
, r
, containerSize
);
2524 // Adjust the inline end edge of the span that contains the child span
2525 psd
->mICoord
-= deltaISize
;
2527 // Slide any frames that follow the child span over by the
2528 // correct amount. The only thing that can follow the child
2529 // span is empty stuff, so we are just making things
2530 // sensible (keeping the combined area honest).
2531 while (pfd
->mNext
) {
2533 pfd
->mBounds
.IStart(lineWM
) -= deltaISize
;
2534 if (psd
!= mRootSpan
) {
2535 // When the child span is not a direct child of the block
2536 // we need to update the child span's frame rectangle
2537 // because it most likely will not be done again. Spans
2538 // that are direct children of the block will be updated
2539 // later, however, because the VerticalAlignFrames method
2540 // will be run after this method.
2541 SlideSpanFrameRect(pfd
->mFrame
, deltaISize
);
2547 } else if (!pfd
->mIsTextFrame
&& !pfd
->mSkipWhenTrimmingWhitespace
) {
2548 // If we hit a frame on the end that's not text and not a placeholder,
2549 // then there is no trailing whitespace to trim. Stop the search.
2552 } else if (pfd
->mIsTextFrame
) {
2553 // Call TrimTrailingWhiteSpace even on empty textframes because they
2554 // might have a soft hyphen which should now appear, changing the frame's
2556 nsTextFrame::TrimOutput trimOutput
=
2557 static_cast<nsTextFrame
*>(pfd
->mFrame
)
2558 ->TrimTrailingWhiteSpace(
2559 mBlockReflowInput
->mRenderingContext
->GetDrawTarget());
2561 psd
->mFrame
->mFrame
->ListTag(stdout
);
2562 printf(": trim of ");
2563 pfd
->mFrame
->ListTag(stdout
);
2564 printf(" returned %d\n", trimOutput
.mDeltaWidth
);
2567 if (trimOutput
.mChanged
) {
2568 pfd
->mRecomputeOverflow
= true;
2571 // Delta width not being zero means that
2572 // there is trimmed space in the frame.
2573 if (trimOutput
.mDeltaWidth
) {
2574 pfd
->mBounds
.ISize(lineWM
) -= trimOutput
.mDeltaWidth
;
2576 // If any trailing space is trimmed, the justification opportunity
2577 // generated by the space should be removed as well.
2578 pfd
->mJustificationInfo
.CancelOpportunityForTrimmedSpace();
2580 // See if the text frame has already been placed in its parent
2581 if (psd
!= mRootSpan
) {
2582 // The frame was already placed during psd's
2583 // reflow. Update the frames rectangle now.
2584 pfd
->mFrame
->SetRect(lineWM
, pfd
->mBounds
, ContainerSizeForSpan(psd
));
2587 // Adjust containing span's right edge
2588 psd
->mICoord
-= trimOutput
.mDeltaWidth
;
2590 // Slide any frames that follow the text frame over by the
2591 // right amount. The only thing that can follow the text
2592 // frame is empty stuff, so we are just making things
2593 // sensible (keeping the combined area honest).
2594 while (pfd
->mNext
) {
2596 pfd
->mBounds
.IStart(lineWM
) -= trimOutput
.mDeltaWidth
;
2597 if (psd
!= mRootSpan
) {
2598 // When the child span is not a direct child of the block
2599 // we need to update the child spans frame rectangle
2600 // because it most likely will not be done again. Spans
2601 // that are direct children of the block will be updated
2602 // later, however, because the VerticalAlignFrames method
2603 // will be run after this method.
2604 SlideSpanFrameRect(pfd
->mFrame
, trimOutput
.mDeltaWidth
);
2609 if (pfd
->mIsNonEmptyTextFrame
|| trimOutput
.mChanged
) {
2610 // Pass up to caller so they can shrink their span
2611 *aDeltaISize
= trimOutput
.mDeltaWidth
;
2622 bool nsLineLayout::TrimTrailingWhiteSpace() {
2623 PerSpanData
* psd
= mRootSpan
;
2625 TrimTrailingWhiteSpaceIn(psd
, &deltaISize
);
2626 return 0 != deltaISize
;
2629 bool nsLineLayout::PerFrameData::ParticipatesInJustification() const {
2630 if (mIsMarker
|| mIsEmpty
|| mSkipWhenTrimmingWhitespace
) {
2631 // Skip ::markers, empty frames, and placeholders
2634 if (mIsTextFrame
&& !mIsNonWhitespaceTextFrame
&&
2635 static_cast<nsTextFrame
*>(mFrame
)->IsAtEndOfLine()) {
2636 // Skip trimmed whitespaces
2642 struct nsLineLayout::JustificationComputationState
{
2643 PerFrameData
* mFirstParticipant
;
2644 PerFrameData
* mLastParticipant
;
2645 // When we are going across a boundary of ruby base, i.e. entering
2646 // one, leaving one, or both, the following fields will be set to
2647 // the corresponding ruby base frame for handling ruby-align.
2648 PerFrameData
* mLastExitedRubyBase
;
2649 PerFrameData
* mLastEnteredRubyBase
;
2651 JustificationComputationState()
2652 : mFirstParticipant(nullptr),
2653 mLastParticipant(nullptr),
2654 mLastExitedRubyBase(nullptr),
2655 mLastEnteredRubyBase(nullptr) {}
2658 static bool IsRubyAlignSpaceAround(nsIFrame
* aRubyBase
) {
2659 return aRubyBase
->StyleText()->mRubyAlign
== StyleRubyAlign::SpaceAround
;
2663 * Assign justification gaps for justification
2664 * opportunities across two frames.
2667 int nsLineLayout::AssignInterframeJustificationGaps(
2668 PerFrameData
* aFrame
, JustificationComputationState
& aState
) {
2669 PerFrameData
* prev
= aState
.mLastParticipant
;
2672 auto& assign
= aFrame
->mJustificationAssignment
;
2673 auto& prevAssign
= prev
->mJustificationAssignment
;
2675 if (aState
.mLastExitedRubyBase
|| aState
.mLastEnteredRubyBase
) {
2676 PerFrameData
* exitedRubyBase
= aState
.mLastExitedRubyBase
;
2677 if (!exitedRubyBase
|| IsRubyAlignSpaceAround(exitedRubyBase
->mFrame
)) {
2678 prevAssign
.mGapsAtEnd
= 1;
2680 exitedRubyBase
->mJustificationAssignment
.mGapsAtEnd
= 1;
2683 PerFrameData
* enteredRubyBase
= aState
.mLastEnteredRubyBase
;
2684 if (!enteredRubyBase
|| IsRubyAlignSpaceAround(enteredRubyBase
->mFrame
)) {
2685 assign
.mGapsAtStart
= 1;
2687 enteredRubyBase
->mJustificationAssignment
.mGapsAtStart
= 1;
2690 // We are no longer going across a ruby base boundary.
2691 aState
.mLastExitedRubyBase
= nullptr;
2692 aState
.mLastEnteredRubyBase
= nullptr;
2696 const auto& info
= aFrame
->mJustificationInfo
;
2697 const auto& prevInfo
= prev
->mJustificationInfo
;
2698 if (!info
.mIsStartJustifiable
&& !prevInfo
.mIsEndJustifiable
) {
2702 if (!info
.mIsStartJustifiable
) {
2703 prevAssign
.mGapsAtEnd
= 2;
2704 assign
.mGapsAtStart
= 0;
2705 } else if (!prevInfo
.mIsEndJustifiable
) {
2706 prevAssign
.mGapsAtEnd
= 0;
2707 assign
.mGapsAtStart
= 2;
2709 prevAssign
.mGapsAtEnd
= 1;
2710 assign
.mGapsAtStart
= 1;
2716 * Compute the justification info of the given span, and store the
2717 * number of inner opportunities into the frame's justification info.
2718 * It returns the number of non-inner opportunities it detects.
2720 int32_t nsLineLayout::ComputeFrameJustification(
2721 PerSpanData
* aPSD
, JustificationComputationState
& aState
) {
2722 NS_ASSERTION(aPSD
, "null arg");
2723 NS_ASSERTION(!aState
.mLastParticipant
|| !aState
.mLastParticipant
->mSpan
,
2724 "Last participant shall always be a leaf frame");
2725 bool firstChild
= true;
2726 int32_t& innerOpportunities
=
2727 aPSD
->mFrame
->mJustificationInfo
.mInnerOpportunities
;
2728 MOZ_ASSERT(innerOpportunities
== 0,
2729 "Justification info should not have been set yet.");
2730 int32_t outerOpportunities
= 0;
2732 for (PerFrameData
* pfd
= aPSD
->mFirstFrame
; pfd
; pfd
= pfd
->mNext
) {
2733 if (!pfd
->ParticipatesInJustification()) {
2737 bool isRubyBase
= pfd
->mFrame
->IsRubyBaseFrame();
2738 PerFrameData
* outerRubyBase
= aState
.mLastEnteredRubyBase
;
2740 aState
.mLastEnteredRubyBase
= pfd
;
2743 int extraOpportunities
= 0;
2745 PerSpanData
* span
= pfd
->mSpan
;
2746 extraOpportunities
= ComputeFrameJustification(span
, aState
);
2747 innerOpportunities
+= pfd
->mJustificationInfo
.mInnerOpportunities
;
2749 if (pfd
->mIsTextFrame
) {
2750 innerOpportunities
+= pfd
->mJustificationInfo
.mInnerOpportunities
;
2753 if (!aState
.mLastParticipant
) {
2754 aState
.mFirstParticipant
= pfd
;
2755 // It is not an empty ruby base, but we are not assigning gaps
2756 // to the content for now. Clear the last entered ruby base so
2757 // that we can correctly set the last exited ruby base.
2758 aState
.mLastEnteredRubyBase
= nullptr;
2760 extraOpportunities
= AssignInterframeJustificationGaps(pfd
, aState
);
2763 aState
.mLastParticipant
= pfd
;
2767 if (aState
.mLastEnteredRubyBase
== pfd
) {
2768 // There is no justification participant inside this ruby base.
2769 // Ignore this ruby base completely and restore the outer ruby
2771 aState
.mLastEnteredRubyBase
= outerRubyBase
;
2773 aState
.mLastExitedRubyBase
= pfd
;
2778 outerOpportunities
= extraOpportunities
;
2781 innerOpportunities
+= extraOpportunities
;
2785 return outerOpportunities
;
2788 void nsLineLayout::AdvanceAnnotationInlineBounds(PerFrameData
* aPFD
,
2789 const nsSize
& aContainerSize
,
2790 nscoord aDeltaICoord
,
2791 nscoord aDeltaISize
) {
2792 nsIFrame
* frame
= aPFD
->mFrame
;
2793 LayoutFrameType frameType
= frame
->Type();
2794 MOZ_ASSERT(frameType
== LayoutFrameType::RubyText
||
2795 frameType
== LayoutFrameType::RubyTextContainer
);
2796 MOZ_ASSERT(aPFD
->mSpan
, "rt and rtc should have span.");
2798 PerSpanData
* psd
= aPFD
->mSpan
;
2799 WritingMode lineWM
= mRootSpan
->mWritingMode
;
2800 aPFD
->mBounds
.IStart(lineWM
) += aDeltaICoord
;
2802 // Check whether this expansion should be counted into the reserved
2803 // isize or not. When it is a ruby text container, and it has some
2804 // children linked to the base, it must not have reserved isize,
2805 // or its children won't align with their bases. Otherwise, this
2806 // expansion should be reserved. There are two cases a ruby text
2807 // container does not have children linked to the base:
2808 // 1. it is a container for span; 2. its children are collapsed.
2809 // See bug 1055674 for the second case.
2810 if (frameType
== LayoutFrameType::RubyText
||
2811 // This ruby text container is a span.
2812 (psd
->mFirstFrame
== psd
->mLastFrame
&& psd
->mFirstFrame
&&
2813 !psd
->mFirstFrame
->mIsLinkedToBase
)) {
2814 // For ruby text frames, only increase frames
2815 // which are not auto-hidden.
2816 if (frameType
!= LayoutFrameType::RubyText
||
2817 !static_cast<nsRubyTextFrame
*>(frame
)->IsAutoHidden()) {
2818 nscoord reservedISize
= RubyUtils::GetReservedISize(frame
);
2819 RubyUtils::SetReservedISize(frame
, reservedISize
+ aDeltaISize
);
2822 // It is a normal ruby text container. Its children will expand
2823 // themselves properly. We only need to expand its own size here.
2824 aPFD
->mBounds
.ISize(lineWM
) += aDeltaISize
;
2826 aPFD
->mFrame
->SetRect(lineWM
, aPFD
->mBounds
, aContainerSize
);
2830 * This function applies the changes of icoord and isize caused by
2831 * justification to annotations of the given frame.
2833 void nsLineLayout::ApplyLineJustificationToAnnotations(PerFrameData
* aPFD
,
2834 nscoord aDeltaICoord
,
2835 nscoord aDeltaISize
) {
2836 PerFrameData
* pfd
= aPFD
->mNextAnnotation
;
2838 nsSize containerSize
= pfd
->mFrame
->GetParent()->GetSize();
2839 AdvanceAnnotationInlineBounds(pfd
, containerSize
, aDeltaICoord
,
2842 // There are two cases where an annotation frame has siblings which
2843 // do not attached to a ruby base-level frame:
2844 // 1. there's an intra-annotation whitespace which has no intra-base
2845 // white-space to pair with;
2846 // 2. there are not enough ruby bases to be paired with annotations.
2847 // In these cases, their size should not be affected, but we still
2848 // need to move them so that they won't overlap other frames.
2849 PerFrameData
* sibling
= pfd
->mNext
;
2850 while (sibling
&& !sibling
->mIsLinkedToBase
) {
2851 AdvanceAnnotationInlineBounds(sibling
, containerSize
,
2852 aDeltaICoord
+ aDeltaISize
, 0);
2853 sibling
= sibling
->mNext
;
2856 pfd
= pfd
->mNextAnnotation
;
2860 nscoord
nsLineLayout::ApplyFrameJustification(
2861 PerSpanData
* aPSD
, JustificationApplicationState
& aState
) {
2862 NS_ASSERTION(aPSD
, "null arg");
2864 nscoord deltaICoord
= 0;
2865 for (PerFrameData
* pfd
= aPSD
->mFirstFrame
; pfd
!= nullptr;
2868 WritingMode lineWM
= mRootSpan
->mWritingMode
;
2869 const auto& assign
= pfd
->mJustificationAssignment
;
2871 pfd
->mIsTextFrame
&& !pfd
->mWritingMode
.IsOrthogonalTo(lineWM
);
2873 // Don't apply justification if the frame doesn't participate. Same
2874 // as the condition used in ComputeFrameJustification. Note that,
2875 // we still need to move the frame based on deltaICoord even if the
2876 // frame itself doesn't expand.
2877 if (pfd
->ParticipatesInJustification()) {
2879 if (aState
.IsJustifiable()) {
2880 // Set corresponding justification gaps here, so that the
2881 // text frame knows how it should add gaps at its sides.
2882 const auto& info
= pfd
->mJustificationInfo
;
2883 auto textFrame
= static_cast<nsTextFrame
*>(pfd
->mFrame
);
2884 textFrame
->AssignJustificationGaps(assign
);
2885 dw
= aState
.Consume(JustificationUtils::CountGaps(info
, assign
));
2889 pfd
->mRecomputeOverflow
= true;
2892 if (nullptr != pfd
->mSpan
) {
2893 dw
= ApplyFrameJustification(pfd
->mSpan
, aState
);
2897 MOZ_ASSERT(!assign
.TotalGaps(),
2898 "Non-participants shouldn't have assigned gaps");
2901 pfd
->mBounds
.ISize(lineWM
) += dw
;
2902 nscoord gapsAtEnd
= 0;
2903 if (!isInlineText
&& assign
.TotalGaps()) {
2904 // It is possible that we assign gaps to non-text frame or an
2905 // orthogonal text frame. Apply the gaps as margin for them.
2906 deltaICoord
+= aState
.Consume(assign
.mGapsAtStart
);
2907 gapsAtEnd
= aState
.Consume(assign
.mGapsAtEnd
);
2910 pfd
->mBounds
.IStart(lineWM
) += deltaICoord
;
2912 // The gaps added to the end of the frame should also be
2913 // excluded from the isize added to the annotation.
2914 ApplyLineJustificationToAnnotations(pfd
, deltaICoord
, dw
- gapsAtEnd
);
2916 pfd
->mFrame
->SetRect(lineWM
, pfd
->mBounds
, ContainerSizeForSpan(aPSD
));
2921 static nsIFrame
* FindNearestRubyBaseAncestor(nsIFrame
* aFrame
) {
2922 MOZ_ASSERT(aFrame
->Style()->ShouldSuppressLineBreak());
2923 while (aFrame
&& !aFrame
->IsRubyBaseFrame()) {
2924 aFrame
= aFrame
->GetParent();
2926 // XXX It is possible that no ruby base ancestor is found because of
2927 // some edge cases like form control or canvas inside ruby text.
2928 // See bug 1138092 comment 4.
2929 NS_WARNING_ASSERTION(aFrame
, "no ruby base ancestor?");
2934 * This method expands the given frame by the given reserved isize.
2936 void nsLineLayout::ExpandRubyBox(PerFrameData
* aFrame
, nscoord aReservedISize
,
2937 const nsSize
& aContainerSize
) {
2938 WritingMode lineWM
= mRootSpan
->mWritingMode
;
2939 auto rubyAlign
= aFrame
->mFrame
->StyleText()->mRubyAlign
;
2940 switch (rubyAlign
) {
2941 case StyleRubyAlign::Start
:
2942 // do nothing for start
2944 case StyleRubyAlign::SpaceBetween
:
2945 case StyleRubyAlign::SpaceAround
: {
2946 int32_t opportunities
= aFrame
->mJustificationInfo
.mInnerOpportunities
;
2947 int32_t gaps
= opportunities
* 2;
2948 if (rubyAlign
== StyleRubyAlign::SpaceAround
) {
2949 // Each expandable ruby box with ruby-align space-around has a
2950 // gap at each of its sides. For rb/rbc, see comment in
2951 // AssignInterframeJustificationGaps; for rt/rtc, see comment
2952 // in ExpandRubyBoxWithAnnotations.
2956 JustificationApplicationState
state(gaps
, aReservedISize
);
2957 ApplyFrameJustification(aFrame
->mSpan
, state
);
2960 // If there are no justification opportunities for space-between,
2961 // fall-through to center per spec.
2964 case StyleRubyAlign::Center
:
2965 // Indent all children by half of the reserved inline size.
2966 for (PerFrameData
* child
= aFrame
->mSpan
->mFirstFrame
; child
;
2967 child
= child
->mNext
) {
2968 child
->mBounds
.IStart(lineWM
) += aReservedISize
/ 2;
2969 child
->mFrame
->SetRect(lineWM
, child
->mBounds
, aContainerSize
);
2973 MOZ_ASSERT_UNREACHABLE("Unknown ruby-align value");
2976 aFrame
->mBounds
.ISize(lineWM
) += aReservedISize
;
2977 aFrame
->mFrame
->SetRect(lineWM
, aFrame
->mBounds
, aContainerSize
);
2981 * This method expands the given frame by the reserved inline size.
2982 * It also expands its annotations if they are expandable and have
2983 * reserved isize larger than zero.
2985 void nsLineLayout::ExpandRubyBoxWithAnnotations(PerFrameData
* aFrame
,
2986 const nsSize
& aContainerSize
) {
2987 nscoord reservedISize
= RubyUtils::GetReservedISize(aFrame
->mFrame
);
2988 if (reservedISize
) {
2989 ExpandRubyBox(aFrame
, reservedISize
, aContainerSize
);
2992 WritingMode lineWM
= mRootSpan
->mWritingMode
;
2993 bool isLevelContainer
= aFrame
->mFrame
->IsRubyBaseContainerFrame();
2994 for (PerFrameData
* annotation
= aFrame
->mNextAnnotation
; annotation
;
2995 annotation
= annotation
->mNextAnnotation
) {
2996 if (lineWM
.IsOrthogonalTo(annotation
->mFrame
->GetWritingMode())) {
2997 // Inter-character case: don't attempt to expand ruby annotations.
3000 if (isLevelContainer
) {
3001 nsIFrame
* rtcFrame
= annotation
->mFrame
;
3002 MOZ_ASSERT(rtcFrame
->IsRubyTextContainerFrame());
3003 // It is necessary to set the rect again because the container
3004 // width was unknown, and zero was used instead when we reflow
3005 // them. The corresponding base containers were repositioned in
3006 // VerticalAlignFrames and PlaceTopBottomFrames.
3007 MOZ_ASSERT(rtcFrame
->GetLogicalSize(lineWM
) ==
3008 annotation
->mBounds
.Size(lineWM
));
3009 rtcFrame
->SetPosition(lineWM
, annotation
->mBounds
.Origin(lineWM
),
3013 nscoord reservedISize
= RubyUtils::GetReservedISize(annotation
->mFrame
);
3014 if (!reservedISize
) {
3018 MOZ_ASSERT(annotation
->mSpan
);
3019 JustificationComputationState computeState
;
3020 ComputeFrameJustification(annotation
->mSpan
, computeState
);
3021 if (!computeState
.mFirstParticipant
) {
3024 if (IsRubyAlignSpaceAround(annotation
->mFrame
)) {
3025 // Add one gap at each side of this annotation.
3026 computeState
.mFirstParticipant
->mJustificationAssignment
.mGapsAtStart
= 1;
3027 computeState
.mLastParticipant
->mJustificationAssignment
.mGapsAtEnd
= 1;
3029 nsIFrame
* parentFrame
= annotation
->mFrame
->GetParent();
3030 nsSize containerSize
= parentFrame
->GetSize();
3031 MOZ_ASSERT(containerSize
== aContainerSize
||
3032 parentFrame
->IsRubyTextContainerFrame(),
3033 "Container width should only be different when the current "
3034 "annotation is a ruby text frame, whose parent is not same "
3035 "as its base frame.");
3036 ExpandRubyBox(annotation
, reservedISize
, containerSize
);
3037 ExpandInlineRubyBoxes(annotation
->mSpan
);
3042 * This method looks for all expandable ruby box in the given span, and
3043 * calls ExpandRubyBox to expand them in depth-first preorder.
3045 void nsLineLayout::ExpandInlineRubyBoxes(PerSpanData
* aSpan
) {
3046 nsSize containerSize
= ContainerSizeForSpan(aSpan
);
3047 for (PerFrameData
* pfd
= aSpan
->mFirstFrame
; pfd
; pfd
= pfd
->mNext
) {
3048 if (RubyUtils::IsExpandableRubyBox(pfd
->mFrame
)) {
3049 ExpandRubyBoxWithAnnotations(pfd
, containerSize
);
3052 ExpandInlineRubyBoxes(pfd
->mSpan
);
3057 // Align inline frames within the line according to the CSS text-align
3059 void nsLineLayout::TextAlignLine(nsLineBox
* aLine
, bool aIsLastLine
) {
3061 * NOTE: aIsLastLine ain't necessarily so: it is correctly set by caller
3062 * only in cases where the last line needs special handling.
3064 PerSpanData
* psd
= mRootSpan
;
3065 WritingMode lineWM
= psd
->mWritingMode
;
3066 LAYOUT_WARN_IF_FALSE(psd
->mIEnd
!= NS_UNCONSTRAINEDSIZE
,
3067 "have unconstrained width; this should only result from "
3068 "very large sizes, not attempts at intrinsic width "
3070 nscoord availISize
= psd
->mIEnd
- psd
->mIStart
;
3071 nscoord remainingISize
= availISize
- aLine
->ISize();
3072 #ifdef NOISY_INLINEDIR_ALIGN
3073 mBlockReflowInput
->mFrame
->ListTag(stdout
);
3074 printf(": availISize=%d lineBounds.IStart=%d lineISize=%d delta=%d\n",
3075 availISize
, aLine
->IStart(), aLine
->ISize(), remainingISize
);
3079 StyleTextAlign textAlign
=
3080 aIsLastLine
? mStyleText
->TextAlignForLastLine() : mStyleText
->mTextAlign
;
3082 bool isSVG
= SVGUtils::IsInSVGTextSubtree(mBlockReflowInput
->mFrame
);
3083 bool doTextAlign
= remainingISize
> 0;
3085 int32_t additionalGaps
= 0;
3087 (mHasRuby
|| (doTextAlign
&& textAlign
== StyleTextAlign::Justify
))) {
3088 JustificationComputationState computeState
;
3089 ComputeFrameJustification(psd
, computeState
);
3090 if (mHasRuby
&& computeState
.mFirstParticipant
) {
3091 PerFrameData
* firstFrame
= computeState
.mFirstParticipant
;
3092 if (firstFrame
->mFrame
->Style()->ShouldSuppressLineBreak()) {
3093 MOZ_ASSERT(!firstFrame
->mJustificationAssignment
.mGapsAtStart
);
3094 nsIFrame
* rubyBase
= FindNearestRubyBaseAncestor(firstFrame
->mFrame
);
3095 if (rubyBase
&& IsRubyAlignSpaceAround(rubyBase
)) {
3096 firstFrame
->mJustificationAssignment
.mGapsAtStart
= 1;
3100 PerFrameData
* lastFrame
= computeState
.mLastParticipant
;
3101 if (lastFrame
->mFrame
->Style()->ShouldSuppressLineBreak()) {
3102 MOZ_ASSERT(!lastFrame
->mJustificationAssignment
.mGapsAtEnd
);
3103 nsIFrame
* rubyBase
= FindNearestRubyBaseAncestor(lastFrame
->mFrame
);
3104 if (rubyBase
&& IsRubyAlignSpaceAround(rubyBase
)) {
3105 lastFrame
->mJustificationAssignment
.mGapsAtEnd
= 1;
3112 if (!isSVG
&& doTextAlign
) {
3113 switch (textAlign
) {
3114 case StyleTextAlign::Justify
: {
3115 int32_t opportunities
=
3116 psd
->mFrame
->mJustificationInfo
.mInnerOpportunities
;
3117 if (opportunities
> 0) {
3118 int32_t gaps
= opportunities
* 2 + additionalGaps
;
3119 JustificationApplicationState
applyState(gaps
, remainingISize
);
3121 // Apply the justification, and make sure to update our linebox
3122 // width to account for it.
3123 aLine
->ExpandBy(ApplyFrameJustification(psd
, applyState
),
3124 ContainerSizeForSpan(psd
));
3126 MOZ_ASSERT(applyState
.mGaps
.mHandled
== applyState
.mGaps
.mCount
,
3127 "Unprocessed justification gaps");
3129 applyState
.mWidth
.mConsumed
== applyState
.mWidth
.mAvailable
,
3130 "Unprocessed justification width");
3133 // Fall through to the default case if we could not justify to fill
3138 case StyleTextAlign::Start
:
3139 case StyleTextAlign::Char
:
3140 // default alignment is to start edge so do nothing.
3141 // Char is for tables so treat as start if we find it in block layout.
3144 case StyleTextAlign::Left
:
3145 case StyleTextAlign::MozLeft
:
3146 if (lineWM
.IsBidiRTL()) {
3147 dx
= remainingISize
;
3151 case StyleTextAlign::Right
:
3152 case StyleTextAlign::MozRight
:
3153 if (lineWM
.IsBidiLTR()) {
3154 dx
= remainingISize
;
3158 case StyleTextAlign::End
:
3159 dx
= remainingISize
;
3162 case StyleTextAlign::Center
:
3163 case StyleTextAlign::MozCenter
:
3164 dx
= remainingISize
/ 2;
3170 ExpandInlineRubyBoxes(mRootSpan
);
3173 if (mPresContext
->BidiEnabled() &&
3174 (!mPresContext
->IsVisualMode() || lineWM
.IsBidiRTL())) {
3175 PerFrameData
* startFrame
= psd
->mFirstFrame
;
3176 MOZ_ASSERT(startFrame
, "empty line?");
3177 if (startFrame
->mIsMarker
) {
3178 // ::marker shouldn't participate in bidi reordering.
3179 startFrame
= startFrame
->mNext
;
3180 MOZ_ASSERT(startFrame
, "no frame after ::marker?");
3181 MOZ_ASSERT(!startFrame
->mIsMarker
, "multiple ::markers?");
3183 nsBidiPresUtils::ReorderFrames(startFrame
->mFrame
, aLine
->GetChildCount(),
3184 lineWM
, mContainerSize
,
3185 psd
->mIStart
+ mTextIndent
+ dx
);
3187 if (startFrame
->mFrame
->IsLineFrame()) {
3188 // If startFrame is a ::first-line frame, the mIStart and mTextIndent
3189 // offsets will already have been applied to its position. But we still
3190 // need to apply the text-align adjustment |dx| to its position.
3191 startFrame
->mBounds
.IStart(lineWM
) += dx
;
3192 startFrame
->mFrame
->SetRect(lineWM
, startFrame
->mBounds
,
3193 ContainerSizeForSpan(psd
));
3195 aLine
->IndentBy(dx
, ContainerSize());
3198 for (PerFrameData
* pfd
= psd
->mFirstFrame
; pfd
; pfd
= pfd
->mNext
) {
3199 pfd
->mBounds
.IStart(lineWM
) += dx
;
3200 pfd
->mFrame
->SetRect(lineWM
, pfd
->mBounds
, ContainerSizeForSpan(psd
));
3202 aLine
->IndentBy(dx
, ContainerSize());
3206 // This method applies any relative positioning to the given frame.
3207 void nsLineLayout::ApplyRelativePositioning(PerFrameData
* aPFD
) {
3208 if (!aPFD
->mRelativePos
) {
3212 nsIFrame
* frame
= aPFD
->mFrame
;
3213 WritingMode frameWM
= aPFD
->mWritingMode
;
3214 LogicalPoint origin
= frame
->GetLogicalPosition(ContainerSize());
3215 // right and bottom are handled by
3216 // ReflowInput::ComputeRelativeOffsets
3217 ReflowInput::ApplyRelativePositioning(frame
, frameWM
, aPFD
->mOffsets
, &origin
,
3219 frame
->SetPosition(frameWM
, origin
, ContainerSize());
3222 // This method do relative positioning for ruby annotations.
3223 void nsLineLayout::RelativePositionAnnotations(PerSpanData
* aRubyPSD
,
3224 OverflowAreas
& aOverflowAreas
) {
3225 MOZ_ASSERT(aRubyPSD
->mFrame
->mFrame
->IsRubyFrame());
3226 for (PerFrameData
* pfd
= aRubyPSD
->mFirstFrame
; pfd
; pfd
= pfd
->mNext
) {
3227 MOZ_ASSERT(pfd
->mFrame
->IsRubyBaseContainerFrame());
3228 for (PerFrameData
* rtc
= pfd
->mNextAnnotation
; rtc
;
3229 rtc
= rtc
->mNextAnnotation
) {
3230 nsIFrame
* rtcFrame
= rtc
->mFrame
;
3231 MOZ_ASSERT(rtcFrame
->IsRubyTextContainerFrame());
3232 ApplyRelativePositioning(rtc
);
3233 OverflowAreas rtcOverflowAreas
;
3234 RelativePositionFrames(rtc
->mSpan
, rtcOverflowAreas
);
3235 aOverflowAreas
.UnionWith(rtcOverflowAreas
+ rtcFrame
->GetPosition());
3240 void nsLineLayout::RelativePositionFrames(PerSpanData
* psd
,
3241 OverflowAreas
& aOverflowAreas
) {
3242 OverflowAreas overflowAreas
;
3243 WritingMode wm
= psd
->mWritingMode
;
3244 if (psd
!= mRootSpan
) {
3245 // The span's overflow areas come in three parts:
3246 // -- this frame's width and height
3247 // -- pfd->mOverflowAreas, which is the area of a ::marker or the union
3248 // of a relatively positioned frame's absolute children
3249 // -- the bounds of all inline descendants
3250 // The former two parts are computed right here, we gather the descendants
3252 // At this point psd->mFrame->mBounds might be out of date since
3253 // bidi reordering can move and resize the frames. So use the frame's
3254 // rect instead of mBounds.
3255 nsRect
adjustedBounds(nsPoint(0, 0), psd
->mFrame
->mFrame
->GetSize());
3257 overflowAreas
.ScrollableOverflow().UnionRect(
3258 psd
->mFrame
->mOverflowAreas
.ScrollableOverflow(), adjustedBounds
);
3259 overflowAreas
.InkOverflow().UnionRect(
3260 psd
->mFrame
->mOverflowAreas
.InkOverflow(), adjustedBounds
);
3262 LogicalRect
rect(wm
, psd
->mIStart
, mBStartEdge
, psd
->mICoord
- psd
->mIStart
,
3264 // The minimum combined area for the frames that are direct
3265 // children of the block starts at the upper left corner of the
3266 // line and is sized to match the size of the line's bounding box
3267 // (the same size as the values returned from VerticalAlignFrames)
3268 overflowAreas
.InkOverflow() = rect
.GetPhysicalRect(wm
, ContainerSize());
3269 overflowAreas
.ScrollableOverflow() = overflowAreas
.InkOverflow();
3272 for (PerFrameData
* pfd
= psd
->mFirstFrame
; pfd
; pfd
= pfd
->mNext
) {
3273 nsIFrame
* frame
= pfd
->mFrame
;
3275 // Adjust the origin of the frame
3276 ApplyRelativePositioning(pfd
);
3278 // We must position the view correctly before positioning its
3279 // descendants so that widgets are positioned properly (since only
3280 // some views have widgets).
3281 if (frame
->HasView())
3282 nsContainerFrame::SyncFrameViewAfterReflow(
3283 mPresContext
, frame
, frame
->GetView(),
3284 pfd
->mOverflowAreas
.InkOverflow(),
3285 nsIFrame::ReflowChildFlags::NoSizeView
);
3287 // Note: the combined area of a child is in its coordinate
3288 // system. We adjust the childs combined area into our coordinate
3289 // system before computing the aggregated value by adding in
3290 // <b>x</b> and <b>y</b> which were computed above.
3293 // Compute a new combined area for the child span before
3294 // aggregating it into our combined area.
3295 RelativePositionFrames(pfd
->mSpan
, r
);
3297 r
= pfd
->mOverflowAreas
;
3298 if (pfd
->mIsTextFrame
) {
3299 // We need to recompute overflow areas in four cases:
3300 // (1) When PFD_RECOMPUTEOVERFLOW is set due to trimming
3301 // (2) When there are text decorations, since we can't recompute the
3302 // overflow area until Reflow and VerticalAlignLine have finished
3303 // (3) When there are text emphasis marks, since the marks may be
3304 // put further away if the text is inside ruby.
3305 // (4) When there are text strokes
3306 if (pfd
->mRecomputeOverflow
||
3307 frame
->Style()->HasTextDecorationLines() ||
3308 frame
->StyleText()->HasEffectiveTextEmphasis() ||
3309 frame
->StyleText()->HasWebkitTextStroke()) {
3310 nsTextFrame
* f
= static_cast<nsTextFrame
*>(frame
);
3311 r
= f
->RecomputeOverflow(mBlockReflowInput
->mFrame
);
3313 frame
->FinishAndStoreOverflow(r
, frame
->GetSize());
3316 // If we have something that's not an inline but with a complex frame
3317 // hierarchy inside that contains views, they need to be
3319 // All descendant views must be repositioned even if this frame
3320 // does have a view in case this frame's view does not have a
3321 // widget and some of the descendant views do have widgets --
3322 // otherwise the widgets won't be repositioned.
3323 nsContainerFrame::PositionChildViews(frame
);
3326 // Do this here (rather than along with setting the overflow rect
3327 // below) so we get leaf frames as well. No need to worry
3328 // about the root span, since it doesn't have a frame.
3329 if (frame
->HasView())
3330 nsContainerFrame::SyncFrameViewAfterReflow(
3331 mPresContext
, frame
, frame
->GetView(), r
.InkOverflow(),
3332 nsIFrame::ReflowChildFlags::NoMoveView
);
3334 overflowAreas
.UnionWith(r
+ frame
->GetPosition());
3337 // Also compute relative position in the annotations.
3338 if (psd
->mFrame
->mFrame
->IsRubyFrame()) {
3339 RelativePositionAnnotations(psd
, overflowAreas
);
3342 // If we just computed a spans combined area, we need to update its
3344 if (psd
!= mRootSpan
) {
3345 PerFrameData
* spanPFD
= psd
->mFrame
;
3346 nsIFrame
* frame
= spanPFD
->mFrame
;
3347 frame
->FinishAndStoreOverflow(overflowAreas
, frame
->GetSize());
3349 aOverflowAreas
= overflowAreas
;