diff --git a/mozilla/content/base/src/nsTextFragment.cpp b/mozilla/content/base/src/nsTextFragment.cpp index 34d04f31390..e83d7fa1064 100644 --- a/mozilla/content/base/src/nsTextFragment.cpp +++ b/mozilla/content/base/src/nsTextFragment.cpp @@ -249,6 +249,16 @@ nsTextFragment::AppendTo(nsAString& aString) const } } +void +nsTextFragment::AppendTo(nsAString& aString, PRInt32 aOffset, PRInt32 aLength) const +{ + if (mState.mIs2b) { + aString.Append(m2b + aOffset, aLength); + } else { + AppendASCIItoUTF16(Substring(m1b + aOffset, m1b + aOffset + aLength), aString); + } +} + void nsTextFragment::CopyTo(PRUnichar *aDest, PRInt32 aOffset, PRInt32 aCount) { diff --git a/mozilla/content/base/src/nsTextFragment.h b/mozilla/content/base/src/nsTextFragment.h index 31c5b9ef534..926b3c80d86 100644 --- a/mozilla/content/base/src/nsTextFragment.h +++ b/mozilla/content/base/src/nsTextFragment.h @@ -160,6 +160,13 @@ public: */ void AppendTo(nsAString& aString) const; + /** + * Append a substring of the contents of this string fragment to aString. + * @param aOffset where to start the substring in this text fragment + * @param aLength the length of the substring + */ + void AppendTo(nsAString& aString, PRInt32 aOffset, PRInt32 aLength) const; + /** * Make a copy of the fragments contents starting at offset for * count characters. The offset and count will be adjusted to diff --git a/mozilla/layout/base/nsDisplayList.cpp b/mozilla/layout/base/nsDisplayList.cpp index dd89ebf0e08..3a71e982839 100644 --- a/mozilla/layout/base/nsDisplayList.cpp +++ b/mozilla/layout/base/nsDisplayList.cpp @@ -95,18 +95,11 @@ DestroyRectFunc(void* aFrame, delete NS_STATIC_CAST(nsRect*, aPropertyValue); } -static nsIFrame* GetParentOrPlaceholderFor(nsFrameManager* aFrameManager, - nsIFrame* aFrame) { - if (aFrame->GetStateBits() & NS_FRAME_OUT_OF_FLOW) { - return aFrameManager->GetPlaceholderFrameFor(aFrame); - } - return aFrame->GetParent(); -} - static void MarkFrameForDisplay(nsIFrame* aFrame, nsIFrame* aStopAtFrame) { nsFrameManager* frameManager = aFrame->GetPresContext()->PresShell()->FrameManager(); - for (nsIFrame* f = aFrame; f; f = GetParentOrPlaceholderFor(frameManager, f)) { + for (nsIFrame* f = aFrame; f; + f = nsLayoutUtils::GetParentOrPlaceholderFor(frameManager, f)) { if (f->GetStateBits() & NS_FRAME_FORCE_DISPLAY_LIST_DESCEND_INTO) return; f->AddStateBits(NS_FRAME_FORCE_DISPLAY_LIST_DESCEND_INTO); @@ -135,7 +128,8 @@ static void UnmarkFrameForDisplay(nsIFrame* aFrame) { nsFrameManager* frameManager = aFrame->GetPresContext()->PresShell()->FrameManager(); - for (nsIFrame* f = aFrame; f; f = GetParentOrPlaceholderFor(frameManager, f)) { + for (nsIFrame* f = aFrame; f; + f = nsLayoutUtils::GetParentOrPlaceholderFor(frameManager, f)) { if (!(f->GetStateBits() & NS_FRAME_FORCE_DISPLAY_LIST_DESCEND_INTO)) return; f->RemoveStateBits(NS_FRAME_FORCE_DISPLAY_LIST_DESCEND_INTO); diff --git a/mozilla/layout/base/nsLayoutUtils.cpp b/mozilla/layout/base/nsLayoutUtils.cpp index 13ec7ae58a5..4e0c0d937d0 100644 --- a/mozilla/layout/base/nsLayoutUtils.cpp +++ b/mozilla/layout/base/nsLayoutUtils.cpp @@ -56,6 +56,7 @@ #include "nsGUIEvent.h" #include "nsDisplayList.h" #include "nsRegion.h" +#include "nsFrameManager.h" #ifdef MOZ_SVG_FOREIGNOBJECT #include "nsSVGForeignObjectFrame.h" @@ -995,3 +996,69 @@ nsLayoutUtils::GetFontMetricsForFrame(nsIFrame* aFrame, sc->GetStyleVisibility()->mLangGroup, *aFontMetrics); } + +nsIFrame* +nsLayoutUtils::GetParentOrPlaceholderFor(nsFrameManager* aFrameManager, + nsIFrame* aFrame) +{ + if (aFrame->GetStateBits() & NS_FRAME_OUT_OF_FLOW) + return aFrameManager->GetPlaceholderFrameFor(aFrame); + return aFrame->GetParent(); +} + +nsIFrame* +nsLayoutUtils::GetClosestCommonAncestorViaPlaceholders(nsIFrame* aFrame1, + nsIFrame* aFrame2, + nsIFrame* aKnownCommonAncestorHint) +{ + NS_PRECONDITION(aFrame1, "aFrame1 must not be null"); + NS_PRECONDITION(aFrame2, "aFrame2 must not be null"); + + nsPresContext* presContext = aFrame1->GetPresContext(); + if (presContext != aFrame2->GetPresContext()) { + // different documents, no common ancestor + return nsnull; + } + nsFrameManager* frameManager = presContext->PresShell()->FrameManager(); + + nsAutoVoidArray frame1Ancestors; + nsIFrame* f1; + for (f1 = aFrame1; f1 && f1 != aKnownCommonAncestorHint; + f1 = GetParentOrPlaceholderFor(frameManager, f1)) { + frame1Ancestors.AppendElement(f1); + } + if (!f1 && aKnownCommonAncestorHint) { + // So, it turns out aKnownCommonAncestorHint was not an ancestor of f1. Oops. + // Never mind. We can continue as if aKnownCommonAncestorHint was null. + aKnownCommonAncestorHint = nsnull; + } + + nsAutoVoidArray frame2Ancestors; + nsIFrame* f2; + for (f2 = aFrame2; f2 && f2 != aKnownCommonAncestorHint; + f2 = GetParentOrPlaceholderFor(frameManager, f2)) { + frame2Ancestors.AppendElement(f2); + } + if (!f2 && aKnownCommonAncestorHint) { + // So, it turns out aKnownCommonAncestorHint was not an ancestor of f2. + // We need to retry with no common ancestor hint. + return GetClosestCommonAncestorViaPlaceholders(aFrame1, aFrame2, nsnull); + } + + // now frame1Ancestors and frame2Ancestors give us the parent frame chain + // up to aKnownCommonAncestorHint, or if that is null, up to and including + // the root frame. We need to walk from the end (i.e., the top of the + // frame (sub)tree) down to aFrame1/aFrame2 looking for the first difference. + nsIFrame* lastCommonFrame = aKnownCommonAncestorHint; + PRInt32 last1 = frame1Ancestors.Count() - 1; + PRInt32 last2 = frame2Ancestors.Count() - 1; + while (last1 >= 0 && last2 >= 0) { + nsIFrame* frame1 = NS_STATIC_CAST(nsIFrame*, frame1Ancestors.ElementAt(last1)); + if (frame1 != frame2Ancestors.ElementAt(last2)) + break; + lastCommonFrame = frame1; + last1--; + last2--; + } + return lastCommonFrame; +} diff --git a/mozilla/layout/base/nsLayoutUtils.h b/mozilla/layout/base/nsLayoutUtils.h index 90b2484e0f8..8a1e7de4c07 100644 --- a/mozilla/layout/base/nsLayoutUtils.h +++ b/mozilla/layout/base/nsLayoutUtils.h @@ -431,6 +431,27 @@ public: */ static nsresult GetFontMetricsForFrame(nsIFrame* aFrame, nsIFontMetrics** aFontMetrics); + + /** + * If aFrame is an out of flow frame, return its placeholder, otherwise + * return its parent. + */ + static nsIFrame* GetParentOrPlaceholderFor(nsFrameManager* aFrameManager, + nsIFrame* aFrame); + + /** + * Find the closest common ancestor of aFrame1 and aFrame2, following + * out of flow frames to their placeholders instead of their parents. Returns + * nsnull if the frames are in different frame trees. + * + * @param aKnownCommonAncestorHint a frame that is believed to be on the + * ancestor chain of both aFrame1 and aFrame2. If null, or a frame that is + * not in fact on both ancestor chains, then this function will still return + * the correct result, but it will be slower. + */ + static nsIFrame* + GetClosestCommonAncestorViaPlaceholders(nsIFrame* aFrame1, nsIFrame* aFrame2, + nsIFrame* aKnownCommonAncestorHint); }; #endif // nsLayoutUtils_h__ diff --git a/mozilla/layout/generic/nsBlockFrame.cpp b/mozilla/layout/generic/nsBlockFrame.cpp index c512653a557..91b82f9fe5c 100644 --- a/mozilla/layout/generic/nsBlockFrame.cpp +++ b/mozilla/layout/generic/nsBlockFrame.cpp @@ -3811,6 +3811,8 @@ nsBlockFrame::ReflowInlineFrames(nsBlockReflowState& aState, PRBool movedPastFloat = PR_FALSE; do { PRBool allowPullUp = aTryPull; + nsIContent* forceBreakInContent = nsnull; + PRInt32 forceBreakOffset = -1; do { nsSpaceManager::SavedState spaceManagerState; aState.mReflowState.mSpaceManager->PushState(&spaceManagerState); @@ -3827,17 +3829,30 @@ nsBlockFrame::ReflowInlineFrames(nsBlockReflowState& aState, &aState.mReflowState, aState.GetFlag(BRS_COMPUTEMAXELEMENTWIDTH)); lineLayout.Init(&aState, aState.mMinLineHeight, aState.mLineNumber); + if (forceBreakInContent) { + lineLayout.ForceBreakAtPosition(forceBreakInContent, forceBreakOffset); + } rv = DoReflowInlineFrames(aState, lineLayout, aLine, aKeepReflowGoing, &lineReflowStatus, aUpdateMaximumWidth, aDamageDirtyArea, allowPullUp); lineLayout.EndLineReflow(); - + if (LINE_REFLOW_REDO_NO_PULL == lineReflowStatus || LINE_REFLOW_REDO_NEXT_BAND == lineReflowStatus) { + if (lineLayout.NeedsBackup()) { + NS_ASSERTION(!forceBreakInContent, "Backuping up twice; this should never be necessary"); + // If there is no saved break position, then this will set + // set forceBreakInContent to null and we won't back up, which is + // correct. + forceBreakInContent = lineLayout.GetLastOptionalBreakPosition(&forceBreakOffset); + } else { + forceBreakInContent = nsnull; + } // restore the space manager state aState.mReflowState.mSpaceManager->PopState(&spaceManagerState); - // Clear out below-current-line-floats + // Clear out float lists + aState.mCurrentLineFloats.DeleteAll(); aState.mBelowCurrentLineFloats.DeleteAll(); } @@ -3887,6 +3902,13 @@ nsBlockFrame::PushTruncatedPlaceholderLine(nsBlockReflowState& aState, aState.mReflowStatus = NS_FRAME_NOT_COMPLETE; } +#ifdef DEBUG +static const char* LineReflowStatusNames[] = { + "LINE_REFLOW_OK", "LINE_REFLOW_STOP", "LINE_REFLOW_REDO_NO_PULL", + "LINE_REFLOW_REDO_NEXT_BAND", "LINE_REFLOW_TRUNCATED" +}; +#endif + nsresult nsBlockFrame::DoReflowInlineFrames(nsBlockReflowState& aState, nsLineLayout& aLineLayout, @@ -3953,9 +3975,18 @@ nsBlockFrame::DoReflowInlineFrames(nsBlockReflowState& aState, // continuations PRBool isContinuingPlaceholders = PR_FALSE; + if (impactedByFloats) { + // There is a soft break opportunity at the start of the line, because + // we can always move this line down below float(s). + if (aLineLayout.NotifyOptionalBreakPosition(frame->GetContent(), 0)) { + lineReflowStatus = LINE_REFLOW_REDO_NEXT_BAND; + } + } + // need to repeatedly call GetChildCount here, because the child // count can change during the loop! - for (i = 0; i < aLine->GetChildCount(); i++) { + for (i = 0; LINE_REFLOW_OK == lineReflowStatus && i < aLine->GetChildCount(); + i++, frame = frame->GetNextSibling()) { if (IsContinuationPlaceholder(frame)) { isContinuingPlaceholders = PR_TRUE; } @@ -3986,9 +4017,7 @@ nsBlockFrame::DoReflowInlineFrames(nsBlockReflowState& aState, PushTruncatedPlaceholderLine(aState, aLine, lastPlaceholder, *aKeepReflowGoing); } } - break; } - frame = frame->GetNextSibling(); } // Don't pull up new frames into lines with continuation placeholders @@ -4024,6 +4053,20 @@ nsBlockFrame::DoReflowInlineFrames(nsBlockReflowState& aState, } } + if ((lineReflowStatus == LINE_REFLOW_STOP || lineReflowStatus == LINE_REFLOW_OK) && + !aLineLayout.HaveForcedBreakPosition() && aLineLayout.NeedsBackup()) { + // We need to try backing up to before a text run + PRInt32 offset; + nsIContent* breakContent = aLineLayout.GetLastOptionalBreakPosition(&offset); + if (breakContent) { + // We can back up! + lineReflowStatus = LINE_REFLOW_REDO_NO_PULL; + } + } else { + // Don't try to force any breaking if we are going to retry + aLineLayout.ClearOptionalBreakPosition(); + } + if (LINE_REFLOW_REDO_NEXT_BAND == lineReflowStatus) { // This happens only when we have a line that is impacted by // floats and the first element in the line doesn't fit with @@ -4084,6 +4127,11 @@ nsBlockFrame::DoReflowInlineFrames(nsBlockReflowState& aState, } } } +#ifdef DEBUG + if (gNoisyReflow) { + printf("Line reflow status = %s\n", LineReflowStatusNames[lineReflowStatus]); + } +#endif *aLineReflowStatus = lineReflowStatus; return rv; diff --git a/mozilla/layout/generic/nsHTMLCanvasFrame.cpp b/mozilla/layout/generic/nsHTMLCanvasFrame.cpp index 657b3acd8aa..0257e02725a 100644 --- a/mozilla/layout/generic/nsHTMLCanvasFrame.cpp +++ b/mozilla/layout/generic/nsHTMLCanvasFrame.cpp @@ -218,20 +218,6 @@ nsHTMLCanvasFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder, nsISelectionDisplay::DISPLAY_IMAGES); } -NS_IMETHODIMP -nsHTMLCanvasFrame::CanContinueTextRun(PRBool& aContinueTextRun) const -{ - // stolen from nsImageFrame.cpp - // images really CAN continue text runs, but the textFrame needs to be - // educated before we can indicate that it can. For now, we handle the fixing up - // of max element widths in nsLineLayout::VerticalAlignFrames, but hopefully - // this can be eliminated and the textFrame can be convinced to handle inlines - // that take up space in text runs. - - aContinueTextRun = PR_FALSE; - return NS_OK; -} - NS_IMETHODIMP nsHTMLCanvasFrame::GetContentForEvent(nsPresContext* aPresContext, nsEvent* aEvent, diff --git a/mozilla/layout/generic/nsHTMLCanvasFrame.h b/mozilla/layout/generic/nsHTMLCanvasFrame.h index 4b9b13afcd9..b7e3d6d0a9b 100644 --- a/mozilla/layout/generic/nsHTMLCanvasFrame.h +++ b/mozilla/layout/generic/nsHTMLCanvasFrame.h @@ -65,9 +65,6 @@ public: const nsHTMLReflowState& aReflowState, nsReflowStatus& aStatus); - NS_IMETHOD CanContinueTextRun(PRBool& aContinueTextRun) const; - - NS_IMETHOD GetContentForEvent(nsPresContext* aPresContext, nsEvent* aEvent, nsIContent** aContent); diff --git a/mozilla/layout/generic/nsImageFrame.cpp b/mozilla/layout/generic/nsImageFrame.cpp index 506b68d5730..e87ea34a494 100644 --- a/mozilla/layout/generic/nsImageFrame.cpp +++ b/mozilla/layout/generic/nsImageFrame.cpp @@ -1628,20 +1628,6 @@ nsImageFrame::GetAnchorHREFTargetAndNode(nsIURI** aHref, nsString& aTarget, return status; } -NS_IMETHODIMP -nsImageFrame::CanContinueTextRun(PRBool& aContinueTextRun) const -{ - // images really CAN continue text runs, but the textFrame needs to be - // educated before we can indicate that it can. For now, we handle the fixing up - // of max element widths in nsLineLayout::VerticalAlignFrames, but hopefully - // this can be eliminated and the textFrame can be convinced to handle inlines - // that take up space in text runs. - - aContinueTextRun = PR_FALSE; - return NS_OK; -} - - NS_IMETHODIMP nsImageFrame::GetContentForEvent(nsPresContext* aPresContext, nsEvent* aEvent, diff --git a/mozilla/layout/generic/nsImageFrame.h b/mozilla/layout/generic/nsImageFrame.h index a61d9c3b6c7..044d1df10b2 100644 --- a/mozilla/layout/generic/nsImageFrame.h +++ b/mozilla/layout/generic/nsImageFrame.h @@ -110,9 +110,6 @@ public: const nsHTMLReflowState& aReflowState, nsReflowStatus& aStatus); - NS_IMETHOD CanContinueTextRun(PRBool& aContinueTextRun) const; - - NS_IMETHOD GetContentForEvent(nsPresContext* aPresContext, nsEvent* aEvent, nsIContent** aContent); diff --git a/mozilla/layout/generic/nsInlineFrame.cpp b/mozilla/layout/generic/nsInlineFrame.cpp index db2a2962ec8..e289a9924f2 100644 --- a/mozilla/layout/generic/nsInlineFrame.cpp +++ b/mozilla/layout/generic/nsInlineFrame.cpp @@ -680,7 +680,9 @@ nsInlineFrame::ReflowInlineFrame(nsPresContext* aPresContext, nsLineLayout* lineLayout = aReflowState.mLineLayout; PRBool reflowingFirstLetter = lineLayout->GetFirstLetterStyleOK(); PRBool pushedFrame; - nsresult rv = lineLayout->ReflowFrame(aFrame, aStatus, nsnull, pushedFrame); + nsresult rv = + lineLayout->ReflowFrame(aFrame, aStatus, nsnull, pushedFrame); + /* This next block is for bug 28811 Test the child frame for %-awareness, and mark this frame with a bit if it is %-aware. diff --git a/mozilla/layout/generic/nsLineLayout.cpp b/mozilla/layout/generic/nsLineLayout.cpp index 7e67db9224a..289365f67d8 100644 --- a/mozilla/layout/generic/nsLineLayout.cpp +++ b/mozilla/layout/generic/nsLineLayout.cpp @@ -156,11 +156,15 @@ nsLineLayout::nsLineLayout(nsPresContext* aPresContext, : mPresContext(aPresContext), mSpaceManager(aSpaceManager), mBlockReflowState(aOuterReflowState), + mLastOptionalBreakContent(nsnull), + mForceBreakContent(nsnull), + mLastOptionalBreakContentOffset(-1), + mForceBreakContentOffset(-1), + mTrailingTextFrame(nsnull), mBlockRS(nsnull),/* XXX temporary */ mMinLineHeight(0), mComputeMaxElementWidth(aComputeMaxElementWidth), - mTextIndent(0), - mWordFrames(0) + mTextIndent(0) { MOZ_COUNT_CTOR(nsLineLayout); @@ -195,8 +199,6 @@ nsLineLayout::~nsLineLayout() NS_ASSERTION(nsnull == mRootSpan, "bad line-layout user"); - delete mWordFrames; // operator delete for this class just returns - // PL_FreeArenaPool takes our memory and puts in on a global free list so // that the next time an arena makes an allocation it will not have to go // all the way down to malloc. This is desirable as this class is created @@ -210,22 +212,6 @@ nsLineLayout::~nsLineLayout() PL_FinishArenaPool(&mArena); } -void* -nsLineLayout::ArenaDeque::operator new(size_t aSize, PLArenaPool &aPool) -{ - void *mem; - - PL_ARENA_ALLOCATE(mem, &aPool, aSize); - return mem; -} - -PRBool nsLineLayout::AllocateDeque() -{ - mWordFrames = new(mArena) ArenaDeque; - - return mWordFrames != nsnull; -} - // Find out if the frame has a non-null prev-in-flow, i.e., whether it // is a continuation. inline PRBool @@ -282,8 +268,6 @@ nsLineLayout::BeginLineReflow(nscoord aX, nscoord aY, mSpanDepth = 0; mMaxTopBoxHeight = mMaxBottomBoxHeight = 0; - ForgetWordFrames(); - PerSpanData* psd; NewPerSpanData(&psd); mCurrentSpan = mRootSpan = psd; @@ -313,7 +297,7 @@ nsLineLayout::BeginLineReflow(nscoord aX, nscoord aY, // If this is the first line of a block then see if the text-indent // property amounts to anything. - + if (0 == mLineNumber && !HasPrevInFlow(mBlockReflowState->frame)) { nscoord indent = 0; nsStyleUnit unit = mStyleText->mTextIndent.GetUnit(); @@ -936,17 +920,16 @@ nsLineLayout::ReflowFrame(nsIFrame* aFrame, // We want to guarantee that we always make progress when // formatting. Therefore, if the object being placed on the line is - // too big for the line, but it is the only thing on the line - // (including counting floats) then we go ahead and place it - // anyway. Its also true that if the object is a part of a larger - // object (a multiple frame word) then we will place it on the line - // too. + // too big for the line, but it is the only thing on the line and is not + // impacted by a float, then we go ahead and place it anyway. (If the line + // is impacted by one or more floats, then it is safe to break because + // we can move the line down below float(s).) // // Capture this state *before* we reflow the frame in case it clears // the state out. We need to know how to treat the current frame // when breaking. - PRBool notSafeToBreak = CanPlaceFloatNow() || InWord(); - + PRBool notSafeToBreak = CanPlaceFloatNow() && !GetFlag(LL_IMPACTEDBYFLOATS); + // Apply start margins (as appropriate) to the frame computing the // new starting x,y coordinates for the frame. ApplyStartMargin(pfd, reflowState); @@ -991,32 +974,36 @@ nsLineLayout::ReflowFrame(nsIFrame* aFrame, #endif // IBMBIDI nsIAtom* frameType = aFrame->GetType(); + + PRInt32 savedOptionalBreakOffset; + nsIContent* savedOptionalBreakContent = + GetLastOptionalBreakPosition(&savedOptionalBreakOffset); rv = aFrame->Reflow(mPresContext, metrics, reflowState, aReflowStatus); if (NS_FAILED(rv)) { NS_WARNING( "Reflow of frame failed in nsLineLayout" ); return rv; } - + pfd->mJustificationNumSpaces = mTextJustificationNumSpaces; pfd->mJustificationNumLetters = mTextJustificationNumLetters; // XXX See if the frame is a placeholderFrame and if it is process // the float. + PRBool placedFloat = PR_FALSE; if (frameType) { if (nsLayoutAtoms::placeholderFrame == frameType) { pfd->SetFlag(PFD_SKIPWHENTRIMMINGWHITESPACE, PR_TRUE); nsIFrame* outOfFlowFrame = nsLayoutUtils::GetFloatFromPlaceholder(aFrame); if (outOfFlowFrame) { nsPlaceholderFrame* placeholder = NS_STATIC_CAST(nsPlaceholderFrame*, aFrame); - PRBool didPlace; if (eReflowReason_Incremental == reason) { - didPlace = InitFloat(placeholder, aReflowStatus); + placedFloat = InitFloat(placeholder, aReflowStatus); } else { - didPlace = AddFloat(placeholder, aReflowStatus); + placedFloat = AddFloat(placeholder, aReflowStatus); } - if (!didPlace) { + if (!placedFloat) { aReflowStatus = NS_INLINE_LINE_BREAK_BEFORE(); } if (outOfFlowFrame->GetType() == nsLayoutAtoms::letterFrame) { @@ -1164,9 +1151,15 @@ nsLineLayout::ReflowFrame(nsIFrame* aFrame, } } + // Check whether this frame breaks up text runs. All frames break up text + // runs (hence return false here) except for text frames and inline containers. + PRBool continuingTextRun; + aFrame->CanContinueTextRun(continuingTextRun); + // See if we can place the frame. If we can't fit it, then we // return now. - if (CanPlaceFrame(pfd, reflowState, notSafeToBreak, metrics, aReflowStatus)) { + if (CanPlaceFrame(pfd, reflowState, notSafeToBreak, continuingTextRun, + metrics, aReflowStatus)) { // Place the frame, updating aBounds with the final size and // location. Then apply the bottom+right margins (as // appropriate) to the frame. @@ -1178,16 +1171,34 @@ nsLineLayout::ReflowFrame(nsIFrame* aFrame, // so do most of it now. VerticalAlignFrames(span); } + + if (!continuingTextRun) { + SetFlag(LL_INWORD, PR_FALSE); + mTrailingTextFrame = nsnull; + if (!psd->mNoWrap && (!CanPlaceFloatNow() || placedFloat)) { + // record soft break opportunity after this content that can't be + // part of a text run. This is not a text frame so we know + // that offset PR_INT32_MAX means "after the content". + if (NotifyOptionalBreakPosition(aFrame->GetContent(), PR_INT32_MAX)) { + // If this returns true then we are being told to actually break here. + aReflowStatus = NS_INLINE_LINE_BREAK_AFTER(aReflowStatus); + } + } + } } else { PushFrame(aFrame); aPushedFrame = PR_TRUE; + // Undo any saved break positions that the frame might have told us about, + // since we didn't end up placing it + RestoreSavedBreakPosition(savedOptionalBreakContent, + savedOptionalBreakOffset); } } else { PushFrame(aFrame); } - + #ifdef REALLY_NOISY_REFLOW nsFrame::IndentBy(stdout, mSpanDepth); printf("End ReflowFrame "); @@ -1262,6 +1273,7 @@ PRBool nsLineLayout::CanPlaceFrame(PerFrameData* pfd, const nsHTMLReflowState& aReflowState, PRBool aNotSafeToBreak, + PRBool aFrameCanContinueTextRun, nsHTMLReflowMetrics& aMetrics, nsReflowStatus& aStatus) { @@ -1297,6 +1309,9 @@ nsLineLayout::CanPlaceFrame(PerFrameData* pfd, return PR_TRUE; } + PRBool ltr = NS_STYLE_DIRECTION_LTR == aReflowState.mStyleVisibility->mDirection; + nscoord endMargin = ltr ? pfd->mMargin.right : pfd->mMargin.left; + #ifdef NOISY_CAN_PLACE_FRAME if (nsnull != psd->mFrame) { nsFrame::ListTag(stdout, psd->mFrame->mFrame); @@ -1306,13 +1321,12 @@ nsLineLayout::CanPlaceFrame(PerFrameData* pfd, } printf(": aNotSafeToBreak=%s frame=", aNotSafeToBreak ? "true" : "false"); nsFrame::ListTag(stdout, pfd->mFrame); - printf(" frameWidth=%d\n", pfd->mBounds.XMost() + rightMargin - psd->mX); + printf(" frameWidth=%d\n", pfd->mBounds.XMost() + endMargin - psd->mX); #endif // Set outside to PR_TRUE if the result of the reflow leads to the // frame sticking outside of our available area. - PRBool ltr = (NS_STYLE_DIRECTION_LTR == aReflowState.mStyleVisibility->mDirection); - PRBool outside = pfd->mBounds.XMost() + (ltr ? pfd->mMargin.right : pfd->mMargin.left) > psd->mRightEdge; + PRBool outside = pfd->mBounds.XMost() + endMargin > psd->mRightEdge; if (!outside) { // If it fits, it fits #ifdef NOISY_CAN_PLACE_FRAME @@ -1342,75 +1356,20 @@ nsLineLayout::CanPlaceFrame(PerFrameData* pfd, #endif if (aNotSafeToBreak) { - // There are no frames on the line or we are in the first word on - // the line. If the line isn't impacted by a float then the - // current frame fits. - if (!GetFlag(LL_IMPACTEDBYFLOATS)) { + // There are no frames on the line that take up width and the line is + // not impacted by floats, so we must allow the current frame to be + // placed on the line #ifdef NOISY_CAN_PLACE_FRAME - printf(" ==> not-safe and not-impacted fits: "); - while (nsnull != psd) { - printf(" ", psd, psd->mX, psd->mLeftEdge); - psd = psd->mParent; - } - printf("\n"); + printf(" ==> not-safe and not-impacted fits: "); + while (nsnull != psd) { + printf(" ", psd, psd->mX, psd->mLeftEdge); + psd = psd->mParent; + } + printf("\n"); #endif - return PR_TRUE; - } - else if (GetFlag(LL_LASTFLOATWASLETTERFRAME)) { - // Another special case: see if the float is a letter - // frame. If it is, then allow the frame next to it to fit. - if (pfd->GetFlag(PFD_ISNONEMPTYTEXTFRAME)) { - // This must be the first piece of non-empty text (because - // aNotSafeToBreak is true) or it's a piece of text that is - // part of a larger word. - pfd->SetFlag(PFD_ISSTICKY, PR_TRUE); - } - else if (pfd->mSpan) { - PerFrameData* pf = pfd->mSpan->mFirstFrame; - while (pf) { - if (pf->GetFlag(PFD_ISSTICKY)) { - // If one of the spans children was sticky then the span - // itself is sticky. - pfd->SetFlag(PFD_ISSTICKY, PR_TRUE); - } - pf = pf->mNext; - } - } - - if (pfd->GetFlag(PFD_ISSTICKY)) { -#ifdef NOISY_CAN_PLACE_FRAME - printf(" ==> last float was letter frame && frame is sticky\n"); -#endif - return PR_TRUE; - } - } + return PR_TRUE; } - - // If this is a piece of text inside a letter frame... - if (pfd->GetFlag(PFD_ISNONEMPTYTEXTFRAME)) { - if (psd->mFrame && psd->mFrame->GetFlag(PFD_ISLETTERFRAME)) { - nsIFrame* prevInFlow = psd->mFrame->mFrame->GetPrevInFlow(); - if (prevInFlow) { - nsIFrame* prevPrevInFlow = prevInFlow->GetPrevInFlow(); - if (!prevPrevInFlow) { - // And it's the first continuation of the letter frame... - // Then make sure that the text fits - return PR_TRUE; - } - } - } - } - else if (pfd->GetFlag(PFD_ISLETTERFRAME)) { - // If this is the first continuation of the letter frame... - nsIFrame* prevInFlow = pfd->mFrame->GetPrevInFlow(); - if (prevInFlow) { - nsIFrame* prevPrevInFlow = prevInFlow->GetPrevInFlow(); - if (!prevPrevInFlow) { - return PR_TRUE; - } - } - } - + // Special check for span frames if (pfd->mSpan && pfd->mSpan->mContainsFloat) { // If the span either directly or indirectly contains a float then @@ -1439,6 +1398,24 @@ nsLineLayout::CanPlaceFrame(PerFrameData* pfd, return PR_TRUE; } + if (aFrameCanContinueTextRun) { + // Let it fit, but we reserve the right to roll back + // to before the text run! Note that we usually won't get here because + // a text frame will break itself to avoid exceeding the available width. + // We'll only get here for text frames that couldn't break early enough. +#ifdef NOISY_CAN_PLACE_FRAME + printf(" ==> placing overflowing textrun, requesting backup\n"); +#endif + if (!mLastOptionalBreakContent) { + // Nowhere to roll back to, so make this fit + return PR_TRUE; + } + + // We have something to roll back to. So, signal that we will to roll back, + // and fall through to not place this frame. + SetFlag(LL_NEEDBACKUP, PR_TRUE); + } + #ifdef NOISY_CAN_PLACE_FRAME printf(" ==> didn't fit\n"); #endif @@ -3062,107 +3039,6 @@ nsLineLayout::RelativePositionFrames(PerSpanData* psd, nsRect& aCombinedArea) aCombinedArea = combinedAreaResult; } -void -nsLineLayout::ForgetWordFrame(nsIFrame* aFrame) -{ - if (mWordFrames && 0 != mWordFrames->GetSize()) { - NS_ASSERTION((void*)aFrame == mWordFrames->PeekFront(), "forget-word-frame"); - mWordFrames->PopFront(); - } -} - -nsIFrame* -nsLineLayout::FindNextText(nsPresContext* aPresContext, nsIFrame* aFrame) -{ - // Grovel through the frame hierarchy to find a text frame that is - // "adjacent" to aFrame. - - // So this is kind of funky. During reflow, overflow frames will - // have their parent pointers set up lazily. We assume that, on - // entry, aFrame has its parent pointer set correctly (as do all of - // its ancestors). Starting from that, we need to make sure that as - // we traverse through frames trying to find the next text frame, we - // leave the frames with their parent pointers set correctly, so the - // *next* time we come through here, we're good to go. - - // Build a path from the enclosing block frame down to aFrame. We'll - // use this to walk the frame tree. (XXXwaterson if I was clever, I - // wouldn't need to build this up before hand, and could incorporate - // this logic into the walking code directly.) - nsAutoVoidArray stack; - for (;;) { - stack.InsertElementAt(aFrame, 0); - - aFrame = aFrame->GetParent(); - - NS_ASSERTION(aFrame, "wow, no block frame found"); - if (! aFrame) - break; - - if (NS_STYLE_DISPLAY_INLINE != aFrame->GetStyleDisplay()->mDisplay) - break; - } - - // Using the path we've built up, walk the frame tree looking for - // the text frame that follows aFrame. - PRInt32 count; - while ((count = stack.Count()) != 0) { - PRInt32 lastIndex = count - 1; - nsIFrame* top = NS_STATIC_CAST(nsIFrame*, stack.ElementAt(lastIndex)); - - // If this is a frame that'll break a word, then bail. - PRBool canContinue; - top->CanContinueTextRun(canContinue); - if (! canContinue) - return nsnull; - - // Advance to top's next sibling - nsIFrame* next = top->GetNextSibling(); - - if (! next) { - // No more siblings. Pop the top element to walk back up the - // frame tree. - stack.RemoveElementAt(lastIndex); - continue; - } - - // We know top's parent is good, but next's might not be. So let's - // set it to be sure. - next->SetParent(top->GetParent()); - - // Save next at the top of the stack... - stack.ReplaceElementAt(next, lastIndex); - - // ...and prowl down to next's deepest child. We'll need to check - // for potential run-busters "on the way down", too. - for (;;) { - next->CanContinueTextRun(canContinue); - if (! canContinue) - return nsnull; - - nsIFrame* child = next->GetFirstChild(nsnull); - - if (! child) - break; - - stack.AppendElement(child); - next = child; - } - - // Ignore continuing frames - if (HasPrevInFlow(next)) - continue; - - // If this is a text frame, return it. - if (nsLayoutAtoms::textFrame == next->GetType()) - return next; - } - - // If we get here, then there are no more text frames in this block. - return nsnull; -} - - PRBool nsLineLayout::TreatFrameAsBlock(nsIFrame* aFrame) { diff --git a/mozilla/layout/generic/nsLineLayout.h b/mozilla/layout/generic/nsLineLayout.h index 17aafb1896b..8d1e1706231 100644 --- a/mozilla/layout/generic/nsLineLayout.h +++ b/mozilla/layout/generic/nsLineLayout.h @@ -56,6 +56,8 @@ #include "nsBlockReflowState.h" #include "plarena.h" +class nsBlockFrame; + class nsSpaceManager; class nsPlaceholderFrame; struct nsStyleText; @@ -68,14 +70,6 @@ public: PRBool aComputeMaxElementWidth); ~nsLineLayout(); - class ArenaDeque : public nsDeque - { - public: - ArenaDeque() : nsDeque(nsnull) {} - void *operator new(size_t, PLArenaPool &pool); - void operator delete(void *) {} // Dont do anything. Its Arena memory - }; - void Init(nsBlockReflowState* aState, nscoord aMinLineHeight, PRInt32 aLineNumber) { mBlockRS = aState; @@ -156,6 +150,7 @@ public: protected: #define LL_ENDSINWHITESPACE 0x00000001 #define LL_UNDERSTANDSNWHITESPACE 0x00000002 +#define LL_INWORD 0x00000004 #define LL_FIRSTLETTERSTYLEOK 0x00000008 #define LL_ISTOPOFPAGE 0x00000010 #define LL_UPDATEDBAND 0x00000020 @@ -169,7 +164,9 @@ protected: // large, e.g., if a large word-spacing is set). LL should not be misled into // placing something where the whitespace was trimmed. See bug 329987. #define LL_LINEENDSINSOFTBR 0x00000400 -#define LL_LASTFLAG LL_LINEENDSINSOFTBR +#define LL_NEEDBACKUP 0x00000800 +#define LL_LASTTEXTFRAME_WRAPPINGENABLED 0x00001000 +#define LL_LASTFLAG LL_LASTTEXTFRAME_WRAPPINGENABLED PRUint16 mFlags; @@ -215,25 +212,6 @@ public: mTextJustificationNumLetters = aNumLetters; } - void RecordWordFrame(nsIFrame* aWordFrame) { - if(mWordFrames || AllocateDeque()) - mWordFrames->Push(aWordFrame); - } - - PRBool InWord() const { - return mWordFrames && 0 != mWordFrames->GetSize(); - } - - void ForgetWordFrame(nsIFrame* aFrame); - - void ForgetWordFrames() { - if(mWordFrames) { - mWordFrames->Empty(); - } - } - - nsIFrame* FindNextText(nsPresContext* aPresContext, nsIFrame* aFrame); - PRBool CanPlaceFloatNow() const; PRBool LineIsBreakable() const; @@ -279,6 +257,44 @@ public: return mBlockRS->AddFloat(*this, aFrame, PR_FALSE, aReflowStatus); } + /** + * InWord is true when the last text frame reflowed ended in non-whitespace + * (so it has content that might form a word with subsequent text). The word width + * is the width of contiguous text up to the end of that last word, possibly + * including words from previous frames. + * + * If GetTrailingTextFrame is null then InWord will be false. + */ + PRBool InWord(nscoord* aWordWidth) { + if (!GetFlag(LL_INWORD)) + return PR_FALSE; + *aWordWidth = mWordWidth; + return PR_TRUE; + } + void SetInWord(PRBool aInWord, nscoord aWordWidth) { + SetFlag(LL_INWORD, aInWord); + mWordWidth = aWordWidth; + } + + /** + * If the last content placed on the line (not counting inline containers) + * was text, and can form a contiguous text flow with the next content to be + * placed, and is not just a frame of all-skipped whitespace, this is the + * frame for that last content ... otherwise it's null. + * + * @param aWrappingEnabled whether that text had word-wrapping enabled + * (white-space:normal or -moz-pre-wrap) + */ + nsIFrame* GetTrailingTextFrame(PRBool* aWrappingEnabled) { + *aWrappingEnabled = GetFlag(LL_LASTTEXTFRAME_WRAPPINGENABLED); + return mTrailingTextFrame; + } + void SetTrailingTextFrame(nsIFrame* aFrame, PRBool aWrappingEnabled) + { + mTrailingTextFrame = aFrame; + SetFlag(LL_LASTTEXTFRAME_WRAPPINGENABLED, aWrappingEnabled); + } + //---------------------------------------- PRBool GetFirstLetterStyleOK() const { @@ -307,12 +323,99 @@ public: nsPresContext* mPresContext; + /** + * Record where an optional break could have been placed. During line reflow, + * frames containing optional break points (e.g., whitespace in text frames) + * can call SetLastOptionalBreakPosition to record where a break could + * have been made, but wasn't because there appeared to be enough room + * to place more content on the line. For non-text frames, offset 0 means + * before the content, offset PR_INT32_MAX means after the content. + * + * Currently this is used to handle cases where a single word comprises + * multiple frames, and the first frame fits on the line but the whole word + * doesn't. We look back to the last optional break position and + * reflow the whole line again, forcing a break at that position. The last + * optional break position could be in a text frame or else after a frame + * that cannot be part of a text run, so those are the positions we record. + * + * It is imperative that this only gets called for break points that + * are within the available width. + * + * @return PR_TRUE if we are actually reflowing with forced break position and we + * should break here + */ + PRBool NotifyOptionalBreakPosition(nsIContent* aContent, PRInt32 aOffset) { + NS_ASSERTION(!GetFlag(LL_NEEDBACKUP), + "Shouldn't be updating the break position after we've already flagged an overrun"); + mLastOptionalBreakContent = aContent; + mLastOptionalBreakContentOffset = aOffset; + return aContent && mForceBreakContent == aContent && + mForceBreakContentOffset == aOffset; + } + /** + * Like NotifyOptionalBreakPosition, but here it's OK for LL_NEEDBACKUP + * to be set, because the caller is merely pruning some saved break position(s) + * that are actually not feasible. + */ + void RestoreSavedBreakPosition(nsIContent* aContent, PRInt32 aOffset) { + mLastOptionalBreakContent = aContent; + mLastOptionalBreakContentOffset = aOffset; + } + void ClearOptionalBreakPosition() { + mLastOptionalBreakContent = nsnull; + mLastOptionalBreakContentOffset = -1; + } + // Retrieve last set optional break position. When this returns null, no + // optional break has been recorded (which means that the line can't break yet). + nsIContent* GetLastOptionalBreakPosition(PRInt32* aOffset) { + *aOffset = mLastOptionalBreakContentOffset; + return mLastOptionalBreakContent; + } + + /** + * Check whether frames overflowed the available width and CanPlaceFrame + * requested backing up to a saved break position. + */ + PRBool NeedsBackup() { return GetFlag(LL_NEEDBACKUP); } + + // Line layout may place too much content on a line, overflowing its available + // width. When that happens, if SetLastOptionalBreakPosition has been + // used to record an optional break that wasn't taken, we can reflow the line + // again and force the break to happen at that point (i.e., backtracking + // to the last choice point). + + // Record that we want to break at the given content+offset (which + // should have been previously returned by GetLastOptionalBreakPosition + // from another nsLineLayout). + void ForceBreakAtPosition(nsIContent* aContent, PRInt32 aOffset) { + mForceBreakContent = aContent; + mForceBreakContentOffset = aOffset; + } + PRBool HaveForcedBreakPosition() { return mForceBreakContent != nsnull; } + PRInt32 GetForcedBreakPosition(nsIContent* aContent) { + return mForceBreakContent == aContent ? mForceBreakContentOffset : -1; + } + + /** + * This can't be null. It usually returns a block frame but may return + * some other kind of frame when inline frames are reflowed in a non-block + * context (e.g. MathML). + */ + nsIFrame* GetLineContainerFrame() { return mBlockReflowState->frame; } + protected: // This state is constant for a given block frame doing line layout nsSpaceManager* mSpaceManager; const nsStyleText* mStyleText; // for the block const nsHTMLReflowState* mBlockReflowState; + nsIContent* mLastOptionalBreakContent; + nsIContent* mForceBreakContent; + PRInt32 mLastOptionalBreakContentOffset; + PRInt32 mForceBreakContentOffset; + + nsIFrame* mTrailingTextFrame; + // XXX remove this when landing bug 154892 (splitting absolute positioned frames) friend class nsInlineFrame; @@ -333,14 +436,13 @@ protected: nsIFrame* mFirstLetterFrame; PRInt32 mLineNumber; PRInt32 mColumn; + nscoord mWordWidth; PRInt32 mTextJustificationNumSpaces; PRInt32 mTextJustificationNumLetters; nsLineBox* mLineBox; PRInt32 mTotalPlacedFrames; - ArenaDeque *mWordFrames; - PRBool AllocateDeque(); nscoord mTopEdge; nscoord mMaxTopBoxHeight; @@ -395,7 +497,6 @@ protected: #define PFD_ISNONEMPTYTEXTFRAME 0x00000004 #define PFD_ISNONWHITESPACETEXTFRAME 0x00000008 #define PFD_ISLETTERFRAME 0x00000010 -#define PFD_ISSTICKY 0x00000020 #define PFD_ISBULLET 0x00000040 #define PFD_SKIPWHENTRIMMINGWHITESPACE 0x00000080 #define PFD_LASTFLAG PFD_SKIPWHENTRIMMINGWHITESPACE @@ -497,6 +598,7 @@ protected: PRBool CanPlaceFrame(PerFrameData* pfd, const nsHTMLReflowState& aReflowState, PRBool aNotSafeToBreak, + PRBool aFrameCanContinueTextRun, nsHTMLReflowMetrics& aMetrics, nsReflowStatus& aStatus); diff --git a/mozilla/layout/generic/nsPlaceholderFrame.cpp b/mozilla/layout/generic/nsPlaceholderFrame.cpp index b40d72ab155..765c5913560 100644 --- a/mozilla/layout/generic/nsPlaceholderFrame.cpp +++ b/mozilla/layout/generic/nsPlaceholderFrame.cpp @@ -105,6 +105,18 @@ nsPlaceholderFrame::GetType() const return nsLayoutAtoms::placeholderFrame; } +NS_IMETHODIMP +nsPlaceholderFrame::CanContinueTextRun(PRBool& aContinueTextRun) const +{ + if (!mOutOfFlowFrame) { + aContinueTextRun = PR_FALSE; + return NS_OK; + } + // first-letter frames can continue text runs, and placeholders for floated + // first-letter frames can too + return mOutOfFlowFrame->CanContinueTextRun(aContinueTextRun); +} + #ifdef DEBUG static void PaintDebugPlaceholder(nsIFrame* aFrame, nsIRenderingContext* aCtx, diff --git a/mozilla/layout/generic/nsPlaceholderFrame.h b/mozilla/layout/generic/nsPlaceholderFrame.h index 1a33aea6e2e..1ba2f340a97 100644 --- a/mozilla/layout/generic/nsPlaceholderFrame.h +++ b/mozilla/layout/generic/nsPlaceholderFrame.h @@ -99,6 +99,8 @@ public: virtual PRBool IsEmpty() { return PR_TRUE; } virtual PRBool IsSelfEmpty() { return PR_TRUE; } + NS_IMETHOD CanContinueTextRun(PRBool& aContinueTextRun) const; + #ifdef ACCESSIBILITY NS_IMETHOD GetAccessible(nsIAccessible** aAccessible) { diff --git a/mozilla/layout/generic/nsTextFrame.cpp b/mozilla/layout/generic/nsTextFrame.cpp index 2f5ded3d768..24da1027801 100644 --- a/mozilla/layout/generic/nsTextFrame.cpp +++ b/mozilla/layout/generic/nsTextFrame.cpp @@ -540,61 +540,13 @@ public: nsTextPaintStyle& aStyle, nscoord dx, nscoord dy); - /** - * ComputeTotalWordDimensions and ComputeWordFragmentDimensions work - * together to measure a text that spans multiple frames, e.g., as in - * "baseTextmoreTextmoreDeepTextmoreAlsoHere" - * where the total text shoudn't be broken (or the joined pieces should be - * passed to the linebreaker for examination, especially in i18n cases). - * - * ComputeTotalWordDimensions will loop over ComputeWordFragmentDimensions - * to look-ahead and accumulate the joining fragments. - * - * @param aNextFrame is the first textFrame after the baseText's textFrame. - * - * @param aBaseDimensions is the dimension of baseText. - * - * @param aCanBreakBefore is false when it is not possible to break before - * the baseText (e.g., when this is the first word on the line). - */ - nsTextDimensions ComputeTotalWordDimensions(nsPresContext* aPresContext, - nsLineLayout& aLineLayout, - const nsHTMLReflowState& aReflowState, - nsIFrame* aNextFrame, - const nsTextDimensions& aBaseDimensions, - PRUnichar* aWordBuf, - PRUint32 aWordBufLen, - PRUint32 aWordBufSize, - PRBool aCanBreakBefore); - - /** - * @param aNextFrame is the textFrame following the current fragment. - * - * @param aMoreSize plays a double role. The process should continue - * normally when it is zero. But when it returns -1, it means that there is - * no more fragment of interest and the look-ahead should be stopped. When - * it returns a positive value, it means that the current buffer (aWordBuf - * of size aWordBufSize) is not big enough to accumulate the current fragment. - * The returned positive value is the shortfall. - * - * @param aWordBufLen is the accumulated length of the fragments that have - * been accounted for so far. - */ - nsTextDimensions ComputeWordFragmentDimensions(nsPresContext* aPresContext, - nsLineLayout& aLineLayout, - const nsHTMLReflowState& aReflowState, - nsIFrame* aNextFrame, - nsIContent* aContent, - PRInt32* aMoreSize, - const PRUnichar* aWordBuf, - PRUint32 &aWordBufLen, - PRUint32 aWordBufSize, - PRBool aCanBreakBefore); - #ifdef DEBUG void ToCString(nsString& aBuf, PRInt32* aTotalContentLength) const; #endif - + + PRInt32 GetContentOffset() { return mContentOffset; } + PRInt32 GetContentLength() { return mContentLength; } + protected: virtual ~nsTextFrame(); @@ -4907,21 +4859,6 @@ struct TextRun { } }; -// Transforms characters in place from ascii to Unicode -static void -TransformTextToUnicode(char* aText, PRInt32 aNumChars) -{ - // Go backwards over the characters and convert them. - unsigned char* cp1 = (unsigned char*)aText + aNumChars - 1; - PRUnichar* cp2 = (PRUnichar*)aText + (aNumChars - 1); - - while (aNumChars-- > 0) { - // XXX: If you crash here then you may see the issue described - // in http://bugzilla.mozilla.org/show_bug.cgi?id=36146#c44 - *cp2-- = PRUnichar(*cp1--); - } -} - PRUint32 nsTextFrame::EstimateNumChars(PRUint32 aAvailableWidth, PRUint32 aAverageCharWidth) @@ -4951,6 +4888,85 @@ DEFINE_CCMAP(gPuncCharsCCMap, const); #define IsPunctuationMark(ch) (CCMAP_HAS_CHAR(gPuncCharsCCMap, ch)) +static PRBool CanBreakBetween(nsTextFrame* aBefore, + PRBool aBreakWhitespaceBefore, + nsTextFrame* aAfter, + PRBool aBreakWhitespaceAfter, + PRBool aSkipLeadingWhitespaceAfter, + nsIFrame* aLineContainer) +{ + // This assumes text transforms don't change text breaking properties + const nsTextFragment* fragBefore = aBefore->GetContent()->GetText(); + const nsTextFragment* fragAfter = aAfter->GetContent()->GetText(); + NS_ASSERTION(fragBefore && fragAfter, "text frames with no text!"); + + // The end of the before-content + PRInt32 beforeOffset = aBefore->GetContentOffset() + aBefore->GetContentLength(); + // The start of the after-content + PRInt32 afterOffset = aAfter->GetContentOffset(); + PRInt32 afterLength = fragAfter->GetLength() - afterOffset; + + if (beforeOffset <= 0 || afterLength <= 0) { + // This shouldn't really happen, text frames shouldn't map no content + NS_WARNING("Textframe maps no content"); + return PR_FALSE; + } + + PRUnichar lastBefore = fragBefore->CharAt(beforeOffset - 1); + PRUnichar firstAfter = fragAfter->CharAt(afterOffset); + + // If we're skipping leading whitespace in the after-frame, and we actually + // have leading whitespace in the after-frame, then we can't break before + // it. We will rely on a saved break opportunity from the end of the last frame + // (if any). The problem is that we can't accurately figure out here whether + // a break is allowed. + if (aSkipLeadingWhitespaceAfter && XP_IS_SPACE(firstAfter)) + return PR_FALSE; + + while (IS_DISCARDED(firstAfter)) { + ++afterOffset; + --afterLength; + if (afterLength == 0) { + // aAfter will be entirely skipped. No breaking allowed here. + return PR_FALSE; + } + firstAfter = fragAfter->CharAt(afterOffset); + } + while (IS_DISCARDED(lastBefore)) { + NS_ASSERTION(beforeOffset > 0, + "Before-textframe maps no content, should not have called SetTrailingTextFrame"); + --beforeOffset; + lastBefore = fragBefore->CharAt(beforeOffset - 1); + } + + // If the character before or after the boundary is a breakable whitespace + // character that's not skipped, we're good to break. + if ((XP_IS_SPACE(lastBefore) && aBreakWhitespaceBefore) || + (XP_IS_SPACE(firstAfter) && aBreakWhitespaceAfter)) + return PR_TRUE; + // See nsJISx4051LineBreaker::BreakInBetween ... characters 0-255 don't + // trigger complex line breaking behaviour. This is an approximation since + // currently nsJISx4051LineBreaker can look far along a line, but it'll do + // until the line breaker is sorted out. + if (!fragBefore->Is2b() && !fragAfter->Is2b()) + return PR_FALSE; + + nsIFrame* f = + nsLayoutUtils::GetClosestCommonAncestorViaPlaceholders(aBefore, aAfter, aLineContainer); + NS_ASSERTION(f, "Frames to check not in the same document???"); + // Check if our nearest common ancestor allows wrapping between its children + if (f->GetStyleText()->WhiteSpaceCanWrap()) + return PR_FALSE; + + nsAutoString beforeStr; + nsAutoString afterStr; + fragBefore->AppendTo(beforeStr, 0, beforeOffset); + fragAfter->AppendTo(afterStr, afterOffset, afterLength); + + return nsContentUtils::LineBreaker()->BreakInBetween( + beforeStr.get(), beforeStr.Length(), afterStr.get(), afterStr.Length()); +} + nsReflowStatus nsTextFrame::MeasureText(nsPresContext* aPresContext, const nsHTMLReflowState& aReflowState, @@ -4963,9 +4979,7 @@ nsTextFrame::MeasureText(nsPresContext* aPresContext, nsLineLayout& lineLayout = *aReflowState.mLineLayout; PRInt32 contentLength = aTx.GetContentLength(); PRInt32 startingOffset = aTextData.mOffset; - PRInt32 prevOffset = -1; PRInt32 column = mColumn; - PRInt32 prevColumn = column; nscoord prevMaxWordWidth = 0, prevAscent = 0, prevDescent = 0; PRInt32 lastWordLen = 0; PRUnichar* lastWordPtr = nsnull; @@ -4974,6 +4988,21 @@ nsTextFrame::MeasureText(nsPresContext* aPresContext, PRBool justDidFirstLetter = PR_FALSE; nsTextDimensions dimensions, lastWordDimensions; PRBool measureTextRuns = PR_FALSE; + nscoord lastWordWidth = NS_UNCONSTRAINEDSIZE; + + // Check whether we can break between the last text frame (if any) and this one + PRBool trailingTextFrameCanWrap; + nsIFrame* lastTextFrame = lineLayout.GetTrailingTextFrame(&trailingTextFrameCanWrap); + PRBool canBreakBetweenTextFrames = PR_FALSE; + if (lastTextFrame) { + NS_ASSERTION(lastTextFrame->GetType() == nsGkAtoms::textFrame, + "Trailing text frame isn't text!"); + canBreakBetweenTextFrames = + CanBreakBetween(NS_STATIC_CAST(nsTextFrame*, lastTextFrame), + trailingTextFrameCanWrap, + this, aTextData.mWrapping, aTextData.mSkipWhitespace, + lineLayout.GetLineContainerFrame()); + } if (contentLength == 0) { aTextData.mX = 0; @@ -5108,7 +5137,6 @@ nsTextFrame::MeasureText(nsPresContext* aPresContext, if ('\n' == firstChar) { // We hit a newline. Stop looping. NS_ASSERTION(aTs.mPreformatted, "newline w/o ts.mPreformatted"); - prevOffset = aTextData.mOffset; aTextData.mOffset++; endsInWhitespace = PR_TRUE; endsInNewline = PR_TRUE; @@ -5159,10 +5187,8 @@ nsTextFrame::MeasureText(nsPresContext* aPresContext, //Even if there is not enough space for this "space", we still put it //here instead of next line - prevColumn = column; column += wordLen; endsInWhitespace = PR_TRUE; - prevOffset = aTextData.mOffset; aTextData.mOffset += contentLen; if (aTextData.mMeasureText) { @@ -5193,7 +5219,9 @@ nsTextFrame::MeasureText(nsPresContext* aPresContext, } //(aTextData.mMeasureText) } else { + PRBool currentWordIsFirstThing = firstThing; firstThing = PR_FALSE; + aTextData.mSkipWhitespace = PR_FALSE; if (aTextData.mFirstLetterOK) { @@ -5269,20 +5297,47 @@ nsTextFrame::MeasureText(nsPresContext* aPresContext, } } } + lastWordDimensions = dimensions; - // See if there is room for the text - if ((0 != aTextData.mX) && aTextData.mWrapping && (aTextData.mX + dimensions.width > maxWidth)) { - // The text will not fit. - break; + PRBool canBreak; + if (0 == aTextData.mX) { + canBreak = canBreakBetweenTextFrames; + // Allow breaking between text frames even if mWrapping is false + // (e.g., we're white-space:pre). If canBreakBetweenTextFrames is + // true, then the previous text frame's mWrapping must have been + // true, and we allow breaking between text frames if at least + // one of them allows breaking. + } else { + canBreak = aTextData.mWrapping; } + if (canBreak) { + // Remember that we *could* have broken here, even if we choose not to + PRBool forceBreak = + lineLayout.NotifyOptionalBreakPosition(GetContent(), aTextData.mOffset); + // See if there is room for the text + if (forceBreak || (aTextData.mX + dimensions.width > maxWidth)) { + // The text will not fit, or a break was forced. + break; + } + } + prevMaxWordWidth = aTextData.mMaxWordWidth; prevAscent = aTextData.mAscent; prevDescent = aTextData.mDescent; aTextData.mX += dimensions.width; - if (dimensions.width > aTextData.mMaxWordWidth) { - aTextData.mMaxWordWidth = dimensions.width; + if (aTextData.mComputeMaxWordWidth) { + lastWordWidth = dimensions.width; + if (currentWordIsFirstThing) { + nscoord incomingWordWidth; + if (lineLayout.InWord(&incomingWordWidth)) { + lastWordWidth += incomingWordWidth; + } + } + if (lastWordWidth > aTextData.mMaxWordWidth) { + aTextData.mMaxWordWidth = lastWordWidth; + } } if (aTextData.mAscent < dimensions.ascent) { aTextData.mAscent = dimensions.ascent; @@ -5291,10 +5346,8 @@ nsTextFrame::MeasureText(nsPresContext* aPresContext, aTextData.mDescent = dimensions.descent; } - prevColumn = column; column += wordLen; endsInWhitespace = PR_FALSE; - prevOffset = aTextData.mOffset; aTextData.mOffset += contentLen; if (justDidFirstLetter) { // Time to stop @@ -5303,11 +5356,25 @@ nsTextFrame::MeasureText(nsPresContext* aPresContext, } } else { + // Remember that we *could* have broken before this chunk of text. + PRBool canBreak; + if (aTextData.mOffset == startingOffset) { + canBreak = canBreakBetweenTextFrames; + } else { + canBreak = aTextData.mWrapping; + } + if (canBreak) { +#ifdef DEBUG + PRBool forceBreak = +#endif + lineLayout.NotifyOptionalBreakPosition(GetContent(), aTextData.mOffset); + NS_ASSERTION(!forceBreak, "If we're supposed to break, we should be " + "really measuring"); + } + // We didn't measure the text, but we need to update our state - prevColumn = column; column += wordLen; endsInWhitespace = PR_FALSE; - prevOffset = aTextData.mOffset; aTextData.mOffset += contentLen; if (justDidFirstLetter) { // Time to stop @@ -5319,119 +5386,142 @@ nsTextFrame::MeasureText(nsPresContext* aPresContext, MeasureTextRun: #if defined(_WIN32) || defined(XP_OS2) || defined(MOZ_X11) || defined(XP_BEOS) - // see if we have implementation for GetTextDimensions() - if (hints & NS_RENDERING_HINT_FAST_MEASURE) { - PRInt32 numCharsFit; - // These calls can return numCharsFit not positioned at a break in the textRun. Beware. - if (aTx.TransformedTextIsAscii()) { - aReflowState.rendContext->GetTextDimensions((char*)aTx.GetWordBuffer(), textRun.mTotalNumChars, - maxWidth - aTextData.mX, - textRun.mBreaks, textRun.mNumSegments, - dimensions, numCharsFit, lastWordDimensions); - } else { - aReflowState.rendContext->GetTextDimensions(aTx.GetWordBuffer(), textRun.mTotalNumChars, - maxWidth - aTextData.mX, - textRun.mBreaks, textRun.mNumSegments, - dimensions, numCharsFit, lastWordDimensions); - } - // See how much of the text fit - if ((0 != aTextData.mX) && aTextData.mWrapping && (aTextData.mX + dimensions.width > maxWidth)) { - // None of the text fits -#ifdef IBMBIDI - nextBidi = nsnull; -#endif // IBMBIDI - break; - } + // see if we have implementation for GetTextDimensions() + if (hints & NS_RENDERING_HINT_FAST_MEASURE) { + PRInt32 numCharsFit; - // Find the index of the last segment that fit - PRInt32 lastSegment; - if (numCharsFit >= textRun.mTotalNumChars) { // fast path, normal case - NS_ASSERTION(numCharsFit == textRun.mTotalNumChars, "shouldn't overshoot"); - lastSegment = textRun.mNumSegments - 1; - } else { - for (lastSegment = 0; textRun.mBreaks[lastSegment] < numCharsFit; lastSegment++) ; - NS_ASSERTION(lastSegment < textRun.mNumSegments, "failed to find segment"); - // now we have textRun.mBreaks[lastSegment] >= numCharsFit - /* O'Callahan XXX: This snippet together with the snippet below prevents mail from loading - Justification seems to work just fine without these changes. - We get into trouble in a case where lastSegment gets set to -1 - - if (textRun.mBreaks[lastSegment] > numCharsFit) { - // NOTE: this segment did not actually fit! - lastSegment--; + PRInt32 forcedOffset = lineLayout.GetForcedBreakPosition(GetContent()); + PRInt32 measureChars = textRun.mTotalNumChars; + if (forcedOffset == -1) { + forcedOffset -= aTextData.mOffset; + NS_ASSERTION(forcedOffset >= 0, + "Overshot forced offset, we should have already exited"); + if (forcedOffset >= 0 && forcedOffset < textRun.mTotalNumChars) { + // Only measure up to forcedOffset characters. We still need to measure + // to make sure we get the right text dimensions, even though we know + // where we're going to break. This will reduce the number of chars that + // fit, which we then detect as a required break. + measureChars = forcedOffset; + } } - */ - } - /* O'Callahan XXX: This snippet together with the snippet above prevents mail from loading - - if (lastSegment < 0) { - // no segments fit - break; - } else */ - if (lastSegment == 0) { - // Only one segment fit - prevColumn = column; - prevOffset = aTextData.mOffset; - } else { - // The previous state is for the next to last word - // NOTE: The textRun data are relative to the last updated column and offset! - prevColumn = column + textRun.mBreaks[lastSegment - 1]; - prevOffset = aTextData.mOffset + textRun.mSegments[lastSegment - 1].ContentLen(); - } - - aTextData.mX += dimensions.width; - if (aTextData.mAscent < dimensions.ascent) { - aTextData.mAscent = dimensions.ascent; - } - if (aTextData.mDescent < dimensions.descent) { - aTextData.mDescent = dimensions.descent; - } - // this is where to backup if line-breaking happens to push the last word - prevAscent = aTextData.mAscent; - prevDescent = aTextData.mDescent; - // we can now consider the last word since we know where to backup - if (aTextData.mAscent < lastWordDimensions.ascent) { - aTextData.mAscent = lastWordDimensions.ascent; - } - if (aTextData.mDescent < lastWordDimensions.descent) { - aTextData.mDescent = lastWordDimensions.descent; - } - - column += numCharsFit; - aTextData.mOffset += textRun.mSegments[lastSegment].ContentLen(); - endsInWhitespace = textRun.mSegments[lastSegment].IsWhitespace(); - - // If all the text didn't fit, then we're done - if (numCharsFit != textRun.mTotalNumChars) { + // These calls can return numCharsFit not positioned at a break in the textRun. Beware. + if (aTx.TransformedTextIsAscii()) { + aReflowState.rendContext->GetTextDimensions((char*)aTx.GetWordBuffer(), measureChars, + maxWidth - aTextData.mX, + textRun.mBreaks, textRun.mNumSegments, + dimensions, numCharsFit, lastWordDimensions); + } else { + aReflowState.rendContext->GetTextDimensions(aTx.GetWordBuffer(), measureChars, + maxWidth - aTextData.mX, + textRun.mBreaks, textRun.mNumSegments, + dimensions, numCharsFit, lastWordDimensions); + } + + // See how much of the text fit + PRBool canBreak; + if (0 == aTextData.mX) { + canBreak = canBreakBetweenTextFrames; + } else { + canBreak = aTextData.mWrapping; + } + if (canBreak && aTextData.mX + dimensions.width > maxWidth) { + // None of the text fits #ifdef IBMBIDI - nextBidi = nsnull; + nextBidi = nsnull; #endif // IBMBIDI - break; - } - + break; + } + + // Find the index of the last segment that fit + PRInt32 lastSegment; + if (numCharsFit >= textRun.mTotalNumChars) { // fast path, normal case + NS_ASSERTION(numCharsFit == textRun.mTotalNumChars, "shouldn't overshoot"); + lastSegment = textRun.mNumSegments - 1; + } else { + for (lastSegment = 0; textRun.mBreaks[lastSegment] < numCharsFit; lastSegment++) ; + NS_ASSERTION(lastSegment < textRun.mNumSegments, "failed to find segment"); + // now we have textRun.mBreaks[lastSegment] >= numCharsFit + /* O'Callahan XXX: This snippet together with the snippet below prevents mail from loading + Justification seems to work just fine without these changes. + We get into trouble in a case where lastSegment gets set to -1 + + if (textRun.mBreaks[lastSegment] > numCharsFit) { + // NOTE: this segment did not actually fit! + lastSegment--; + } + */ + } + + /* O'Callahan XXX: This snippet together with the snippet above prevents mail from loading + + if (lastSegment < 0) { + // no segments fit + break; + } */ + + aTextData.mX += dimensions.width; + if (aTextData.mAscent < dimensions.ascent) { + aTextData.mAscent = dimensions.ascent; + } + if (aTextData.mDescent < dimensions.descent) { + aTextData.mDescent = dimensions.descent; + } + // this is where to backup if line-breaking happens to push the last word + prevAscent = aTextData.mAscent; + prevDescent = aTextData.mDescent; + // we can now consider the last word since we know where to backup + if (aTextData.mAscent < lastWordDimensions.ascent) { + aTextData.mAscent = lastWordDimensions.ascent; + } + if (aTextData.mDescent < lastWordDimensions.descent) { + aTextData.mDescent = lastWordDimensions.descent; + } + + endsInWhitespace = textRun.mSegments[lastSegment].IsWhitespace(); + + // Save last possible backup position. Whitespace segments are always + // breakable since nsTextTransformer reports preformatted spaces as + // "not whitespace". Note that each segment is either entirely whitespace + // or entirely non-whitespace. + PRInt32 lastWhitespaceSegment = + endsInWhitespace ? lastSegment : lastSegment - 1; + if (lastWhitespaceSegment >= 0) { + lineLayout.NotifyOptionalBreakPosition(GetContent(), + aTextData.mOffset + textRun.mSegments[lastWhitespaceSegment].ContentLen()); + } + + column += numCharsFit; + aTextData.mOffset += textRun.mSegments[lastSegment].ContentLen(); + + // If all the text didn't fit, then we're done + if (numCharsFit != textRun.mTotalNumChars) { #ifdef IBMBIDI - if (nextBidi && (mContentLength <= 0) ) { - break; - } + nextBidi = nsnull; #endif // IBMBIDI - - if (nsnull == bp2) { - // No more text so we're all finished. Advance the offset in case the last - // call to GetNextWord() discarded characters - aTextData.mOffset += contentLen; - break; + break; + } + +#ifdef IBMBIDI + if (nextBidi && (mContentLength <= 0) ) { + break; + } +#endif // IBMBIDI + + if (nsnull == bp2) { + // No more text so we're all finished. Advance the offset in case the last + // call to GetNextWord() discarded characters + aTextData.mOffset += contentLen; + break; + } + + // Reset the number of text run segments + textRun.Reset(); + + // Estimate the remaining number of characters we think will fit + estimatedNumChars = EstimateNumChars(maxWidth - aTextData.mX, + aTs.mAveCharWidth); } - - // Reset the number of text run segments - textRun.Reset(); - - // Estimate the remaining number of characters we think will fit - estimatedNumChars = EstimateNumChars(maxWidth - aTextData.mX, - aTs.mAveCharWidth); - } -#else /* defined(_WIN32) || defined(XP_OS2) || defined(MOZ_X11) || defined(XP_BEOS) */ - int unused = -1; #endif /* defined(_WIN32) || defined(XP_OS2) || defined(MOZ_X11) || defined(XP_BEOS) */ } @@ -5448,160 +5538,24 @@ nsTextFrame::MeasureText(nsPresContext* aPresContext, } } - // Post processing logic to deal with word-breaking that spans - // multiple frames. - if (lineLayout.InWord()) { - // We are already in a word. This means a text frame prior to this - // one had a fragment of a nbword that is joined with this - // frame. It also means that the prior frame already found this - // frame and recorded it as part of the word. -#ifdef DEBUG_WORD_WRAPPING - ListTag(stdout); - printf(": in word; skipping\n"); -#endif - lineLayout.ForgetWordFrame(this); - } - - if (!lineLayout.InWord()) { - // There is no currently active word. This frame may contain the - // start of one. - if (endsInWhitespace) { - // Nope, this frame doesn't start a word. - lineLayout.ForgetWordFrames(); - } - else if ((aTextData.mOffset == contentLength) && (prevOffset >= 0)) { - // Force breakable to false when we aren't wrapping (this - // guarantees that the combined word will stay together) - if (!aTextData.mWrapping) { - aTextData.mCanBreakBefore = PR_FALSE; - } - - // This frame does start a word. However, there is no point - // messing around with it if we are already out of room. We - // always have room if we are not breakable. - if (!aTextData.mCanBreakBefore || (aTextData.mX <= maxWidth)) { - // There is room for this word fragment. It's possible that - // this word fragment is the end of the text-run. If it's not - // then we continue with the look-ahead processing. - nsIFrame* next = lineLayout.FindNextText(aPresContext, this); - if (nsnull != next) { -#ifdef DEBUG_WORD_WRAPPING - nsAutoString tmp(aTx.GetWordBuffer(), lastWordLen); - ListTag(stdout); - printf(": start='"); - fputs(NS_LossyConvertUTF16toASCII(tmp).get(), stdout); - printf("' lastWordLen=%d baseWidth=%d prevOffset=%d offset=%d next=", - lastWordLen, lastWordDimensions.width, prevOffset, aTextData.mOffset); - ListTag(stdout, next); - printf("\n"); -#endif - PRUnichar* pWordBuf = lastWordPtr; - PRUint32 wordBufLen = aTx.GetWordBufferLength() - - (lastWordPtr - aTx.GetWordBuffer()); - - if (aTx.TransformedTextIsAscii()) { - // The text transform buffer contains ascii characters, so - // transform it to Unicode - NS_ASSERTION(wordBufLen >= PRUint32(lastWordLen), "no room to transform in place"); - TransformTextToUnicode((char*)lastWordPtr, lastWordLen); - } - - // Look ahead in the text-run and compute the final word - // width, taking into account any style changes and stopping - // at the first breakable point. - if (!aTextData.mMeasureText || (lastWordDimensions.width == -1)) { - // We either didn't measure any text or we measured multiple words - // at once so either way we don't know lastWordDimensions. We'll have to - // compute it now - if (prevOffset == startingOffset) { - // There's only one word, so we don't have to measure after all - lastWordDimensions.width = aTextData.mX; - } - else if (aTs.mSmallCaps) { - MeasureSmallCapsText(aReflowState, aTs, pWordBuf, - lastWordLen, PR_FALSE, &lastWordDimensions); - } - else { - aReflowState.rendContext->GetTextDimensions(pWordBuf, lastWordLen, lastWordDimensions); - if (aTs.mLetterSpacing) { - lastWordDimensions.width += aTs.mLetterSpacing * lastWordLen; - } - if (aTs.mWordSpacing) { - for (PRUnichar* bp = pWordBuf; - bp < pWordBuf + lastWordLen; bp++) { - if (*bp == ' ') // || *bp == CH_CJKSP) - lastWordDimensions.width += aTs.mWordSpacing; - } - } - } - } - nsTextDimensions wordDimensions = ComputeTotalWordDimensions(aPresContext, - lineLayout, - aReflowState, next, - lastWordDimensions, - pWordBuf, - lastWordLen, - wordBufLen, - aTextData.mCanBreakBefore); - if (!aTextData.mCanBreakBefore || (aTextData.mX - lastWordDimensions.width + wordDimensions.width <= maxWidth)) { - // The fully joined word has fit. Account for the joined - // word's affect on the max-element-size here (since the - // joined word is large than it's pieces, the right effect - // will occur from the perspective of the container - // reflowing this frame) - if (wordDimensions.width > aTextData.mMaxWordWidth) { - aTextData.mMaxWordWidth = wordDimensions.width; - } - // Now that we now that we will retain the last word, we should - // account for its ascent and descent - if (aTextData.mAscent < lastWordDimensions.ascent) { - aTextData.mAscent = lastWordDimensions.ascent; - } - if (aTextData.mDescent < lastWordDimensions.descent) { - aTextData.mDescent = lastWordDimensions.descent; - } - } - else { -#ifdef NOISY_REFLOW - ListTag(stdout); - printf(": look-ahead (didn't fit) x=%d wordWidth=%d lastWordWidth=%d\n", - aTextData.mX, wordDimensions.width, lastWordDimensions.width); -#endif - // The fully joined word won't fit. We need to reduce our - // size by lastWordDimensions - aTextData.mX -= lastWordDimensions.width; - aTextData.mMaxWordWidth = prevMaxWordWidth; - aTextData.mOffset = prevOffset; - column = prevColumn; - if (aTextData.mMeasureText) { - aTextData.mAscent = prevAscent; - aTextData.mDescent = prevDescent; - } - // else { - // XXX we didn't measure the text, and so we don't know where to back up, - // we will retain our current height. However, there is a possible - // edge case that is not handled: since we just chopped the last word, - // our remaining text could have got shorter. - // } -#ifdef DEBUG_WORD_WRAPPING - printf(" x=%d maxWordWidth=%d len=%d\n", aTextData.mX, aTextData.mMaxWordWidth, - aTextData.mOffset - startingOffset); -#endif - lineLayout.ForgetWordFrames(); - } - } - } - } - } - // Inform line layout of how this piece of text ends in whitespace // (only text objects do this). Note that if x is zero then this // text object collapsed into nothingness which means it shouldn't - // effect the current setting of the ends-in-whitespace flag. + // effect the current setting of the ends-in-whitespace flag, nor should it + // be setting InWord state, and it should be ignored when subsequent text + // considers whether it can break-before. lineLayout.SetColumn(column); lineLayout.SetUnderstandsWhiteSpace(PR_TRUE); if (0 != aTextData.mX) { + lineLayout.SetTrailingTextFrame(this, aTextData.mWrapping); lineLayout.SetEndsInWhiteSpace(endsInWhitespace); + lineLayout.SetInWord(!endsInWhitespace, lastWordWidth); + } else { + // Don't allow subsequent text frame to break-before. All our text is + // being skipped (usually whitespace, could be discarded Unicode control + // characters). + lineLayout.SetTrailingTextFrame(nsnull, PR_FALSE); + lineLayout.SetInWord(PR_FALSE, 0); } if (justDidFirstLetter) { lineLayout.SetFirstLetterFrame(this); @@ -5616,21 +5570,35 @@ nsTextFrame::MeasureText(nsPresContext* aPresContext, #endif // IBMBIDI ? NS_FRAME_COMPLETE : NS_FRAME_NOT_COMPLETE; - if (endsInNewline) { - rs = NS_INLINE_LINE_BREAK_AFTER(rs); - lineLayout.SetLineEndsInBR(PR_TRUE); + + if (canBreakBetweenTextFrames && aTextData.mOffset == startingOffset) { + // Couldn't place any text but we can break between text frames, so do that. + return NS_INLINE_LINE_BREAK_BEFORE(); } - else if (aTextData.mTrailingSpaceTrimmed && rs == NS_FRAME_COMPLETE) { + + if (endsInNewline) { + lineLayout.SetLineEndsInBR(PR_TRUE); + return NS_INLINE_LINE_BREAK_AFTER(rs); + } + + if (aTextData.mTrailingSpaceTrimmed && rs == NS_FRAME_COMPLETE) { // Flag a soft-break that we can check (below) if we come back here lineLayout.SetLineEndsInSoftBR(PR_TRUE); - } - else if (lineLayout.GetLineEndsInSoftBR() && !lineLayout.GetEndsInWhiteSpace()) { + } else if (lineLayout.GetLineEndsInSoftBR() && !lineLayout.GetEndsInWhiteSpace()) { // Break-before a word that follows the soft-break flagged earlier - rs = NS_INLINE_LINE_BREAK_BEFORE(); + return NS_INLINE_LINE_BREAK_BEFORE(); } - else if ((aTextData.mOffset != contentLength) && (aTextData.mOffset == startingOffset)) { + + if (rs == NS_FRAME_COMPLETE && 0 != aTextData.mX && endsInWhitespace && + aTextData.mWrapping && aTextData.mX <= maxWidth) { + // Remember the break opportunity at the end of this frame + if (lineLayout.NotifyOptionalBreakPosition(GetContent(), aTextData.mOffset)) + return NS_INLINE_LINE_BREAK_AFTER(rs); + } + + if ((aTextData.mOffset != contentLength) && (aTextData.mOffset == startingOffset)) { // Break-before a long-word that doesn't fit here - rs = NS_INLINE_LINE_BREAK_BEFORE(); + return NS_INLINE_LINE_BREAK_BEFORE(); } return rs; @@ -5722,8 +5690,7 @@ nsTextFrame::Reflow(nsPresContext* aPresContext, } } - PRBool wrapping = (NS_STYLE_WHITESPACE_NORMAL == ts.mText->mWhiteSpace) || - (NS_STYLE_WHITESPACE_MOZ_PRE_WRAP == ts.mText->mWhiteSpace); + PRBool wrapping = ts.mText->WhiteSpaceCanWrap(); // Set whitespace skip flag PRBool skipWhitespace = PR_FALSE; @@ -5757,12 +5724,13 @@ nsTextFrame::Reflow(nsPresContext* aPresContext, // Set inWord to true if we are part of a previous piece of text's word. This // is only valid for one pass through the measuring loop. - PRBool inWord = lineLayout.InWord() || ((nsnull != prevInFlow) && (NS_STATIC_CAST(nsTextFrame*, prevInFlow)->mState & TEXT_FIRST_LETTER)); + nscoord currentWordWidth; + PRBool inWord = lineLayout.InWord(¤tWordWidth); if (inWord) { mState |= TEXT_IN_WORD; } mState &= ~TEXT_FIRST_LETTER; - + PRInt32 column = lineLayout.GetColumn(); PRInt32 prevColumn = mColumn; mColumn = column; @@ -5793,6 +5761,7 @@ nsTextFrame::Reflow(nsPresContext* aPresContext, if (!GetNextInFlow() && (mState & TEXT_OPTIMIZE_RESIZE) && !aMetrics.mComputeMEW && + lineLayout.GetForcedBreakPosition(GetContent()) == -1 && (lastTimeWeSkippedLeadingWS == skipWhitespace) && ((wrapping && (maxWidth >= realWidth)) || (!wrapping && (prevColumn == column))) && @@ -6032,259 +6001,6 @@ nsTextFrame::TrimTrailingWhiteSpace(nsPresContext* aPresContext, return NS_OK; } -static void -RevertSpacesToNBSP(PRUnichar* aBuffer, PRInt32 aWordLen) -{ - PRUnichar* end = aBuffer + aWordLen; - for (; aBuffer < end; aBuffer++) { - PRUnichar ch = *aBuffer; - if (ch == ' ') { - *aBuffer = CH_NBSP; - } - } -} - -nsTextDimensions -nsTextFrame::ComputeTotalWordDimensions(nsPresContext* aPresContext, - nsLineLayout& aLineLayout, - const nsHTMLReflowState& aReflowState, - nsIFrame* aNextFrame, - const nsTextDimensions& aBaseDimensions, - PRUnichar* aWordBuf, - PRUint32 aWordLen, - PRUint32 aWordBufSize, - PRBool aCanBreakBefore) -{ - // Before we get going, convert any spaces in the current word back - // to nbsp's. This keeps the breaking logic happy. - RevertSpacesToNBSP(aWordBuf, (PRInt32) aWordLen); - - nsTextDimensions addedDimensions; - PRUnichar *newWordBuf = aWordBuf; - PRUint32 newWordBufSize = aWordBufSize; - while (aNextFrame) { - nsIContent* content = aNextFrame->GetContent(); - -#ifdef DEBUG_WORD_WRAPPING - printf(" next textRun="); - nsFrame::ListTag(stdout, aNextFrame); - printf("\n"); -#endif - - if (content->IsNodeOfType(nsINode::eTEXT)) { - PRInt32 moreSize = 0; - nsTextDimensions moreDimensions; - moreDimensions = ComputeWordFragmentDimensions(aPresContext, - aLineLayout, - aReflowState, - aNextFrame, content, - &moreSize, - newWordBuf, - aWordLen, - newWordBufSize, - aCanBreakBefore); - if (moreSize > 0) { - //Oh, wordBuf is too small, we have to grow it - newWordBufSize += moreSize; - if (newWordBuf != aWordBuf) { - newWordBuf = (PRUnichar*)nsMemory::Realloc(newWordBuf, sizeof(PRUnichar)*newWordBufSize); - NS_ASSERTION(newWordBuf, "not enough memory"); - } else { - newWordBuf = (PRUnichar*)nsMemory::Alloc(sizeof(PRUnichar)*newWordBufSize); - NS_ASSERTION(newWordBuf, "not enough memory"); - if(newWordBuf) { - memcpy((void*)newWordBuf, aWordBuf, sizeof(PRUnichar)*(newWordBufSize-moreSize)); - } - } - - if(newWordBuf) { - moreDimensions = - ComputeWordFragmentDimensions(aPresContext, - aLineLayout, aReflowState, - aNextFrame, content, &moreSize, - newWordBuf, aWordLen, newWordBufSize, - aCanBreakBefore); - NS_ASSERTION((moreSize <= 0), - "ComputeWordFragmentDimensions is asking more buffer"); - } else { - moreSize = -1; - moreDimensions.Clear(); - } - } - - addedDimensions.Combine(moreDimensions); -#ifdef DEBUG_WORD_WRAPPING - printf(" moreWidth=%d (addedWidth=%d) stop=%c\n", moreDimensions.width, - addedDimensions.width, stop?'T':'F'); -#endif - if (moreSize == -1) { - goto done; - } - } - else { - // It claimed it was text but it doesn't contain a textfragment. - // Therefore I don't know what to do with it and can't look inside - // it. Oh well. - goto done; - } - - // Move on to the next frame in the text-run - aNextFrame = aLineLayout.FindNextText(aPresContext, aNextFrame); - } - - done:; -#ifdef DEBUG_WORD_WRAPPING - printf(" total word width=%d\n", aBaseDimensions.width + addedDimensions.width); -#endif - if (newWordBuf && (newWordBuf != aWordBuf)) { - nsMemory::Free(newWordBuf); - } - addedDimensions.Combine(aBaseDimensions); - return addedDimensions; -} - -nsTextDimensions -nsTextFrame::ComputeWordFragmentDimensions(nsPresContext* aPresContext, - nsLineLayout& aLineLayout, - const nsHTMLReflowState& aReflowState, - nsIFrame* aNextFrame, - nsIContent* aContent, - PRInt32* aMoreSize, - const PRUnichar* aWordBuf, - PRUint32& aRunningWordLen, - PRUint32 aWordBufSize, - PRBool aCanBreakBefore) -{ - nsTextTransformer tx(aPresContext); - PRInt32 nextFrameStart, nextFrameEnd; - aNextFrame->GetOffsets(nextFrameStart, nextFrameEnd); - tx.Init(aNextFrame, aContent, nextFrameStart); - if (nextFrameEnd == 0) // uninitialized - nextFrameEnd = tx.GetContentLength(); - PRBool isWhitespace, wasTransformed; - PRInt32 wordLen, contentLen; - nsTextDimensions dimensions; -#ifdef IBMBIDI - if (aNextFrame->GetStateBits() & NS_FRAME_IS_BIDI) { - wordLen = nextFrameEnd; - } else { - wordLen = -1; - } -#endif // IBMBIDI - *aMoreSize = 0; - PRUnichar* bp = tx.GetNextWord(PR_TRUE, &wordLen, &contentLen, &isWhitespace, &wasTransformed); - if (!bp) { - //empty text node, but we need to continue lookahead measurement - // AND we need to remember the text frame for later so that we don't - // bother doing the word look ahead. - aLineLayout.RecordWordFrame(aNextFrame); - return dimensions; // 0 - } - - if (isWhitespace) { - // Don't bother measuring nothing - *aMoreSize = -1; // flag that we should stop now - return dimensions; // 0 - } - - // We need to adjust the length by looking at the two pieces together. But if - // we have to grow aWordBuf, ask the caller to do it by returning the shortfall - if ((wordLen + aRunningWordLen) > aWordBufSize) { - *aMoreSize = wordLen + aRunningWordLen - aWordBufSize; - return dimensions; // 0 - } - if (nextFrameStart + contentLen < nextFrameEnd) - *aMoreSize = -1; - - // Convert any spaces in the current word back to nbsp's. This keeps - // the breaking logic happy. - RevertSpacesToNBSP(bp, wordLen); - - if (aCanBreakBefore) { - if(wordLen > 0) - { - memcpy((void*)&(aWordBuf[aRunningWordLen]), bp, sizeof(PRUnichar)*wordLen); - - PRInt32 breakP=0; - breakP = nsContentUtils::LineBreaker()->Next(aWordBuf, - aRunningWordLen+wordLen, 0); - // when we look at two pieces text together, we might decide to break - // eariler than if we only look at the 2nd pieces of text - if (breakP != NS_LINEBREAKER_NEED_MORE_TEXT && - (breakP < (aRunningWordLen + wordLen))) - { - wordLen = breakP - aRunningWordLen; - if(wordLen < 0) - wordLen = 0; - *aMoreSize = -1; - } - - // if we don't stop, we need to extend the buf so the next one can - // see this part otherwise, it does not matter since we will stop - // anyway - if (*aMoreSize != -1) - aRunningWordLen += wordLen; - } - } - else { - // Even if the previous text fragment is not breakable, the connected pieces - // can be breakable in between. This especially true for CJK. - PRBool canBreak; - canBreak = nsContentUtils::LineBreaker()->BreakInBetween(aWordBuf, - aRunningWordLen, bp, wordLen); - if (canBreak) { - wordLen = 0; - *aMoreSize = -1; - } - } - - if ((*aMoreSize == -1) && (wordLen == 0)) - return dimensions; // 0; - - nsStyleContext* sc = aNextFrame->GetStyleContext(); - if (sc) { - // Measure the piece of text. Note that we have to select the - // appropriate font into the text first because the rendering - // context has our font in it, not the font that aText is using. - nsIRenderingContext& rc = *aReflowState.rendContext; - nsCOMPtr oldfm; - rc.GetFontMetrics(*getter_AddRefs(oldfm)); - - nsTextStyle ts(aLineLayout.mPresContext, rc, sc); - if (ts.mSmallCaps) { - MeasureSmallCapsText(aReflowState, ts, bp, wordLen, PR_FALSE, &dimensions); - } - else { - rc.GetTextDimensions(bp, wordLen, dimensions); - // NOTE: Don't forget to add letter spacing for the word fragment! - dimensions.width += wordLen*ts.mLetterSpacing; - if (ts.mWordSpacing) { - for (PRUnichar* bp2 = bp; bp2 < bp + wordLen; bp2++) { - if (*bp2 == CH_NBSP) // || *bp2 == CH_CJKSP) - dimensions.width += ts.mWordSpacing; - } - } - } - rc.SetFont(oldfm); - -#ifdef DEBUG_WORD_WRAPPING - nsAutoString tmp(bp, wordLen); - printf(" fragment='"); - fputs(NS_LossyConvertUTF16toASCII(tmp).get(), stdout); - printf("' width=%d [wordLen=%d contentLen=%d ContentLength=%d]\n", - dimensions.width, wordLen, contentLen, tx.GetContentLength()); -#endif - - // Remember the text frame for later so that we don't bother doing - // the word look ahead. - aLineLayout.RecordWordFrame(aNextFrame); - return dimensions; - } - - *aMoreSize = -1; - return dimensions; // 0 -} - #ifdef DEBUG // Translate the mapped content into a string that's printable void diff --git a/mozilla/layout/mathml/base/src/nsMathMLContainerFrame.cpp b/mozilla/layout/mathml/base/src/nsMathMLContainerFrame.cpp index f4199512fc3..288ef733e9e 100644 --- a/mozilla/layout/mathml/base/src/nsMathMLContainerFrame.cpp +++ b/mozilla/layout/mathml/base/src/nsMathMLContainerFrame.cpp @@ -1005,10 +1005,6 @@ nsMathMLContainerFrame::ReflowForeignChild(nsIFrame* aChildFrame, const nsHTMLReflowState& aReflowState, nsReflowStatus& aStatus) { - // don't bother trying to span words as if they were non-breaking beyond this point - if (aReflowState.mLineLayout) - aReflowState.mLineLayout->ForgetWordFrames(); - nsAutoSpaceManager autoSpaceManager(NS_CONST_CAST(nsHTMLReflowState &, aReflowState)); nsresult rv = autoSpaceManager.CreateSpaceManagerFor(aPresContext, this); NS_ENSURE_SUCCESS(rv, rv); diff --git a/mozilla/layout/style/nsStyleStruct.h b/mozilla/layout/style/nsStyleStruct.h index 9bd191c891e..27b99d74ae0 100644 --- a/mozilla/layout/style/nsStyleStruct.h +++ b/mozilla/layout/style/nsStyleStruct.h @@ -748,6 +748,11 @@ struct nsStyleText : public nsStyleStruct { return mWhiteSpace == NS_STYLE_WHITESPACE_PRE || mWhiteSpace == NS_STYLE_WHITESPACE_MOZ_PRE_WRAP; } + + PRBool WhiteSpaceCanWrap() const { + return mWhiteSpace == NS_STYLE_WHITESPACE_NORMAL || + mWhiteSpace == NS_STYLE_WHITESPACE_MOZ_PRE_WRAP; + } }; struct nsStyleVisibility : public nsStyleStruct {