Bug 230555. Support white-space:pre-line. r+sr=dbaron

This commit is contained in:
Robert O'Callahan 2008-08-12 20:31:56 +12:00
Родитель 50100fccb1
Коммит b2c0de7471
11 изменённых файлов: 232 добавлений и 127 удалений

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

@ -601,6 +601,7 @@
#define NS_STYLE_WHITESPACE_PRE 1
#define NS_STYLE_WHITESPACE_NOWRAP 2
#define NS_STYLE_WHITESPACE_PRE_WRAP 3
#define NS_STYLE_WHITESPACE_PRE_LINE 4
// See nsStyleText
#define NS_STYLE_WORDWRAP_NORMAL 0

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

@ -4766,7 +4766,7 @@ ShouldPutNextSiblingOnNewLine(nsIFrame* aLastFrame)
return PR_TRUE;
if (type == nsGkAtoms::textFrame)
return aLastFrame->HasTerminalNewline() &&
aLastFrame->GetStyleText()->WhiteSpaceIsSignificant();
aLastFrame->GetStyleText()->NewlineIsSignificant();
if (type == nsGkAtoms::placeholderFrame)
return IsContinuationPlaceholder(aLastFrame);
return PR_FALSE;

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

@ -4634,7 +4634,7 @@ FindBlockFrameOrBR(nsIFrame* aFrame, nsDirection aDirection)
// If this is a preformatted text frame, see if it ends with a newline
if (aFrame->HasTerminalNewline() &&
aFrame->GetStyleContext()->GetStyleText()->WhiteSpaceIsSignificant()) {
aFrame->GetStyleContext()->GetStyleText()->NewlineIsSignificant()) {
PRInt32 startOffset, endOffset;
aFrame->GetOffsets(startOffset, endOffset);
result.mContent = aFrame->GetContent();

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

@ -143,13 +143,17 @@
// This bit is set on frames that trimmed trailing whitespace characters when
// calculating their width during reflow.
#define TEXT_TRIMMED_TRAILING_WHITESPACE 0x01000000
// This bit is set on frames that have justification enabled. We record
// this in a state bit because we don't always have the containing block
// easily available to check text-align on.
#define TEXT_JUSTIFICATION_ENABLED 0x02000000
// Set this bit if the textframe has overflow area for IME/spellcheck underline.
#define TEXT_SELECTION_UNDERLINE_OVERFLOWED 0x04000000
#define TEXT_REFLOW_FLAGS \
(TEXT_FIRST_LETTER|TEXT_START_OF_LINE|TEXT_END_OF_LINE|TEXT_HYPHEN_BREAK| \
TEXT_TRIMMED_TRAILING_WHITESPACE|TEXT_HAS_NONCOLLAPSED_CHARACTERS| \
TEXT_SELECTION_UNDERLINE_OVERFLOWED)
TEXT_TRIMMED_TRAILING_WHITESPACE|TEXT_JUSTIFICATION_ENABLED| \
TEXT_HAS_NONCOLLAPSED_CHARACTERS|TEXT_SELECTION_UNDERLINE_OVERFLOWED)
// Cache bits for IsEmpty().
// Set this bit if the textframe is known to be only collapsible whitespace.
@ -158,7 +162,6 @@
#define TEXT_ISNOT_ONLY_WHITESPACE 0x10000000
#define TEXT_WHITESPACE_FLAGS 0x18000000
// This bit is set while the frame is registered as a blinking frame.
#define TEXT_BLINK_ON 0x80000000
@ -474,47 +477,49 @@ static PRBool IsSpaceCombiningSequenceTail(const nsTextFragment* aFrag, PRUint32
}
// Check whether aPos is a space for CSS 'word-spacing' purposes
static PRBool IsCSSWordSpacingSpace(const nsTextFragment* aFrag, PRUint32 aPos)
static PRBool IsCSSWordSpacingSpace(const nsTextFragment* aFrag,
PRUint32 aPos)
{
NS_ASSERTION(aPos < aFrag->GetLength(), "No text for IsSpace!");
PRUnichar ch = aFrag->CharAt(aPos);
if (ch == ' ' || ch == CH_CJKSP)
return !IsSpaceCombiningSequenceTail(aFrag, aPos + 1);
return ch == '\t' || ch == '\n' || ch == '\f';
return ch == '\t' || ch == '\f' || ch == '\n';
}
// Check whether the string aChars/aLength starts with a space that's
// trimmable according to CSS 'white-space'.
// Check whether the string aChars/aLength starts with space that's
// trimmable according to CSS 'white-space:normal/nowrap'.
static PRBool IsTrimmableSpace(const PRUnichar* aChars, PRUint32 aLength)
{
NS_ASSERTION(aLength > 0, "No text for IsSpace!");
PRUnichar ch = *aChars;
if (ch == ' ')
return !nsTextFrameUtils::IsSpaceCombiningSequenceTail(aChars + 1, aLength - 1);
return ch == '\t' || ch == '\n' || ch == '\f';
return ch == '\t' || ch == '\f' || ch == '\n';
}
// Check whether the character aCh is trimmable according to CSS 'white-space'
// Check whether the character aCh is trimmable according to CSS
// 'white-space:normal/nowrap'
static PRBool IsTrimmableSpace(char aCh)
{
return aCh == ' ' || aCh == '\t' || aCh == '\n' || aCh == '\f';
return aCh == ' ' || aCh == '\t' || aCh == '\f' || aCh == '\n';
}
static PRBool IsTrimmableSpace(const nsTextFragment* aFrag, PRUint32 aPos)
static PRBool IsTrimmableSpace(const nsTextFragment* aFrag, PRUint32 aPos,
const nsStyleText* aStyleText)
{
NS_ASSERTION(aPos < aFrag->GetLength(), "No text for IsSpace!");
PRUnichar ch = aFrag->CharAt(aPos);
if (ch == ' ')
return !IsSpaceCombiningSequenceTail(aFrag, aPos + 1);
return ch == '\t' || ch == '\n' || ch == '\f';
}
static PRBool IsTrimmableSpace(const nsTextFragment* aFrag,
const nsStyleText* aText, PRUint32 aPos)
{
if (aText->WhiteSpaceIsSignificant())
return PR_FALSE;
return IsTrimmableSpace(aFrag, aPos);
switch (aFrag->CharAt(aPos)) {
case ' ': return !aStyleText->WhiteSpaceIsSignificant() &&
!IsSpaceCombiningSequenceTail(aFrag, aPos + 1);
case '\n': return !aStyleText->NewlineIsSignificant();
case '\t':
case '\f': return !aStyleText->WhiteSpaceIsSignificant();
default: return PR_FALSE;
}
}
static PRBool IsSelectionSpace(const nsTextFragment* aFrag, PRUint32 aPos)
@ -526,13 +531,15 @@ static PRBool IsSelectionSpace(const nsTextFragment* aFrag, PRUint32 aPos)
return ch == '\t' || ch == '\n' || ch == '\f';
}
// Count the amount of trimmable whitespace in a text fragment. The first
// Count the amount of trimmable whitespace (as per CSS
// 'white-space:normal/nowrap') in a text fragment. The first
// character is at offset aStartOffset; the maximum number of characters
// to check is aLength. aDirection is -1 or 1 depending on whether we should
// progress backwards or forwards.
static PRUint32
GetTrimmableWhitespaceCount(const nsTextFragment* aFrag, PRInt32 aStartOffset,
PRInt32 aLength, PRInt32 aDirection)
GetTrimmableWhitespaceCount(const nsTextFragment* aFrag,
PRInt32 aStartOffset, PRInt32 aLength,
PRInt32 aDirection)
{
PRInt32 count = 0;
if (aFrag->Is2b()) {
@ -555,6 +562,22 @@ GetTrimmableWhitespaceCount(const nsTextFragment* aFrag, PRInt32 aStartOffset,
return count;
}
static PRBool
IsAllWhitespace(const nsTextFragment* aFrag, PRBool aAllowNewline)
{
if (aFrag->Is2b())
return PR_FALSE;
PRInt32 len = aFrag->GetLength();
const char* str = aFrag->Get1b();
for (PRInt32 i = 0; i < len; ++i) {
char ch = str[i];
if (ch == ' ' || ch == '\t' || (ch == '\n' && aAllowNewline))
continue;
return PR_FALSE;
}
return PR_TRUE;
}
/**
* This class accumulates state as we scan a paragraph of text. It detects
* textrun boundaries (changes from text to non-text, hard
@ -1167,7 +1190,7 @@ BuildTextRunsScanner::ContinueTextRunAcrossFrames(nsTextFrame* aFrame1, nsTextFr
// even if it has newlines in it, so typically we won't see trailing newlines
// until after reflow has broken up the frame into one (or more) frames per
// line. That's OK though.
if (textStyle1->WhiteSpaceIsSignificant() && HasTerminalNewline(aFrame1))
if (textStyle1->NewlineIsSignificant() && HasTerminalNewline(aFrame1))
return PR_FALSE;
if (aFrame1->GetContent() == aFrame2->GetContent() &&
@ -1386,6 +1409,21 @@ GetFirstFontMetrics(gfxFontGroup* aFontGroup)
return font->GetMetrics();
}
PR_STATIC_ASSERT(NS_STYLE_WHITESPACE_NORMAL == 0);
PR_STATIC_ASSERT(NS_STYLE_WHITESPACE_PRE == 1);
PR_STATIC_ASSERT(NS_STYLE_WHITESPACE_NOWRAP == 2);
PR_STATIC_ASSERT(NS_STYLE_WHITESPACE_PRE_WRAP == 3);
PR_STATIC_ASSERT(NS_STYLE_WHITESPACE_PRE_LINE == 4);
static const nsTextFrameUtils::CompressionMode CSSWhitespaceToCompressionMode[] =
{
nsTextFrameUtils::COMPRESS_WHITESPACE_NEWLINE, // normal
nsTextFrameUtils::COMPRESS_NONE, // pre
nsTextFrameUtils::COMPRESS_WHITESPACE_NEWLINE, // nowrap
nsTextFrameUtils::COMPRESS_NONE, // pre-wrap
nsTextFrameUtils::COMPRESS_WHITESPACE // pre-line
};
gfxTextRun*
BuildTextRunsScanner::BuildTextRunForFrames(void* aTextBuffer)
{
@ -1425,6 +1463,8 @@ BuildTextRunsScanner::BuildTextRunForFrames(void* aTextBuffer)
PRUint32 nextBreakIndex = 0;
nsTextFrame* nextBreakBeforeFrame = GetNextBreakBeforeFrame(&nextBreakIndex);
PRBool enabledJustification = mLineContainer &&
mLineContainer->GetStyleText()->mTextAlign == NS_STYLE_TEXT_ALIGN_JUSTIFY;
PRUint32 i;
const nsStyleText* textStyle = nsnull;
@ -1442,8 +1482,9 @@ BuildTextRunsScanner::BuildTextRunForFrames(void* aTextBuffer)
}
textFlags |= GetSpacingFlags(textStyle->mLetterSpacing);
textFlags |= GetSpacingFlags(textStyle->mWordSpacing);
PRBool compressWhitespace = !textStyle->WhiteSpaceIsSignificant();
if (NS_STYLE_TEXT_ALIGN_JUSTIFY == textStyle->mTextAlign && compressWhitespace) {
nsTextFrameUtils::CompressionMode compression =
CSSWhitespaceToCompressionMode[textStyle->mWhiteSpace];
if (enabledJustification && !textStyle->WhiteSpaceIsSignificant()) {
textFlags |= gfxTextRunFactory::TEXT_ENABLE_SPACING;
}
fontStyle = f->GetStyleFont();
@ -1476,7 +1517,7 @@ BuildTextRunsScanner::BuildTextRunForFrames(void* aTextBuffer)
PRUnichar* bufStart = static_cast<PRUnichar*>(aTextBuffer);
PRUnichar* bufEnd = nsTextFrameUtils::TransformText(
frag->Get2b() + contentStart, contentLength, bufStart,
compressWhitespace, &mTrimNextRunLeadingWhitespace, &builder, &analysisFlags);
compression, &mTrimNextRunLeadingWhitespace, &builder, &analysisFlags);
aTextBuffer = bufEnd;
} else {
if (mDoubleByteText) {
@ -1490,7 +1531,7 @@ BuildTextRunsScanner::BuildTextRunForFrames(void* aTextBuffer)
PRUint8* bufStart = tempBuf.Elements();
PRUint8* end = nsTextFrameUtils::TransformText(
reinterpret_cast<const PRUint8*>(frag->Get1b()) + contentStart, contentLength,
bufStart, compressWhitespace, &mTrimNextRunLeadingWhitespace,
bufStart, compression, &mTrimNextRunLeadingWhitespace,
&builder, &analysisFlags);
aTextBuffer = ExpandBuffer(static_cast<PRUnichar*>(aTextBuffer),
tempBuf.Elements(), end - tempBuf.Elements());
@ -1499,15 +1540,10 @@ BuildTextRunsScanner::BuildTextRunForFrames(void* aTextBuffer)
PRUint8* end = nsTextFrameUtils::TransformText(
reinterpret_cast<const PRUint8*>(frag->Get1b()) + contentStart, contentLength,
bufStart,
compressWhitespace, &mTrimNextRunLeadingWhitespace, &builder, &analysisFlags);
compression, &mTrimNextRunLeadingWhitespace, &builder, &analysisFlags);
aTextBuffer = end;
}
}
// In CSS 2.1, we do not compress a space that is preceded by a non-compressible
// space.
if (!compressWhitespace) {
mTrimNextRunLeadingWhitespace = PR_FALSE;
}
textFlags |= analysisFlags;
currentTransformedTextOffset =
@ -1689,7 +1725,8 @@ BuildTextRunsScanner::BuildTextRunForFrames(void* aTextBuffer)
}
static PRBool
HasCompressedLeadingWhitespace(nsTextFrame* aFrame, PRInt32 aContentEndOffset,
HasCompressedLeadingWhitespace(nsTextFrame* aFrame, const nsStyleText* aStyleText,
PRInt32 aContentEndOffset,
const gfxSkipCharsIterator& aIterator)
{
if (!aIterator.IsOriginalCharSkipped())
@ -1699,7 +1736,7 @@ HasCompressedLeadingWhitespace(nsTextFrame* aFrame, PRInt32 aContentEndOffset,
PRInt32 frameContentOffset = aFrame->GetContentOffset();
const nsTextFragment* frag = aFrame->GetContent()->GetText();
while (frameContentOffset < aContentEndOffset && iter.IsOriginalCharSkipped()) {
if (IsTrimmableSpace(frag, frameContentOffset))
if (IsTrimmableSpace(frag, frameContentOffset, aStyleText))
return PR_TRUE;
++frameContentOffset;
iter.AdvanceOriginal(1);
@ -1753,7 +1790,8 @@ BuildTextRunsScanner::SetupBreakSinksForTextRun(gfxTextRun* aTextRun,
flags |= nsLineBreaker::BREAK_NEED_CAPITALIZATION;
}
if (HasCompressedLeadingWhitespace(startFrame, mappedFlow->GetContentEnd(), iter)) {
if (HasCompressedLeadingWhitespace(startFrame, textStyle,
mappedFlow->GetContentEnd(), iter)) {
mLineBreaker.AppendInvisibleWhitespace(flags);
}
@ -1880,14 +1918,14 @@ nsTextFrame::EnsureTextRun(gfxContext* aReferenceContext, nsIFrame* aLineContain
}
static PRUint32
GetEndOfTrimmedText(const nsTextFragment* aFrag,
GetEndOfTrimmedText(const nsTextFragment* aFrag, const nsStyleText* aStyleText,
PRUint32 aStart, PRUint32 aEnd,
gfxSkipCharsIterator* aIterator)
{
aIterator->SetSkippedOffset(aEnd);
while (aIterator->GetSkippedOffset() > aStart) {
aIterator->AdvanceSkipped(-1);
if (!IsTrimmableSpace(aFrag, aIterator->GetOriginalOffset()))
if (!IsTrimmableSpace(aFrag, aIterator->GetOriginalOffset(), aStyleText))
return aIterator->GetSkippedOffset() + 1;
}
return aStart;
@ -1898,23 +1936,35 @@ nsTextFrame::GetTrimmedOffsets(const nsTextFragment* aFrag,
PRBool aTrimAfter)
{
NS_ASSERTION(mTextRun, "Need textrun here");
// This should not be used during reflow. We need our TEXT_REFLOW_FLAGS
// to be set correctly.
NS_ASSERTION(!(GetStateBits() & NS_FRAME_FIRST_REFLOW),
"Can only call this on frames that have been reflowed");
NS_ASSERTION(!(GetStateBits() & NS_FRAME_IN_REFLOW),
"Can only call this on frames that are not being reflowed");
TrimmedOffsets offsets = { GetContentOffset(), GetContentLength() };
const nsStyleText* textStyle = GetStyleText();
// Note that pre-line newlines should still allow us to trim spaces
// for display
if (textStyle->WhiteSpaceIsSignificant())
return offsets;
if (GetStateBits() & TEXT_START_OF_LINE) {
PRInt32 whitespaceCount =
GetTrimmableWhitespaceCount(aFrag, offsets.mStart, offsets.mLength, 1);
GetTrimmableWhitespaceCount(aFrag,
offsets.mStart, offsets.mLength, 1);
offsets.mStart += whitespaceCount;
offsets.mLength -= whitespaceCount;
}
if (aTrimAfter && (GetStateBits() & TEXT_END_OF_LINE)) {
// This treats a trailing 'pre-line' newline as trimmable. That's fine,
// it's actually what we want since we want whitespace before it to
// be trimmed.
PRInt32 whitespaceCount =
GetTrimmableWhitespaceCount(aFrag, offsets.GetEnd() - 1,
offsets.mLength, -1);
GetTrimmableWhitespaceCount(aFrag,
offsets.GetEnd() - 1, offsets.mLength, -1);
offsets.mLength -= whitespaceCount;
}
return offsets;
@ -2507,8 +2557,7 @@ PropertyProvider::SetupJustificationSpacing()
{
NS_PRECONDITION(mLength != PR_INT32_MAX, "Can't call this with undefined length");
if (NS_STYLE_TEXT_ALIGN_JUSTIFY != mTextStyle->mTextAlign ||
mTextStyle->WhiteSpaceIsSignificant())
if (!(mFrame->GetStateBits() & TEXT_JUSTIFICATION_ENABLED))
return;
gfxSkipCharsIterator start(mStart), end(mStart);
@ -4810,7 +4859,7 @@ IsAcceptableCaretPosition(const gfxSkipCharsIterator& aIter, gfxTextRun* aTextRu
PRUint32 index = aIter.GetSkippedOffset();
if (!aTextRun->IsClusterStart(index))
return PR_FALSE;
return !(aFrame->GetStyleText()->WhiteSpaceIsSignificant() &&
return !(aFrame->GetStyleText()->NewlineIsSignificant() &&
aTextRun->GetChar(index) == '\n');
}
@ -5150,13 +5199,13 @@ FindFirstLetterRange(const nsTextFragment* aFrag,
static PRUint32
FindStartAfterSkippingWhitespace(PropertyProvider* aProvider,
nsIFrame::InlineIntrinsicWidthData* aData,
PRBool aCollapseWhitespace,
const nsStyleText* aTextStyle,
gfxSkipCharsIterator* aIterator,
PRUint32 aFlowEndInTextRun)
{
if (aData->skipWhitespace && aCollapseWhitespace) {
if (aData->skipWhitespace) {
while (aIterator->GetSkippedOffset() < aFlowEndInTextRun &&
IsTrimmableSpace(aProvider->GetFragment(), aIterator->GetOriginalOffset())) {
IsTrimmableSpace(aProvider->GetFragment(), aIterator->GetOriginalOffset(), aTextStyle)) {
aIterator->AdvanceOriginal(1);
}
}
@ -5185,14 +5234,15 @@ nsTextFrame::AddInlineMinWidthForFlow(nsIRenderingContext *aRenderingContext,
// Pass null for the line container. This will disable tab spacing, but that's
// OK since we can't really handle tabs for intrinsic sizing anyway.
const nsStyleText* textStyle = GetStyleText();
const nsTextFragment* frag = mContent->GetText();
PropertyProvider provider(mTextRun, GetStyleText(), frag, this,
PropertyProvider provider(mTextRun, textStyle, frag, this,
iter, PR_INT32_MAX, nsnull, 0);
PRBool collapseWhitespace = !provider.GetStyleText()->WhiteSpaceIsSignificant();
PRBool collapseWhitespace = !textStyle->WhiteSpaceIsSignificant();
PRBool preformatNewlines = textStyle->NewlineIsSignificant();
PRUint32 start =
FindStartAfterSkippingWhitespace(&provider, aData, collapseWhitespace,
&iter, flowEndInTextRun);
FindStartAfterSkippingWhitespace(&provider, aData, textStyle, &iter, flowEndInTextRun);
if (start >= flowEndInTextRun)
return;
@ -5203,7 +5253,7 @@ nsTextFrame::AddInlineMinWidthForFlow(nsIRenderingContext *aRenderingContext,
// XXXldb Shouldn't we be including the newline as part of the
// segment that it ends rather than part of the segment that it
// starts?
preformattedNewline = !collapseWhitespace && mTextRun->GetChar(i) == '\n';
preformattedNewline = preformatNewlines && mTextRun->GetChar(i) == '\n';
if (!mTextRun->CanBreakLineBefore(i) && !preformattedNewline) {
// we can't break here (and it's not the end of the flow)
continue;
@ -5217,7 +5267,7 @@ nsTextFrame::AddInlineMinWidthForFlow(nsIRenderingContext *aRenderingContext,
aData->atStartOfLine = PR_FALSE;
if (collapseWhitespace) {
PRUint32 trimStart = GetEndOfTrimmedText(frag, wordStart, i, &iter);
PRUint32 trimStart = GetEndOfTrimmedText(frag, textStyle, wordStart, i, &iter);
if (trimStart == start) {
// This is *all* trimmable whitespace, so whatever trailingWhitespace
// we saw previously is still trailing...
@ -5246,8 +5296,9 @@ nsTextFrame::AddInlineMinWidthForFlow(nsIRenderingContext *aRenderingContext,
// Check if we have collapsible whitespace at the end
aData->skipWhitespace =
IsTrimmableSpace(provider.GetFragment(), provider.GetStyleText(),
iter.ConvertSkippedToOriginal(flowEndInTextRun - 1));
IsTrimmableSpace(provider.GetFragment(),
iter.ConvertSkippedToOriginal(flowEndInTextRun - 1),
textStyle);
}
// XXX Need to do something here to avoid incremental reflow bugs due to
@ -5287,56 +5338,69 @@ nsTextFrame::AddInlinePrefWidthForFlow(nsIRenderingContext *aRenderingContext,
// Pass null for the line container. This will disable tab spacing, but that's
// OK since we can't really handle tabs for intrinsic sizing anyway.
PropertyProvider provider(mTextRun, GetStyleText(), mContent->GetText(), this,
const nsStyleText* textStyle = GetStyleText();
const nsTextFragment* frag = mContent->GetText();
PropertyProvider provider(mTextRun, textStyle, frag, this,
iter, PR_INT32_MAX, nsnull, 0);
PRBool collapseWhitespace = !provider.GetStyleText()->WhiteSpaceIsSignificant();
PRBool collapseWhitespace = !textStyle->WhiteSpaceIsSignificant();
PRBool preformatNewlines = textStyle->NewlineIsSignificant();
PRUint32 start =
FindStartAfterSkippingWhitespace(&provider, aData, collapseWhitespace,
&iter, flowEndInTextRun);
FindStartAfterSkippingWhitespace(&provider, aData, textStyle, &iter, flowEndInTextRun);
if (start >= flowEndInTextRun)
return;
if (collapseWhitespace) {
// \n line breaks are not honoured, so everything would like to go
// onto one line, so just measure it
nscoord width =
NSToCoordCeil(mTextRun->GetAdvanceWidth(start, flowEndInTextRun - start, &provider));
aData->currentLine = NSCoordSaturatingAdd(aData->currentLine, width);
PRUint32 trimStart = GetEndOfTrimmedText(provider.GetFragment(), start,
flowEndInTextRun, &iter);
if (trimStart == start) {
// This is *all* trimmable whitespace, so whatever trailingWhitespace
// we saw previously is still trailing...
aData->trailingWhitespace += width;
} else {
// Some non-whitespace so the old trailingWhitespace is no longer trailing
aData->trailingWhitespace =
NSToCoordCeil(mTextRun->GetAdvanceWidth(trimStart, flowEndInTextRun - trimStart, &provider));
}
} else {
// We respect line breaks, so measure off each line (or part of line).
aData->trailingWhitespace = 0;
PRUint32 i;
PRUint32 startRun = start;
for (i = start; i <= flowEndInTextRun; ++i) {
if (i < flowEndInTextRun && mTextRun->GetChar(i) != '\n')
// XXX Should we consider hyphenation here?
// If newlines aren't hard breaks, there are no hard breaks so just
// make i skip to the end
for (PRUint32 i = preformatNewlines ? start : flowEndInTextRun, lineStart = start;
i <= flowEndInTextRun; ++i) {
PRBool preformattedNewline = PR_FALSE;
if (i < flowEndInTextRun) {
// XXXldb Shouldn't we be including the newline as part of the
// segment that it ends rather than part of the segment that it
// starts?
NS_ASSERTION(preformatNewlines, "We can't be here unless newlines are hard breaks");
preformattedNewline = mTextRun->GetChar(i) == '\n';
if (!preformattedNewline) {
// we needn't break here (and it's not the end of the flow)
continue;
aData->currentLine +=
NSToCoordCeil(mTextRun->GetAdvanceWidth(startRun, i - startRun, &provider));
if (i < flowEndInTextRun) {
aData->ForceBreak(aRenderingContext);
startRun = i;
}
}
if (i > lineStart) {
nscoord width =
NSToCoordCeil(mTextRun->GetAdvanceWidth(lineStart, i - lineStart, &provider));
aData->currentLine = NSCoordSaturatingAdd(aData->currentLine, width);
if (collapseWhitespace) {
PRUint32 trimStart = GetEndOfTrimmedText(frag, textStyle, lineStart, i, &iter);
if (trimStart == start) {
// This is *all* trimmable whitespace, so whatever trailingWhitespace
// we saw previously is still trailing...
aData->trailingWhitespace += width;
} else {
// Some non-whitespace so the old trailingWhitespace is no longer trailing
aData->trailingWhitespace =
NSToCoordCeil(mTextRun->GetAdvanceWidth(trimStart, i - trimStart, &provider));
}
} else {
aData->trailingWhitespace = 0;
}
}
if (preformattedNewline) {
aData->ForceBreak(aRenderingContext);
lineStart = i;
}
}
// Check if we have collapsible whitespace at the end
aData->skipWhitespace =
IsTrimmableSpace(provider.GetFragment(), provider.GetStyleText(),
iter.ConvertSkippedToOriginal(flowEndInTextRun - 1));
IsTrimmableSpace(provider.GetFragment(),
iter.ConvertSkippedToOriginal(flowEndInTextRun - 1),
textStyle);
}
// XXX Need to do something here to avoid incremental reflow bugs due to
@ -5575,18 +5639,20 @@ nsTextFrame::Reflow(nsPresContext* aPresContext,
// Restrict preformatted text to the nearest newline
PRInt32 newLineOffset = -1; // this will be -1 or a content offset
if (textStyle->WhiteSpaceIsSignificant()) {
if (textStyle->NewlineIsSignificant()) {
newLineOffset = FindChar(frag, offset, length, '\n');
if (newLineOffset >= 0) {
length = newLineOffset + 1 - offset;
}
} else {
if (atStartOfLine) {
// Skip leading whitespace
PRInt32 whitespaceCount = GetTrimmableWhitespaceCount(frag, offset, length, 1);
offset += whitespaceCount;
length -= whitespaceCount;
}
}
if (atStartOfLine && !textStyle->WhiteSpaceIsSignificant()) {
// Skip leading whitespace. Make sure we don't skip a 'pre-line'
// newline if there is one.
PRInt32 skipLength = newLineOffset >= 0 ? length - 1 : length;
PRInt32 whitespaceCount =
GetTrimmableWhitespaceCount(frag, offset, skipLength, 1);
offset += whitespaceCount;
length -= whitespaceCount;
}
PRBool completedFirstLetter = PR_FALSE;
@ -5605,7 +5671,20 @@ nsTextFrame::Reflow(nsPresContext* aPresContext,
EnsureTextRun(ctx, lineContainer, lineLayout.GetLine(), &flowEndInTextRun);
if (mTextRun) {
completedFirstLetter = FindFirstLetterRange(frag, mTextRun, offset, iter, &length);
PRInt32 firstLetterLength = length;
completedFirstLetter =
FindFirstLetterRange(frag, mTextRun, offset, iter, &firstLetterLength);
if (newLineOffset >= 0) {
// Don't allow a preformatted newline to be part of a first-letter.
firstLetterLength = PR_MIN(firstLetterLength, length - 1);
if (length == 1) {
// There is no text to be consumed by the first-letter before the
// preformatted newline. Note that the first letter is therefore
// complete (FindFirstLetterRange will have returned false).
completedFirstLetter = PR_TRUE;
}
}
length = firstLetterLength;
if (length) {
AddStateBits(TEXT_FIRST_LETTER);
}
@ -5901,9 +5980,9 @@ nsTextFrame::Reflow(nsPresContext* aPresContext,
}
// Compute space and letter counts for justification, if required
if (NS_STYLE_TEXT_ALIGN_JUSTIFY == textStyle->mTextAlign &&
!textStyle->WhiteSpaceIsSignificant()) {
// This will include a space for trailing whitespace, if any is present.
if (!textStyle->WhiteSpaceIsSignificant() &&
lineContainer->GetStyleText()->mTextAlign == NS_STYLE_TEXT_ALIGN_JUSTIFY) {
AddStateBits(TEXT_JUSTIFICATION_ENABLED); // This will include a space for trailing whitespace, if any is present.
// This is corrected for in nsLineLayout::TrimWhiteSpaceIn.
PRInt32 numJustifiableCharacters =
provider.ComputeJustifiableCharacters(offset, charsFit);
@ -5995,7 +6074,7 @@ nsTextFrame::TrimTrailingWhiteSpace(nsIRenderingContext* aRC)
}
if (!result.mLastCharIsJustifiable &&
NS_STYLE_TEXT_ALIGN_JUSTIFY == textStyle->mTextAlign) {
(GetStateBits() & TEXT_JUSTIFICATION_ENABLED)) {
// Check if any character in the last cluster is justifiable
PropertyProvider provider(mTextRun, textStyle, frag, this, start, contentLength,
nsnull, 0);
@ -6074,8 +6153,8 @@ nsTextFrame::RecomputeOverflowRect()
static PRUnichar TransformChar(const nsStyleText* aStyle, gfxTextRun* aTextRun,
PRUint32 aSkippedOffset, PRUnichar aChar)
{
if (aChar == '\n' || aChar == '\r') {
return aStyle->WhiteSpaceIsSignificant() ? aChar : ' ';
if (aChar == '\n') {
return aStyle->NewlineIsSignificant() ? aChar : ' ';
}
switch (aStyle->mTextTransform) {
case NS_STYLE_TEXT_TRANSFORM_LOWERCASE:
@ -6215,7 +6294,9 @@ nsTextFrame::IsEmpty()
"Invalid state");
// XXXldb Should this check compatibility mode as well???
if (GetStyleText()->WhiteSpaceIsSignificant()) {
const nsStyleText* textStyle = GetStyleText();
if (textStyle->WhiteSpaceIsSignificant()) {
// XXX shouldn't we return true if the length is zero?
return PR_FALSE;
}
@ -6227,7 +6308,8 @@ nsTextFrame::IsEmpty()
return PR_TRUE;
}
PRBool isEmpty = mContent->TextIsOnlyWhitespace();
PRBool isEmpty = IsAllWhitespace(mContent->GetText(),
textStyle->mWhiteSpace != NS_STYLE_WHITESPACE_PRE_LINE);
mState |= (isEmpty ? TEXT_IS_ONLY_WHITESPACE : TEXT_ISNOT_ONLY_WHITESPACE);
return isEmpty;
}

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

@ -79,7 +79,7 @@ static PRBool IsDiscardable(PRUint8 ch, PRUint32* aFlags)
PRUnichar*
nsTextFrameUtils::TransformText(const PRUnichar* aText, PRUint32 aLength,
PRUnichar* aOutput,
PRBool aCompressWhitespace,
CompressionMode aCompression,
PRPackedBool* aIncomingWhitespace,
gfxSkipCharsBuilder* aSkipChars,
PRUint32* aAnalysisFlags)
@ -87,7 +87,7 @@ nsTextFrameUtils::TransformText(const PRUnichar* aText, PRUint32 aLength,
PRUint32 flags = 0;
PRUnichar* outputStart = aOutput;
if (!aCompressWhitespace) {
if (aCompression == COMPRESS_NONE) {
// Skip discardables.
PRUint32 i;
for (i = 0; i < aLength; ++i) {
@ -113,7 +113,7 @@ nsTextFrameUtils::TransformText(const PRUnichar* aText, PRUint32 aLength,
(i + 1 >= aLength ||
!IsSpaceCombiningSequenceTail(aText, aLength - (i + 1)))) {
nowInWhitespace = PR_TRUE;
} else if (ch == '\n') {
} else if (ch == '\n' && aCompression == COMPRESS_WHITESPACE_NEWLINE) {
if (i > 0 && IS_CJ_CHAR(aText[-1]) &&
i + 1 < aLength && IS_CJ_CHAR(aText[1])) {
// Discard newlines between CJK chars.
@ -160,7 +160,7 @@ nsTextFrameUtils::TransformText(const PRUnichar* aText, PRUint32 aLength,
PRUint8*
nsTextFrameUtils::TransformText(const PRUint8* aText, PRUint32 aLength,
PRUint8* aOutput,
PRBool aCompressWhitespace,
CompressionMode aCompression,
PRPackedBool* aIncomingWhitespace,
gfxSkipCharsBuilder* aSkipChars,
PRUint32* aAnalysisFlags)
@ -168,7 +168,7 @@ nsTextFrameUtils::TransformText(const PRUint8* aText, PRUint32 aLength,
PRUint32 flags = 0;
PRUint8* outputStart = aOutput;
if (!aCompressWhitespace) {
if (aCompression == COMPRESS_NONE) {
// Skip discardables.
PRUint32 i;
for (i = 0; i < aLength; ++i) {
@ -189,7 +189,8 @@ nsTextFrameUtils::TransformText(const PRUint8* aText, PRUint32 aLength,
PRUint32 i;
for (i = 0; i < aLength; ++i) {
PRUint8 ch = *aText++;
PRBool nowInWhitespace = ch == ' ' || ch == '\t' || ch == '\n' || ch == '\f';
PRBool nowInWhitespace = ch == ' ' || ch == '\t' ||
(ch == '\n' && aCompression == COMPRESS_WHITESPACE_NEWLINE);
if (!nowInWhitespace) {
if (IsDiscardable(ch, &flags)) {
aSkipChars->SkipChar();

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

@ -92,29 +92,35 @@ public:
return aLength > 0 && aChars[0] == 0x200D; // ZWJ
}
enum CompressionMode {
COMPRESS_NONE,
COMPRESS_WHITESPACE,
COMPRESS_WHITESPACE_NEWLINE
};
/**
* Create a text run from a run of Unicode text. The text may have whitespace
* compressed. A preformatted tab is sent to the text run as a single space.
* (Tab spacing must be performed by textframe later.) Certain other
* characters are discarded.
*
* @param aCompressWhitespace runs of consecutive whitespace (spaces not
* followed by a diacritical mark, tabs, and newlines) are compressed to a
* single space character.
* @param aCompressWhitespace control what is compressed to a
* single space character: no compression, compress spaces (not followed
* by combining mark) and tabs, and compress those plus newlines.
* @param aIncomingWhitespace a flag indicating whether there was whitespace
* preceding this text. We set it to indicate if there's whitespace
* preceding the end of this text.
*/
static PRUnichar* TransformText(const PRUnichar* aText, PRUint32 aLength,
PRUnichar* aOutput,
PRBool aCompressWhitespace,
CompressionMode aCompression,
PRPackedBool* aIncomingWhitespace,
gfxSkipCharsBuilder* aSkipChars,
PRUint32* aAnalysisFlags);
static PRUint8* TransformText(const PRUint8* aText, PRUint32 aLength,
PRUint8* aOutput,
PRBool aCompressWhitespace,
CompressionMode aCompression,
PRPackedBool* aIncomingWhitespace,
gfxSkipCharsBuilder* aSkipChars,
PRUint32* aAnalysisFlags);

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

@ -1,4 +1,10 @@
== justification-1.html justification-1-ref.html
== justification-2a.html justification-2-ref.html
== justification-2b.html justification-2-ref.html
== long-1.html long-ref.html
== pre-line-1.html pre-line-1-ref.html
== pre-line-2.html pre-line-2-ref.html
== pre-line-3.html pre-line-3-ref.html
random == soft-hyphens-1a.html soft-hyphens-1-ref.html # bug 406299
random == soft-hyphens-1b.html soft-hyphens-1-ref.html # bug 406299
random == soft-hyphens-1c.html soft-hyphens-1-ref.html # bug 406299

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

@ -374,6 +374,7 @@ CSS_KEY(pointer, pointer)
CSS_KEY(portrait, portrait)
CSS_KEY(pre, pre)
CSS_KEY(pre-wrap, pre_wrap)
CSS_KEY(pre-line, pre_line)
CSS_KEY(progress, progress)
CSS_KEY(progressive, progressive)
CSS_KEY(pt, pt)

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

@ -1041,6 +1041,7 @@ const PRInt32 nsCSSProps::kWhitespaceKTable[] = {
eCSSKeyword_nowrap, NS_STYLE_WHITESPACE_NOWRAP,
eCSSKeyword_pre_wrap, NS_STYLE_WHITESPACE_PRE_WRAP,
eCSSKeyword__moz_pre_wrap, NS_STYLE_WHITESPACE_PRE_WRAP,
eCSSKeyword_pre_line, NS_STYLE_WHITESPACE_PRE_LINE,
eCSSKeyword_UNKNOWN,-1
};

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

@ -820,9 +820,16 @@ struct nsStyleText {
mWhiteSpace == NS_STYLE_WHITESPACE_PRE_WRAP;
}
PRBool NewlineIsSignificant() const {
return mWhiteSpace == NS_STYLE_WHITESPACE_PRE ||
mWhiteSpace == NS_STYLE_WHITESPACE_PRE_WRAP ||
mWhiteSpace == NS_STYLE_WHITESPACE_PRE_LINE;
}
PRBool WhiteSpaceCanWrap() const {
return mWhiteSpace == NS_STYLE_WHITESPACE_NORMAL ||
mWhiteSpace == NS_STYLE_WHITESPACE_PRE_WRAP;
mWhiteSpace == NS_STYLE_WHITESPACE_PRE_WRAP ||
mWhiteSpace == NS_STYLE_WHITESPACE_PRE_LINE;
}
PRBool WordCanWrap() const {

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

@ -1633,7 +1633,7 @@ var gCSSProperties = {
inherited: true,
type: CSS_TYPE_LONGHAND,
initial_values: [ "normal" ],
other_values: [ "pre", "nowrap", "pre-wrap" ],
other_values: [ "pre", "nowrap", "pre-wrap", "pre-line" ],
invalid_values: []
},
"widows": {