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
parent 6bf5ced669
commit e1da1696a2
11 changed files with 446 additions and 126 deletions

View File

@ -73,9 +73,9 @@ public:
* into AppendText calls.
*
* The current strategy is that we break the overall text into
* whitespace-delimited "words". Then for words that contain a "complex"
* character (currently CJK or Thai), we break within the word using complex
* rules (JISx4051 or Pango).
* whitespace-delimited "words". Then those words are passed to the nsILineBreaker
* service for deeper analysis if they contain a "complex" character as described
* below.
*/
class nsLineBreaker {
public:
@ -102,9 +102,9 @@ public:
(0xff00 <= u && u <= 0xffef); // Halfwidth and Fullwidth Forms
}
// Normally, break opportunities exist at the end of each run of whitespace
// (see IsSpace above). Break opportunities can also exist inside runs of
// non-whitespace, as determined by nsILineBreaker. We pass a whitespace-
// Break opportunities exist at the end of each run of breakable whitespace
// (see IsSpace above). Break opportunities can also exist between pairs of
// non-whitespace characters, as determined by nsILineBreaker. We pass a whitespace-
// delimited word to nsILineBreaker if it contains at least one character
// matching IsComplexChar.
// 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
// other characters (e.g. tabs and newlines) may have been converted to
// spaces.
/**
* Flags passed with each chunk of text.
*/
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
* 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
@ -184,8 +200,11 @@ private:
nsAutoTArray<TextItem,2> mTextItems;
PRPackedBool mCurrentWordContainsComplexChar;
// True if the previous character was whitespace
PRPackedBool mAfterSpace;
// True if the previous character was breakable whitespace
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_*/

View File

@ -42,7 +42,7 @@
nsLineBreaker::nsLineBreaker()
: mCurrentWordContainsComplexChar(PR_FALSE),
mAfterSpace(PR_FALSE)
mAfterBreakableSpace(PR_FALSE), mBreakHere(PR_FALSE)
{
}
@ -72,10 +72,10 @@ nsLineBreaker::FlushCurrentWord()
TextItem* ti = &mTextItems[i];
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;
}
if (!(ti->mFlags & BREAK_ALLOW_INSIDE)) {
if (ti->mFlags & BREAK_SUPPRESS_INSIDE) {
PRUint32 exclude = ti->mSinkOffset == 0 ? 1 : 0;
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
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])) {
mCurrentWord.AppendElement(aText[offset]);
@ -137,8 +137,14 @@ nsLineBreaker::AppendText(nsIAtom* aLangGroup, const PRUnichar* aText, PRUint32
}
PRUint32 start = offset;
if (!aSink && !aFlags) {
// Skip to the space before the last word, since we don't need the breaks
PRBool noBreaksNeeded = !aSink ||
((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;
while (offset > start) {
--offset;
@ -152,16 +158,17 @@ nsLineBreaker::AppendText(nsIAtom* aLangGroup, const PRUnichar* aText, PRUint32
for (;;) {
PRUnichar ch = aText[offset];
PRBool isSpace = IsSpace(ch);
PRBool isBreakableSpace = isSpace && !(aFlags & BREAK_SUPPRESS_INSIDE);
if (aSink) {
breakState[offset] = mAfterSpace && !isSpace &&
(aFlags & (offset == 0 ? BREAK_ALLOW_INITIAL : BREAK_ALLOW_INSIDE));
breakState[offset] = mBreakHere || (mAfterBreakableSpace && !isBreakableSpace);
}
mAfterSpace = isSpace;
mBreakHere = PR_FALSE;
mAfterBreakableSpace = isBreakableSpace;
if (isSpace) {
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
// set it to false
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);
}
return NS_OK;
@ -214,7 +221,7 @@ nsLineBreaker::AppendText(nsIAtom* aLangGroup, const PRUint8* aText, PRUint32 aL
// Continue the current word
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])) {
mCurrentWord.AppendElement(aText[offset]);
@ -247,8 +254,14 @@ nsLineBreaker::AppendText(nsIAtom* aLangGroup, const PRUint8* aText, PRUint32 aL
}
PRUint32 start = offset;
if (!aSink && !aFlags) {
// Skip to the space before the last word, since we don't need the breaks
PRBool noBreaksNeeded = !aSink ||
((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;
while (offset > start) {
--offset;
@ -262,16 +275,17 @@ nsLineBreaker::AppendText(nsIAtom* aLangGroup, const PRUint8* aText, PRUint32 aL
for (;;) {
PRUint8 ch = aText[offset];
PRBool isSpace = IsSpace(ch);
PRBool isBreakableSpace = isSpace && !(aFlags & BREAK_SUPPRESS_INSIDE);
if (aSink) {
breakState[offset] = mAfterSpace && !isSpace &&
(aFlags & (offset == 0 ? BREAK_ALLOW_INITIAL : BREAK_ALLOW_INSIDE));
breakState[offset] = mBreakHere || (mAfterBreakableSpace && !isBreakableSpace);
}
mAfterSpace = isSpace;
mBreakHere = PR_FALSE;
mAfterBreakableSpace = isBreakableSpace;
if (isSpace) {
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
// set it to false
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);
}
return NS_OK;
}
nsresult
nsLineBreaker::AppendInvisibleWhitespace() {
// Treat as "invisible whitespace"
nsLineBreaker::AppendInvisibleWhitespace(PRUint32 aFlags) {
nsresult rv = FlushCurrentWord();
if (NS_FAILED(rv))
return rv;
mAfterSpace = PR_TRUE;
PRBool isBreakableSpace = !(aFlags & BREAK_SUPPRESS_INSIDE);
if (mAfterBreakableSpace && !isBreakableSpace) {
mBreakHere = PR_TRUE;
}
mAfterBreakableSpace = isBreakableSpace;
return NS_OK;
}

View File

@ -118,6 +118,7 @@ nsLineLayout::nsLineLayout(nsPresContext* aPresContext,
mPlacedFloats = 0;
mTotalPlacedFrames = 0;
mTopEdge = 0;
mTrimmableWidth = 0;
// 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
@ -1034,7 +1035,9 @@ nsLineLayout::ReflowFrame(nsIFrame* aFrame,
}
if (!continuingTextRun) {
SetHasTrailingTextFrame(PR_FALSE);
if (!pfd->GetFlag(PFD_SKIPWHENTRIMMINGWHITESPACE)) {
mTrimmableWidth = 0;
}
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
@ -1183,7 +1186,7 @@ nsLineLayout::CanPlaceFrame(PerFrameData* pfd,
// Set outside to PR_TRUE if the result of the reflow leads to the
// 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 it fits, it fits
#ifdef NOISY_CAN_PLACE_FRAME

View File

@ -143,10 +143,9 @@ protected:
#define LL_LASTFLOATWASLETTERFRAME 0x00000080
#define LL_CANPLACEFLOAT 0x00000100
#define LL_LINEENDSINBR 0x00000200
#define LL_HASTRAILINGTEXTFRAME 0x00000400
#define LL_NEEDBACKUP 0x00000800
#define LL_INFIRSTLINE 0x00002000
#define LL_GOTLINEBOX 0x00004000
#define LL_NEEDBACKUP 0x00000400
#define LL_INFIRSTLINE 0x00000800
#define LL_GOTLINEBOX 0x00001000
#define LL_LASTFLAG LL_GOTLINEBOX
PRUint16 mFlags;
@ -203,17 +202,8 @@ public:
return mBlockRS->AddFloat(*this, aFrame, PR_FALSE, aReflowStatus);
}
/**
* 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 flag is
* true.
*/
PRBool HasTrailingTextFrame() const {
return GetFlag(LL_HASTRAILINGTEXTFRAME);
}
void SetHasTrailingTextFrame(PRBool aHasTrailingTextFrame) {
SetFlag(LL_HASTRAILINGTEXTFRAME, aHasTrailingTextFrame);
void SetTrimmableWidth(nscoord aTrimmableWidth) {
mTrimmableWidth = aTrimmableWidth;
}
//----------------------------------------
@ -377,6 +367,9 @@ protected:
// Final computed line-height value after VerticalAlignFrames for
// the block has been called.
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
// state is the state needed to post-process the line after reflow

View File

@ -654,6 +654,8 @@ public:
if (mTextRun->SetPotentialLineBreaks(aOffset + mOffsetIntoTextRun, aLength,
aBreakBefore, mContext)) {
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)
- offset;
PRUint32 flags = 0;
nsIFrame* initialBreakController = mappedFlow->mAncestorControllingInitialBreak;
if (!initialBreakController) {
initialBreakController = mLineContainer;
}
if (!initialBreakController->GetStyleText()->WhiteSpaceCanWrap()) {
flags |= nsLineBreaker::BREAK_SUPPRESS_INITIAL;
}
nsTextFrame* startFrame = mappedFlow->mStartFrame;
const nsStyleText* textStyle = startFrame->GetStyleText();
if (!textStyle->WhiteSpaceCanWrap()) {
flags |= nsLineBreaker::BREAK_SUPPRESS_INSIDE;
}
if (aTextRun->GetFlags() & nsTextFrameUtils::TEXT_NO_BREAKS) {
flags |= nsLineBreaker::BREAK_SKIP_SETTING_NO_BREAKS;
}
if (HasCompressedLeadingWhitespace(startFrame, mappedFlow->GetContentEnd(), iter)) {
mLineBreaker.AppendInvisibleWhitespace();
mLineBreaker.AppendInvisibleWhitespace(flags);
}
if (length > 0) {
PRUint32 flags = 0;
nsIFrame* initialBreakController = mappedFlow->mAncestorControllingInitialBreak;
if (!initialBreakController) {
initialBreakController = mLineContainer;
}
if (initialBreakController->GetStyleText()->WhiteSpaceCanWrap()) {
flags |= nsLineBreaker::BREAK_ALLOW_INITIAL;
}
const nsStyleText* textStyle = startFrame->GetStyleText();
if (textStyle->WhiteSpaceCanWrap()) {
// If white-space is preserved, then the only break opportunity is at
// the end of whitespace runs; otherwise there is a break opportunity before
// and after each whitespace character
flags |= nsLineBreaker::BREAK_ALLOW_INSIDE;
}
BreakSink* sink = *breakSink;
if (aSuppressSink) {
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;
}
BreakSink* sink = aSuppressSink ? nsnull : (*breakSink).get();
if (aTextRun->GetFlags() & gfxFontGroup::TEXT_IS_8BIT) {
mLineBreaker.AppendText(lang, aTextRun->GetText8Bit() + offset,
length, flags, sink);
@ -1812,8 +1805,7 @@ nsTextFrame::GetTrimmedOffsets(const nsTextFragment* aFrag,
offsets.mLength -= whitespaceCount;
}
if (aTrimAfter && (GetStateBits() & TEXT_END_OF_LINE) &&
textStyle->WhiteSpaceCanWrap()) {
if (aTrimAfter && (GetStateBits() & TEXT_END_OF_LINE)) {
PRInt32 whitespaceCount =
GetTrimmableWhitespaceCount(aFrag, offsets.GetEnd() - 1,
offsets.mLength, -1);
@ -5216,8 +5208,6 @@ nsTextFrame::Reflow(nsPresContext* aPresContext,
NS_ASSERTION(!(NS_REFLOW_CALC_BOUNDING_METRICS & aMetrics.mFlags),
"We shouldn't be passed NS_REFLOW_CALC_BOUNDING_METRICS anymore");
#endif
PRBool suppressInitialBreak = !lineLayout.LineIsBreakable() ||
!lineLayout.HasTrailingTextFrame();
PRInt32 limitLength = length;
PRInt32 forceBreak = lineLayout.GetForcedBreakPosition(mContent);
@ -5249,13 +5239,12 @@ nsTextFrame::Reflow(nsPresContext* aPresContext,
PRBool usedHyphenation;
gfxFloat trimmedWidth = 0;
gfxFloat availWidth = aReflowState.availableWidth;
PRBool canTrimTrailingWhitespace = !textStyle->WhiteSpaceIsSignificant() &&
textStyle->WhiteSpaceCanWrap();
PRBool canTrimTrailingWhitespace = !textStyle->WhiteSpaceIsSignificant();
PRUint32 transformedCharsFit =
mTextRun->BreakAndMeasureText(transformedOffset, transformedLength,
(GetStateBits() & TEXT_START_OF_LINE) != 0,
availWidth,
&provider, suppressInitialBreak,
&provider, !lineLayout.LineIsBreakable(),
canTrimTrailingWhitespace ? &trimmedWidth : nsnull,
&textMetrics, needTightBoundingBox, ctx,
&usedHyphenation, &transformedLastBreak);
@ -5293,26 +5282,38 @@ nsTextFrame::Reflow(nsPresContext* aPresContext,
AddStateBits(TEXT_HYPHEN_BREAK);
}
// If everything fits including trimmed whitespace, then we should add the
// trimmed whitespace to our metrics now because it probably won't be trimmed
// and we need to position subsequent frames correctly...
if (forceBreak < 0 && textMetrics.mAdvanceWidth + trimmedWidth <= availWidth) {
textMetrics.mAdvanceWidth += trimmedWidth;
if (mTextRun->IsRightToLeft()) {
// Space comes before text, so the bounding box is moved to the
// right by trimmdWidth
textMetrics.mBoundingBox.MoveBy(gfxPoint(trimmedWidth, 0));
gfxFloat trimmableWidth = 0;
if (canTrimTrailingWhitespace) {
// Optimization: if we trimmed trailing whitespace, and we can be sure
// 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;
trimmableWidth = trimmedWidth;
if (mTextRun->IsRightToLeft()) {
// Space comes before text, so the bounding box is moved to the
// right by trimmdWidth
textMetrics.mBoundingBox.MoveBy(gfxPoint(trimmedWidth, 0));
}
// Since everything fit and no break was forced,
// record the last break opportunity
if (lastBreak >= 0) {
lineLayout.NotifyOptionalBreakPosition(mContent, lastBreak,
textMetrics.mAdvanceWidth <= aReflowState.availableWidth);
}
}
if (lastBreak >= 0) {
lineLayout.NotifyOptionalBreakPosition(mContent, lastBreak,
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();
@ -5347,25 +5348,19 @@ nsTextFrame::Reflow(nsPresContext* aPresContext,
// Clean up, update state
/////////////////////////////////////////////////////////////////////
if (charsFit > 0) {
lineLayout.SetHasTrailingTextFrame(PR_TRUE);
if (charsFit == length) {
if (textStyle->WhiteSpaceCanWrap() &&
IsTrimmableSpace(frag, offset + charsFit - 1)) {
// Record a potential break after final breakable whitespace
lineLayout.NotifyOptionalBreakPosition(mContent, offset + length,
textMetrics.mAdvanceWidth <= aReflowState.availableWidth);
} else if (HasSoftHyphenBefore(frag, mTextRun, offset, end)) {
// Record a potential break after final soft hyphen
lineLayout.NotifyOptionalBreakPosition(mContent, offset + length,
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 all our characters are discarded or collapsed, then trimmable width
// from the last textframe should be preserved. Otherwise the trimmable width
// from this textframe overrides. (Currently in CSS trimmable width can be
// at most one space so there's no way for trimmable width from a previous
// frame to accumulate with trimmable width from this frame.)
if (transformedCharsFit > 0) {
lineLayout.SetTrimmableWidth(NSToCoordFloor(trimmableWidth));
}
if (charsFit > 0 && charsFit == length &&
HasSoftHyphenBefore(frag, mTextRun, offset, end)) {
// Record a potential break after final soft hyphen
lineLayout.NotifyOptionalBreakPosition(mContent, offset + length,
textMetrics.mAdvanceWidth + provider.GetHyphenWidth() <= availWidth);
}
if (completedFirstLetter) {
lineLayout.SetFirstLetterStyleOK(PR_FALSE);

View File

@ -2,3 +2,6 @@
== soft-hyphens-1a.html soft-hyphens-1-ref.html
== soft-hyphens-1b.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

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>