Bug 393096. Allow an element containing breakable whitespace to introduce a break opportunity no matter what the context. Also cleans up some trimming stuff and adds comprehensive whitespace breaking and trimming reftests. r+sr=dbaron

This commit is contained in:
roc+@cs.cmu.edu 2007-10-20 00:30:26 -07:00
Родитель 6bf5ced669
Коммит e1da1696a2
11 изменённых файлов: 446 добавлений и 126 удалений

Просмотреть файл

@ -73,9 +73,9 @@ public:
* into AppendText calls. * into AppendText calls.
* *
* The current strategy is that we break the overall text into * The current strategy is that we break the overall text into
* whitespace-delimited "words". Then for words that contain a "complex" * whitespace-delimited "words". Then those words are passed to the nsILineBreaker
* character (currently CJK or Thai), we break within the word using complex * service for deeper analysis if they contain a "complex" character as described
* rules (JISx4051 or Pango). * below.
*/ */
class nsLineBreaker { class nsLineBreaker {
public: public:
@ -102,9 +102,9 @@ public:
(0xff00 <= u && u <= 0xffef); // Halfwidth and Fullwidth Forms (0xff00 <= u && u <= 0xffef); // Halfwidth and Fullwidth Forms
} }
// Normally, break opportunities exist at the end of each run of whitespace // Break opportunities exist at the end of each run of breakable whitespace
// (see IsSpace above). Break opportunities can also exist inside runs of // (see IsSpace above). Break opportunities can also exist between pairs of
// non-whitespace, as determined by nsILineBreaker. We pass a whitespace- // non-whitespace characters, as determined by nsILineBreaker. We pass a whitespace-
// delimited word to nsILineBreaker if it contains at least one character // delimited word to nsILineBreaker if it contains at least one character
// matching IsComplexChar. // matching IsComplexChar.
// We provide flags to control on a per-chunk basis where breaks are allowed. // We provide flags to control on a per-chunk basis where breaks are allowed.
@ -114,22 +114,38 @@ public:
// We operate on text after whitespace processing has been applied, so // We operate on text after whitespace processing has been applied, so
// other characters (e.g. tabs and newlines) may have been converted to // other characters (e.g. tabs and newlines) may have been converted to
// spaces. // spaces.
/**
* Flags passed with each chunk of text.
*/
enum { enum {
/** /*
* Allow a break opportunity at the start of this chunk of text. * Do not introduce a break opportunity at the start of this chunk of text.
*/ */
BREAK_ALLOW_INITIAL = 0x01, BREAK_SUPPRESS_INITIAL = 0x01,
/** /**
* Allow a break opportunity in the interior of this chunk of text. * Do not introduce a break opportunity in the interior of this chunk of text.
* Also, whitespace in this chunk is treated as non-breakable.
*/ */
BREAK_ALLOW_INSIDE = 0x02 BREAK_SUPPRESS_INSIDE = 0x02,
/**
* The sink currently is already set up to have no breaks in it;
* if no breaks are possible, nsLineBreaker does not need to call
* SetBreaks on it. This is useful when handling large quantities of
* preformatted text; the textruns will never have any breaks set on them,
* and there is no need to ever actually scan the text for breaks, except
* at the end of textruns in case context is needed for following breakable
* text.
*/
BREAK_SKIP_SETTING_NO_BREAKS = 0x04
}; };
/** /**
* Append "invisible whitespace". This acts like whitespace, but there is * Append "invisible whitespace". This acts like whitespace, but there is
* no actual text associated with it. * no actual text associated with it. Only the BREAK_SUPPRESS_INSIDE flag
* is relevant here.
*/ */
nsresult AppendInvisibleWhitespace(); nsresult AppendInvisibleWhitespace(PRUint32 aFlags);
/** /**
* Feed Unicode text into the linebreaker for analysis. aLength must be * Feed Unicode text into the linebreaker for analysis. aLength must be
@ -184,8 +200,11 @@ private:
nsAutoTArray<TextItem,2> mTextItems; nsAutoTArray<TextItem,2> mTextItems;
PRPackedBool mCurrentWordContainsComplexChar; PRPackedBool mCurrentWordContainsComplexChar;
// True if the previous character was whitespace // True if the previous character was breakable whitespace
PRPackedBool mAfterSpace; PRPackedBool mAfterBreakableSpace;
// True if a break must be allowed at the current position because
// a run of breakable whitespace ends here
PRPackedBool mBreakHere;
}; };
#endif /*NSLINEBREAKER_H_*/ #endif /*NSLINEBREAKER_H_*/

Просмотреть файл

@ -42,7 +42,7 @@
nsLineBreaker::nsLineBreaker() nsLineBreaker::nsLineBreaker()
: mCurrentWordContainsComplexChar(PR_FALSE), : mCurrentWordContainsComplexChar(PR_FALSE),
mAfterSpace(PR_FALSE) mAfterBreakableSpace(PR_FALSE), mBreakHere(PR_FALSE)
{ {
} }
@ -72,10 +72,10 @@ nsLineBreaker::FlushCurrentWord()
TextItem* ti = &mTextItems[i]; TextItem* ti = &mTextItems[i];
NS_ASSERTION(ti->mLength > 0, "Zero length word contribution?"); NS_ASSERTION(ti->mLength > 0, "Zero length word contribution?");
if (!(ti->mFlags & BREAK_ALLOW_INITIAL) && ti->mSinkOffset == 0) { if ((ti->mFlags & BREAK_SUPPRESS_INITIAL) && ti->mSinkOffset == 0) {
breakState[offset] = PR_FALSE; breakState[offset] = PR_FALSE;
} }
if (!(ti->mFlags & BREAK_ALLOW_INSIDE)) { if (ti->mFlags & BREAK_SUPPRESS_INSIDE) {
PRUint32 exclude = ti->mSinkOffset == 0 ? 1 : 0; PRUint32 exclude = ti->mSinkOffset == 0 ? 1 : 0;
memset(breakState.Elements() + offset + exclude, PR_FALSE, ti->mLength - exclude); memset(breakState.Elements() + offset + exclude, PR_FALSE, ti->mLength - exclude);
} }
@ -107,7 +107,7 @@ nsLineBreaker::AppendText(nsIAtom* aLangGroup, const PRUnichar* aText, PRUint32
// Continue the current word // Continue the current word
if (mCurrentWord.Length() > 0) { if (mCurrentWord.Length() > 0) {
NS_ASSERTION(!mAfterSpace, "These should not be set"); NS_ASSERTION(!mAfterBreakableSpace && !mBreakHere, "These should not be set");
while (offset < aLength && !IsSpace(aText[offset])) { while (offset < aLength && !IsSpace(aText[offset])) {
mCurrentWord.AppendElement(aText[offset]); mCurrentWord.AppendElement(aText[offset]);
@ -137,8 +137,14 @@ nsLineBreaker::AppendText(nsIAtom* aLangGroup, const PRUnichar* aText, PRUint32
} }
PRUint32 start = offset; PRUint32 start = offset;
if (!aSink && !aFlags) { PRBool noBreaksNeeded = !aSink ||
// Skip to the space before the last word, since we don't need the breaks ((aFlags & BREAK_SUPPRESS_INITIAL) && (aFlags & BREAK_SUPPRESS_INSIDE) &&
!mBreakHere && !mAfterBreakableSpace && (aFlags & BREAK_SKIP_SETTING_NO_BREAKS));
if (noBreaksNeeded) {
// Skip to the space before the last word, since either the break data
// here is not needed, or no breaks are set in the sink and there cannot
// be any breaks in this chunk; all we need is the context for the next
// chunk (if any)
offset = aLength; offset = aLength;
while (offset > start) { while (offset > start) {
--offset; --offset;
@ -152,16 +158,17 @@ nsLineBreaker::AppendText(nsIAtom* aLangGroup, const PRUnichar* aText, PRUint32
for (;;) { for (;;) {
PRUnichar ch = aText[offset]; PRUnichar ch = aText[offset];
PRBool isSpace = IsSpace(ch); PRBool isSpace = IsSpace(ch);
PRBool isBreakableSpace = isSpace && !(aFlags & BREAK_SUPPRESS_INSIDE);
if (aSink) { if (aSink) {
breakState[offset] = mAfterSpace && !isSpace && breakState[offset] = mBreakHere || (mAfterBreakableSpace && !isBreakableSpace);
(aFlags & (offset == 0 ? BREAK_ALLOW_INITIAL : BREAK_ALLOW_INSIDE));
} }
mAfterSpace = isSpace; mBreakHere = PR_FALSE;
mAfterBreakableSpace = isBreakableSpace;
if (isSpace) { if (isSpace) {
if (offset > wordStart && wordHasComplexChar) { if (offset > wordStart && wordHasComplexChar) {
if (aSink && (aFlags & BREAK_ALLOW_INSIDE)) { if (aSink && !(aFlags & BREAK_SUPPRESS_INSIDE)) {
// Save current start-of-word state because GetJISx4051Breaks will // Save current start-of-word state because GetJISx4051Breaks will
// set it to false // set it to false
PRPackedBool currentStart = breakState[wordStart]; PRPackedBool currentStart = breakState[wordStart];
@ -198,7 +205,7 @@ nsLineBreaker::AppendText(nsIAtom* aLangGroup, const PRUnichar* aText, PRUint32
} }
} }
if (aSink) { if (!noBreaksNeeded) {
aSink->SetBreaks(start, offset - start, breakState.Elements() + start); aSink->SetBreaks(start, offset - start, breakState.Elements() + start);
} }
return NS_OK; return NS_OK;
@ -214,7 +221,7 @@ nsLineBreaker::AppendText(nsIAtom* aLangGroup, const PRUint8* aText, PRUint32 aL
// Continue the current word // Continue the current word
if (mCurrentWord.Length() > 0) { if (mCurrentWord.Length() > 0) {
NS_ASSERTION(!mAfterSpace, "These should not be set"); NS_ASSERTION(!mAfterBreakableSpace && !mBreakHere, "These should not be set");
while (offset < aLength && !IsSpace(aText[offset])) { while (offset < aLength && !IsSpace(aText[offset])) {
mCurrentWord.AppendElement(aText[offset]); mCurrentWord.AppendElement(aText[offset]);
@ -247,8 +254,14 @@ nsLineBreaker::AppendText(nsIAtom* aLangGroup, const PRUint8* aText, PRUint32 aL
} }
PRUint32 start = offset; PRUint32 start = offset;
if (!aSink && !aFlags) { PRBool noBreaksNeeded = !aSink ||
// Skip to the space before the last word, since we don't need the breaks ((aFlags & BREAK_SUPPRESS_INITIAL) && (aFlags & BREAK_SUPPRESS_INSIDE) &&
!mBreakHere && !mAfterBreakableSpace && (aFlags & BREAK_SKIP_SETTING_NO_BREAKS));
if (noBreaksNeeded) {
// Skip to the space before the last word, since either the break data
// here is not needed, or no breaks are set in the sink and there cannot
// be any breaks in this chunk; all we need is the context for the next
// chunk (if any)
offset = aLength; offset = aLength;
while (offset > start) { while (offset > start) {
--offset; --offset;
@ -262,16 +275,17 @@ nsLineBreaker::AppendText(nsIAtom* aLangGroup, const PRUint8* aText, PRUint32 aL
for (;;) { for (;;) {
PRUint8 ch = aText[offset]; PRUint8 ch = aText[offset];
PRBool isSpace = IsSpace(ch); PRBool isSpace = IsSpace(ch);
PRBool isBreakableSpace = isSpace && !(aFlags & BREAK_SUPPRESS_INSIDE);
if (aSink) { if (aSink) {
breakState[offset] = mAfterSpace && !isSpace && breakState[offset] = mBreakHere || (mAfterBreakableSpace && !isBreakableSpace);
(aFlags & (offset == 0 ? BREAK_ALLOW_INITIAL : BREAK_ALLOW_INSIDE));
} }
mAfterSpace = isSpace; mBreakHere = PR_FALSE;
mAfterBreakableSpace = isBreakableSpace;
if (isSpace) { if (isSpace) {
if (offset > wordStart && wordHasComplexChar) { if (offset > wordStart && wordHasComplexChar) {
if (aSink && (aFlags & BREAK_ALLOW_INSIDE)) { if (aSink && !(aFlags & BREAK_SUPPRESS_INSIDE)) {
// Save current start-of-word state because GetJISx4051Breaks will // Save current start-of-word state because GetJISx4051Breaks will
// set it to false // set it to false
PRPackedBool currentStart = breakState[wordStart]; PRPackedBool currentStart = breakState[wordStart];
@ -311,18 +325,22 @@ nsLineBreaker::AppendText(nsIAtom* aLangGroup, const PRUint8* aText, PRUint32 aL
} }
} }
if (aSink) { if (!noBreaksNeeded) {
aSink->SetBreaks(start, offset - start, breakState.Elements() + start); aSink->SetBreaks(start, offset - start, breakState.Elements() + start);
} }
return NS_OK; return NS_OK;
} }
nsresult nsresult
nsLineBreaker::AppendInvisibleWhitespace() { nsLineBreaker::AppendInvisibleWhitespace(PRUint32 aFlags) {
// Treat as "invisible whitespace"
nsresult rv = FlushCurrentWord(); nsresult rv = FlushCurrentWord();
if (NS_FAILED(rv)) if (NS_FAILED(rv))
return rv; return rv;
mAfterSpace = PR_TRUE;
PRBool isBreakableSpace = !(aFlags & BREAK_SUPPRESS_INSIDE);
if (mAfterBreakableSpace && !isBreakableSpace) {
mBreakHere = PR_TRUE;
}
mAfterBreakableSpace = isBreakableSpace;
return NS_OK; return NS_OK;
} }

Просмотреть файл

@ -118,6 +118,7 @@ nsLineLayout::nsLineLayout(nsPresContext* aPresContext,
mPlacedFloats = 0; mPlacedFloats = 0;
mTotalPlacedFrames = 0; mTotalPlacedFrames = 0;
mTopEdge = 0; mTopEdge = 0;
mTrimmableWidth = 0;
// Instead of always pre-initializing the free-lists for frames and // Instead of always pre-initializing the free-lists for frames and
// spans, we do it on demand so that situations that only use a few // spans, we do it on demand so that situations that only use a few
@ -1034,7 +1035,9 @@ nsLineLayout::ReflowFrame(nsIFrame* aFrame,
} }
if (!continuingTextRun) { if (!continuingTextRun) {
SetHasTrailingTextFrame(PR_FALSE); if (!pfd->GetFlag(PFD_SKIPWHENTRIMMINGWHITESPACE)) {
mTrimmableWidth = 0;
}
if (!psd->mNoWrap && (!CanPlaceFloatNow() || placedFloat)) { if (!psd->mNoWrap && (!CanPlaceFloatNow() || placedFloat)) {
// record soft break opportunity after this content that can't be // 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 // part of a text run. This is not a text frame so we know
@ -1183,7 +1186,7 @@ nsLineLayout::CanPlaceFrame(PerFrameData* pfd,
// Set outside to PR_TRUE if the result of the reflow leads to the // Set outside to PR_TRUE if the result of the reflow leads to the
// frame sticking outside of our available area. // frame sticking outside of our available area.
PRBool outside = pfd->mBounds.XMost() + endMargin > psd->mRightEdge; PRBool outside = pfd->mBounds.XMost() - mTrimmableWidth + endMargin > psd->mRightEdge;
if (!outside) { if (!outside) {
// If it fits, it fits // If it fits, it fits
#ifdef NOISY_CAN_PLACE_FRAME #ifdef NOISY_CAN_PLACE_FRAME

Просмотреть файл

@ -143,10 +143,9 @@ protected:
#define LL_LASTFLOATWASLETTERFRAME 0x00000080 #define LL_LASTFLOATWASLETTERFRAME 0x00000080
#define LL_CANPLACEFLOAT 0x00000100 #define LL_CANPLACEFLOAT 0x00000100
#define LL_LINEENDSINBR 0x00000200 #define LL_LINEENDSINBR 0x00000200
#define LL_HASTRAILINGTEXTFRAME 0x00000400 #define LL_NEEDBACKUP 0x00000400
#define LL_NEEDBACKUP 0x00000800 #define LL_INFIRSTLINE 0x00000800
#define LL_INFIRSTLINE 0x00002000 #define LL_GOTLINEBOX 0x00001000
#define LL_GOTLINEBOX 0x00004000
#define LL_LASTFLAG LL_GOTLINEBOX #define LL_LASTFLAG LL_GOTLINEBOX
PRUint16 mFlags; PRUint16 mFlags;
@ -203,17 +202,8 @@ public:
return mBlockRS->AddFloat(*this, aFrame, PR_FALSE, aReflowStatus); return mBlockRS->AddFloat(*this, aFrame, PR_FALSE, aReflowStatus);
} }
/** void SetTrimmableWidth(nscoord aTrimmableWidth) {
* If the last content placed on the line (not counting inline containers) mTrimmableWidth = aTrimmableWidth;
* 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 flag is
* true.
*/
PRBool HasTrailingTextFrame() const {
return GetFlag(LL_HASTRAILINGTEXTFRAME);
}
void SetHasTrailingTextFrame(PRBool aHasTrailingTextFrame) {
SetFlag(LL_HASTRAILINGTEXTFRAME, aHasTrailingTextFrame);
} }
//---------------------------------------- //----------------------------------------
@ -378,6 +368,9 @@ protected:
// the block has been called. // the block has been called.
nscoord mFinalLineHeight; nscoord mFinalLineHeight;
// Amount of trimmable whitespace width for the trailing text frame, if any
nscoord mTrimmableWidth;
// Per-frame data recorded by the line-layout reflow logic. This // Per-frame data recorded by the line-layout reflow logic. This
// state is the state needed to post-process the line after reflow // state is the state needed to post-process the line after reflow
// has completed (vertical alignment, horizontal alignment, // has completed (vertical alignment, horizontal alignment,

Просмотреть файл

@ -654,6 +654,8 @@ public:
if (mTextRun->SetPotentialLineBreaks(aOffset + mOffsetIntoTextRun, aLength, if (mTextRun->SetPotentialLineBreaks(aOffset + mOffsetIntoTextRun, aLength,
aBreakBefore, mContext)) { aBreakBefore, mContext)) {
mChangedBreaks = PR_TRUE; mChangedBreaks = PR_TRUE;
// Be conservative and assume that some breaks have been set
mTextRun->ClearFlagBits(nsTextFrameUtils::TEXT_NO_BREAKS);
} }
} }
@ -1627,38 +1629,29 @@ BuildTextRunsScanner::SetupBreakSinksForTextRun(gfxTextRun* aTextRun,
: mMappedFlows[i + 1].mTransformedTextOffset) : mMappedFlows[i + 1].mTransformedTextOffset)
- offset; - offset;
nsTextFrame* startFrame = mappedFlow->mStartFrame;
if (HasCompressedLeadingWhitespace(startFrame, mappedFlow->GetContentEnd(), iter)) {
mLineBreaker.AppendInvisibleWhitespace();
}
if (length > 0) {
PRUint32 flags = 0; PRUint32 flags = 0;
nsIFrame* initialBreakController = mappedFlow->mAncestorControllingInitialBreak; nsIFrame* initialBreakController = mappedFlow->mAncestorControllingInitialBreak;
if (!initialBreakController) { if (!initialBreakController) {
initialBreakController = mLineContainer; initialBreakController = mLineContainer;
} }
if (initialBreakController->GetStyleText()->WhiteSpaceCanWrap()) { if (!initialBreakController->GetStyleText()->WhiteSpaceCanWrap()) {
flags |= nsLineBreaker::BREAK_ALLOW_INITIAL; flags |= nsLineBreaker::BREAK_SUPPRESS_INITIAL;
} }
nsTextFrame* startFrame = mappedFlow->mStartFrame;
const nsStyleText* textStyle = startFrame->GetStyleText(); const nsStyleText* textStyle = startFrame->GetStyleText();
if (textStyle->WhiteSpaceCanWrap()) { if (!textStyle->WhiteSpaceCanWrap()) {
// If white-space is preserved, then the only break opportunity is at flags |= nsLineBreaker::BREAK_SUPPRESS_INSIDE;
// the end of whitespace runs; otherwise there is a break opportunity before }
// and after each whitespace character if (aTextRun->GetFlags() & nsTextFrameUtils::TEXT_NO_BREAKS) {
flags |= nsLineBreaker::BREAK_ALLOW_INSIDE; flags |= nsLineBreaker::BREAK_SKIP_SETTING_NO_BREAKS;
} }
BreakSink* sink = *breakSink; if (HasCompressedLeadingWhitespace(startFrame, mappedFlow->GetContentEnd(), iter)) {
if (aSuppressSink) { mLineBreaker.AppendInvisibleWhitespace(flags);
sink = nsnull;
} else if (flags) {
aTextRun->ClearFlagBits(nsTextFrameUtils::TEXT_NO_BREAKS);
} else if (aTextRun->GetFlags() & nsTextFrameUtils::TEXT_NO_BREAKS) {
// Don't bother setting breaks on a textrun that can't be broken
// and currently has no breaks set...
sink = nsnull;
} }
if (length > 0) {
BreakSink* sink = aSuppressSink ? nsnull : (*breakSink).get();
if (aTextRun->GetFlags() & gfxFontGroup::TEXT_IS_8BIT) { if (aTextRun->GetFlags() & gfxFontGroup::TEXT_IS_8BIT) {
mLineBreaker.AppendText(lang, aTextRun->GetText8Bit() + offset, mLineBreaker.AppendText(lang, aTextRun->GetText8Bit() + offset,
length, flags, sink); length, flags, sink);
@ -1812,8 +1805,7 @@ nsTextFrame::GetTrimmedOffsets(const nsTextFragment* aFrag,
offsets.mLength -= whitespaceCount; offsets.mLength -= whitespaceCount;
} }
if (aTrimAfter && (GetStateBits() & TEXT_END_OF_LINE) && if (aTrimAfter && (GetStateBits() & TEXT_END_OF_LINE)) {
textStyle->WhiteSpaceCanWrap()) {
PRInt32 whitespaceCount = PRInt32 whitespaceCount =
GetTrimmableWhitespaceCount(aFrag, offsets.GetEnd() - 1, GetTrimmableWhitespaceCount(aFrag, offsets.GetEnd() - 1,
offsets.mLength, -1); offsets.mLength, -1);
@ -5216,8 +5208,6 @@ nsTextFrame::Reflow(nsPresContext* aPresContext,
NS_ASSERTION(!(NS_REFLOW_CALC_BOUNDING_METRICS & aMetrics.mFlags), NS_ASSERTION(!(NS_REFLOW_CALC_BOUNDING_METRICS & aMetrics.mFlags),
"We shouldn't be passed NS_REFLOW_CALC_BOUNDING_METRICS anymore"); "We shouldn't be passed NS_REFLOW_CALC_BOUNDING_METRICS anymore");
#endif #endif
PRBool suppressInitialBreak = !lineLayout.LineIsBreakable() ||
!lineLayout.HasTrailingTextFrame();
PRInt32 limitLength = length; PRInt32 limitLength = length;
PRInt32 forceBreak = lineLayout.GetForcedBreakPosition(mContent); PRInt32 forceBreak = lineLayout.GetForcedBreakPosition(mContent);
@ -5249,13 +5239,12 @@ nsTextFrame::Reflow(nsPresContext* aPresContext,
PRBool usedHyphenation; PRBool usedHyphenation;
gfxFloat trimmedWidth = 0; gfxFloat trimmedWidth = 0;
gfxFloat availWidth = aReflowState.availableWidth; gfxFloat availWidth = aReflowState.availableWidth;
PRBool canTrimTrailingWhitespace = !textStyle->WhiteSpaceIsSignificant() && PRBool canTrimTrailingWhitespace = !textStyle->WhiteSpaceIsSignificant();
textStyle->WhiteSpaceCanWrap();
PRUint32 transformedCharsFit = PRUint32 transformedCharsFit =
mTextRun->BreakAndMeasureText(transformedOffset, transformedLength, mTextRun->BreakAndMeasureText(transformedOffset, transformedLength,
(GetStateBits() & TEXT_START_OF_LINE) != 0, (GetStateBits() & TEXT_START_OF_LINE) != 0,
availWidth, availWidth,
&provider, suppressInitialBreak, &provider, !lineLayout.LineIsBreakable(),
canTrimTrailingWhitespace ? &trimmedWidth : nsnull, canTrimTrailingWhitespace ? &trimmedWidth : nsnull,
&textMetrics, needTightBoundingBox, ctx, &textMetrics, needTightBoundingBox, ctx,
&usedHyphenation, &transformedLastBreak); &usedHyphenation, &transformedLastBreak);
@ -5293,26 +5282,38 @@ nsTextFrame::Reflow(nsPresContext* aPresContext,
AddStateBits(TEXT_HYPHEN_BREAK); AddStateBits(TEXT_HYPHEN_BREAK);
} }
// If everything fits including trimmed whitespace, then we should add the gfxFloat trimmableWidth = 0;
// trimmed whitespace to our metrics now because it probably won't be trimmed if (canTrimTrailingWhitespace) {
// and we need to position subsequent frames correctly... // Optimization: if we trimmed trailing whitespace, and we can be sure
if (forceBreak < 0 && textMetrics.mAdvanceWidth + trimmedWidth <= availWidth) { // this frame will be at the end of the line, then leave it trimmed off.
// Otherwise we have to undo the trimming, in case we're not at the end of
// the line. (If we actually do end up at the end of the line, we'll have
// to trim it off again in TrimTrailingWhiteSpace, and we'd like to avoid
// having to re-do it.)
if (forceBreak >= 0 || transformedCharsFit < transformedLength) {
// We're definitely going to break so our trailing whitespace should
// definitely be timmed. Record that we've already done it.
AddStateBits(TEXT_TRIMMED_TRAILING_WHITESPACE);
} else {
// We might not be at the end of the line. (Note that even if this frame
// ends in breakable whitespace, it might not be at the end of the line
// because it might be followed by breakable, but preformatted, whitespace.)
// Undo the trimming.
textMetrics.mAdvanceWidth += trimmedWidth; textMetrics.mAdvanceWidth += trimmedWidth;
trimmableWidth = trimmedWidth;
if (mTextRun->IsRightToLeft()) { if (mTextRun->IsRightToLeft()) {
// Space comes before text, so the bounding box is moved to the // Space comes before text, so the bounding box is moved to the
// right by trimmdWidth // right by trimmdWidth
textMetrics.mBoundingBox.MoveBy(gfxPoint(trimmedWidth, 0)); textMetrics.mBoundingBox.MoveBy(gfxPoint(trimmedWidth, 0));
} }
// Since everything fit and no break was forced,
// record the last break opportunity
if (lastBreak >= 0) { if (lastBreak >= 0) {
lineLayout.NotifyOptionalBreakPosition(mContent, lastBreak, lineLayout.NotifyOptionalBreakPosition(mContent, lastBreak,
textMetrics.mAdvanceWidth <= aReflowState.availableWidth); textMetrics.mAdvanceWidth <= aReflowState.availableWidth);
} }
} else { }
// We're definitely going to break and our whitespace will definitely
// be trimmed.
// Record that whitespace has already been trimmed.
AddStateBits(TEXT_TRIMMED_TRAILING_WHITESPACE);
} }
PRInt32 contentLength = offset + charsFit - GetContentOffset(); PRInt32 contentLength = offset + charsFit - GetContentOffset();
@ -5347,26 +5348,20 @@ nsTextFrame::Reflow(nsPresContext* aPresContext,
// Clean up, update state // Clean up, update state
///////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////
if (charsFit > 0) { // If all our characters are discarded or collapsed, then trimmable width
lineLayout.SetHasTrailingTextFrame(PR_TRUE); // from the last textframe should be preserved. Otherwise the trimmable width
if (charsFit == length) { // from this textframe overrides. (Currently in CSS trimmable width can be
if (textStyle->WhiteSpaceCanWrap() && // at most one space so there's no way for trimmable width from a previous
IsTrimmableSpace(frag, offset + charsFit - 1)) { // frame to accumulate with trimmable width from this frame.)
// Record a potential break after final breakable whitespace if (transformedCharsFit > 0) {
lineLayout.NotifyOptionalBreakPosition(mContent, offset + length, lineLayout.SetTrimmableWidth(NSToCoordFloor(trimmableWidth));
textMetrics.mAdvanceWidth <= aReflowState.availableWidth); }
} else if (HasSoftHyphenBefore(frag, mTextRun, offset, end)) { if (charsFit > 0 && charsFit == length &&
HasSoftHyphenBefore(frag, mTextRun, offset, end)) {
// Record a potential break after final soft hyphen // Record a potential break after final soft hyphen
lineLayout.NotifyOptionalBreakPosition(mContent, offset + length, lineLayout.NotifyOptionalBreakPosition(mContent, offset + length,
textMetrics.mAdvanceWidth + provider.GetHyphenWidth() <= availWidth); textMetrics.mAdvanceWidth + provider.GetHyphenWidth() <= availWidth);
} }
}
} 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.SetHasTrailingTextFrame(PR_FALSE);
}
if (completedFirstLetter) { if (completedFirstLetter) {
lineLayout.SetFirstLetterStyleOK(PR_FALSE); lineLayout.SetFirstLetterStyleOK(PR_FALSE);
} }

Просмотреть файл

@ -2,3 +2,6 @@
== soft-hyphens-1a.html soft-hyphens-1-ref.html == soft-hyphens-1a.html soft-hyphens-1-ref.html
== soft-hyphens-1b.html soft-hyphens-1-ref.html == soft-hyphens-1b.html soft-hyphens-1-ref.html
== soft-hyphens-1c.html soft-hyphens-1-ref.html == soft-hyphens-1c.html soft-hyphens-1-ref.html
== white-space-1a.html white-space-1-ref.html
== white-space-1b.html white-space-1-ref.html
== white-space-2.html white-space-2-ref.html

Просмотреть файл

@ -0,0 +1,64 @@
<!DOCTYPE HTML>
<html>
<head>
<!-- Reference -->
<style>
div { border:1px solid black; }
.container { float:left; width:20%; border-color:cyan; }
p { width:0; }
b { font-weight:normal; background-color:yellow; white-space:pre; }
</style>
</head>
<body>
<div>
<p><b>Hello
Kitty</b>
<p><b>Hello Kitty</b>
<p><b>Hello Kitty</b>
<p><b>Hello
Kitty</b>
</div>
<div class="container">
<p><b>Hello
Kitty</b>
<p><b>Hello
Kitty</b>
<p><b>Hello
Kitty</b>
<p><b>Hello
Kitty</b>
</div>
<div class="container">
<p><b>Hello
Kitty</b>
<p><b>Hello Kitty</b>
<p><b>Hello Kitty</b>
<p><b>Hello
Kitty</b>
</div>
<div class="container">
<p><b>Hello
Kitty</b>
<p><b>Hello Kitty</b>
<p><b>Hello Kitty</b>
<p><b>Hello
Kitty</b>
</div>
<div class="container">
<p><b>Hello
Kitty</b>
<p><b>Hello
Kitty</b>
<p><b>Hello
Kitty</b>
<p><b>Hello
Kitty</b>
</div>
</body>
</html>

Просмотреть файл

@ -0,0 +1,53 @@
<!DOCTYPE HTML>
<html>
<head>
<!-- Testing all combinations of pre/nowrap/pre-wrap/normal space pairs -->
<style>
.nowrap { white-space:nowrap; }
.pre { white-space:pre; }
.prewrap { white-space:-moz-pre-wrap; }
div { border:1px solid black; }
.container { float:left; width:20%; border-color:cyan; }
p { width:0; }
b { font-weight:normal; background-color:yellow; }
</style>
</head>
<body>
<div>
<p><b>Hello Kitty</b>
<p><b>Hello<span class="pre"> </span>Kitty</b>
<p><b>Hello<span class="nowrap"> </span>Kitty</b>
<p><b>Hello<span class="prewrap"> </span>Kitty</b>
</div>
<div class="container">
<p><b>Hello Kitty</b>
<p><b>Hello<span class="pre"> </span> Kitty</b>
<p><b>Hello<span class="nowrap"> </span> Kitty</b>
<p><b>Hello<span class="prewrap"> </span> Kitty</b>
</div>
<div class="container">
<p><b>Hello <span class="pre"> </span>Kitty</b>
<p><b>Hello<span class="pre"> </span><span class="pre"> </span>Kitty</b>
<p><b>Hello<span class="nowrap"> </span><span class="pre"> </span>Kitty</b>
<p><b>Hello<span class="prewrap"> </span><span class="pre"> </span>Kitty</b>
</div>
<div class="container">
<p><b>Hello <span class="nowrap"> </span>Kitty</b>
<p><b>Hello<span class="pre"> </span><span class="nowrap"> </span>Kitty</b>
<p><b>Hello<span class="nowrap"> </span><span class="nowrap"> </span>Kitty</b>
<p><b>Hello<span class="prewrap"> </span><span class="nowrap"> </span>Kitty</b>
</div>
<div class="container">
<p><b>Hello <span class="prewrap"> </span>Kitty</b>
<p><b>Hello<span class="pre"> </span><span class="prewrap"> </span>Kitty</b>
<p><b>Hello<span class="nowrap"> </span><span class="prewrap"> </span>Kitty</b>
<p><b>Hello<span class="prewrap"> </span><span class="prewrap"> </span>Kitty</b>
</div>
</body>
</html>

Просмотреть файл

@ -0,0 +1,53 @@
<!DOCTYPE HTML>
<html>
<head>
<!-- Adding extra span boundaries -->
<style>
.nowrap { white-space:nowrap; }
.pre { white-space:pre; }
.prewrap { white-space:-moz-pre-wrap; }
div { border:1px solid black; }
.container { float:left; width:20%; border-color:cyan; }
p { width:0; }
b { font-weight:normal; background-color:yellow; }
</style>
</head>
<body>
<div>
<p><b><span>Hello<span> <span>Kitty</span></b>
<p><b><span>Hello<span><span class="pre"> </span><span>Kitty</span></b>
<p><b><span>Hello<span><span class="nowrap"> </span><span>Kitty</span></b>
<p><b><span>Hello<span><span class="prewrap"> </span><span>Kitty</span></b>
</div>
<div class="container">
<p><b><span>Hello<span> <span>Kitty</span></b>
<p><b><span>Hello<span><span class="pre"> </span> <span>Kitty</span></b>
<p><b><span>Hello<span><span class="nowrap"> </span> <span>Kitty</span></b>
<p><b><span>Hello<span><span class="prewrap"> </span> <span>Kitty</span></b>
</div>
<div class="container">
<p><b><span>Hello<span> <span class="pre"> </span><span>Kitty</span></b>
<p><b><span>Hello<span><span class="pre"> </span><span class="pre"> </span><span>Kitty</span></b>
<p><b><span>Hello<span><span class="nowrap"> </span><span class="pre"> </span><span>Kitty</span></b>
<p><b><span>Hello<span><span class="prewrap"> </span><span class="pre"> </span><span>Kitty</span></b>
</div>
<div class="container">
<p><b><span>Hello<span> <span class="nowrap"> </span>Kitty</b>
<p><b><span>Hello<span><span class="pre"> </span><span class="nowrap"> </span><span>Kitty</span></b>
<p><b><span>Hello<span><span class="nowrap"> </span><span class="nowrap"> </span><span>Kitty</span></b>
<p><b><span>Hello<span><span class="prewrap"> </span><span class="nowrap"> </span><span>Kitty</span></b>
</div>
<div class="container">
<p><b><span>Hello<span> <span class="prewrap"> </span>Kitty</b>
<p><b><span>Hello<span><span class="pre"> </span><span class="prewrap"> </span><span>Kitty</span></b>
<p><b><span>Hello<span><span class="nowrap"> </span><span class="prewrap"> </span><span>Kitty</span></b>
<p><b><span>Hello<span><span class="prewrap"> </span><span class="prewrap"> </span><span>Kitty</span></b>
</div>
</body>
</html>

Просмотреть файл

@ -0,0 +1,65 @@
<!DOCTYPE HTML>
<html>
<head>
<!-- Reference -->
<style>
div { border:1px solid black; }
.container { float:left; width:20%; border-color:cyan; }
p { width:0; }
b { font-weight:normal; background-color:yellow; white-space:pre; }
.cell { display:table-cell; border:1px solid green; }
</style>
</head>
<body>
<div>
<p><span class="cell"><b>Hello
Kitty</b></span>
<p><span class="cell"><b>Hello Kitty</b></span>
<p><span class="cell"><b>Hello Kitty</b></span>
<p><span class="cell"><b>Hello
Kitty</b></span>
</div>
<div class="container">
<p><span class="cell"><b>Hello
Kitty</b></span>
<p><span class="cell"><b>Hello
Kitty</b></span>
<p><span class="cell"><b>Hello
Kitty</b></span>
<p><span class="cell"><b>Hello
Kitty</b></span>
</div>
<div class="container">
<p><span class="cell"><b>Hello
Kitty</b></span>
<p><span class="cell"><b>Hello Kitty</b></span>
<p><span class="cell"><b>Hello Kitty</b></span>
<p><span class="cell"><b>Hello
Kitty</b></span>
</div>
<div class="container">
<p><span class="cell"><b>Hello
Kitty</b></span>
<p><span class="cell"><b>Hello Kitty</b></span>
<p><span class="cell"><b>Hello Kitty</b></span>
<p><span class="cell"><b>Hello
Kitty</b></span>
</div>
<div class="container">
<p><span class="cell"><b>Hello
Kitty</b></span>
<p><span class="cell"><b>Hello
Kitty</b></span>
<p><span class="cell"><b>Hello
Kitty</b></span>
<p><span class="cell"><b>Hello
Kitty</b></span>
</div>
</body>
</html>

Просмотреть файл

@ -0,0 +1,54 @@
<!DOCTYPE HTML>
<html>
<head>
<!-- Wrapping tests in table-cell to test min-width computation -->
<style>
div { border:1px solid black; }
.container { float:left; width:20%; border-color:cyan; }
p { width:0; }
b { font-weight:normal; background-color:yellow; }
.cell { display:table-cell; border:1px solid green; }
.nowrap { white-space:nowrap; }
.pre { white-space:pre; }
.prewrap { white-space:-moz-pre-wrap; }
</style>
</head>
<body>
<div>
<p><span class="cell"><b>Hello Kitty</b></span>
<p><span class="cell"><b>Hello<span class="pre"> </span>Kitty</b></span>
<p><span class="cell"><b>Hello<span class="nowrap"> </span>Kitty</b></span>
<p><span class="cell"><b>Hello<span class="prewrap"> </span>Kitty</b></span>
</div>
<div class="container">
<p><span class="cell"><b>Hello <span>Kitty</span></b></span>
<p><span class="cell"><b>Hello<span class="pre"> </span> Kitty</b></span>
<p><span class="cell"><b>Hello<span class="nowrap"> </span> Kitty</b></span>
<p><span class="cell"><b>Hello<span class="prewrap"> </span> Kitty</b></span>
</div>
<div class="container">
<p><span class="cell"><b>Hello <span class="pre"> </span>Kitty</b></span>
<p><span class="cell"><b>Hello<span class="pre"> </span><span class="pre"> </span>Kitty</b></span>
<p><span class="cell"><b>Hello<span class="nowrap"> </span><span class="pre"> </span>Kitty</b></span>
<p><span class="cell"><b>Hello<span class="prewrap"> </span><span class="pre"> </span>Kitty</b></span>
</div>
<div class="container">
<p><span class="cell"><b>Hello <span class="nowrap"> </span>Kitty</b></span>
<p><span class="cell"><b>Hello<span class="pre"> </span><span class="nowrap"> </span>Kitty</b></span>
<p><span class="cell"><b>Hello<span class="nowrap"> </span><span class="nowrap"> </span>Kitty</b></span>
<p><span class="cell"><b>Hello<span class="prewrap"> </span><span class="nowrap"> </span>Kitty</b></span>
</div>
<div class="container">
<p><span class="cell"><b>Hello <span class="prewrap"> </span>Kitty</b></span>
<p><span class="cell"><b>Hello<span class="pre"> </span><span class="prewrap"> </span>Kitty</b></span>
<p><span class="cell"><b>Hello<span class="nowrap"> </span><span class="prewrap"> </span>Kitty</b></span>
<p><span class="cell"><b>Hello<span class="prewrap"> </span><span class="prewrap"> </span>Kitty</b></span>
</div>
</body>
</html>