From effbb5992f1bca7cb4be247dd379727c5211f671 Mon Sep 17 00:00:00 2001 From: "kipp%netscape.com" Date: Tue, 19 Oct 1999 23:01:45 +0000 Subject: [PATCH] r=ftang; rewrite to fix 16656, parts of 7455 --- layout/generic/nsTextTransformer.cpp | 1395 ++++++++++++-------- layout/generic/nsTextTransformer.h | 107 +- layout/html/base/src/nsTextTransformer.cpp | 1395 ++++++++++++-------- layout/html/base/src/nsTextTransformer.h | 107 +- 4 files changed, 1922 insertions(+), 1082 deletions(-) diff --git a/layout/generic/nsTextTransformer.cpp b/layout/generic/nsTextTransformer.cpp index 6f74a63fa57a..9205c1e8041c 100644 --- a/layout/generic/nsTextTransformer.cpp +++ b/layout/generic/nsTextTransformer.cpp @@ -16,6 +16,7 @@ * Corporation. Portions created by Netscape are Copyright (C) 1998 * Netscape Communications Corporation. All Rights Reserved. */ +#include "nsCOMPtr.h" #include "nsTextTransformer.h" #include "nsIContent.h" #include "nsIFrame.h" @@ -28,13 +29,79 @@ #include "nsIServiceManager.h" #include "nsUnicharUtilCIID.h" #include "nsICaseConversion.h" +#include "prenv.h" + +nsAutoTextBuffer::nsAutoTextBuffer() + : mBuffer(mAutoBuffer), + mBufferLen(NS_TEXT_TRANSFORMER_AUTO_WORD_BUF_SIZE) +{ +} + +nsAutoTextBuffer::~nsAutoTextBuffer() +{ + if (mBuffer && (mBuffer != mAutoBuffer)) { + delete [] mBuffer; + } +} + +nsresult +nsAutoTextBuffer::GrowBy(PRInt32 aAtLeast, PRBool aCopyToHead) +{ + PRInt32 newSize = mBufferLen * 2; + if (newSize < mBufferLen + aAtLeast) { + newSize = mBufferLen + aAtLeast + 100; + } + return GrowTo(newSize, aCopyToHead); +} + +nsresult +nsAutoTextBuffer::GrowTo(PRInt32 aNewSize, PRBool aCopyToHead) +{ + if (aNewSize > mBufferLen) { + PRUnichar* newBuffer = new PRUnichar[aNewSize]; + if (!newBuffer) { + return NS_ERROR_OUT_OF_MEMORY; + } + nsCRT::memcpy(&newBuffer[aCopyToHead ? 0 : mBufferLen], + mBuffer, sizeof(PRUnichar) * mBufferLen); + if (mBuffer != mAutoBuffer) { + delete [] mBuffer; + } + mBuffer = newBuffer; + mBufferLen = aNewSize; + } + return NS_OK; +} + +//---------------------------------------------------------------------- static NS_DEFINE_IID(kUnicharUtilCID, NS_UNICHARUTIL_CID); static NS_DEFINE_IID(kICaseConversionIID, NS_ICASECONVERSION_IID); -static nsICaseConversion* gCaseConv = nsnull; -static nsrefcnt gCaseConvRefCnt = 0; -//#define DEBUG_GETPREVWORD +static nsICaseConversion* gCaseConv = nsnull; + +nsresult +nsTextTransformer::Initialize() +{ + nsresult res = NS_OK; + if (!gCaseConv) { + res = nsServiceManager::GetService(kUnicharUtilCID, kICaseConversionIID, + (nsISupports**)&gCaseConv); + NS_ASSERTION( NS_SUCCEEDED(res), "cannot get UnicharUtil"); + NS_ASSERTION( gCaseConv != NULL, "cannot get UnicharUtil"); + } + return res; +} + +void +nsTextTransformer::Shutdown() +{ + if (gCaseConv) { + nsServiceManager::ReleaseService(kUnicharUtilCID, gCaseConv); + gCaseConv = nsnull; + } +} + // XXX I'm sure there are other special characters #define CH_NBSP 160 @@ -44,32 +111,28 @@ MOZ_DECL_CTOR_COUNTER(nsTextTransformer); nsTextTransformer::nsTextTransformer(nsILineBreaker* aLineBreaker, nsIWordBreaker* aWordBreaker) - : mBuffer(mAutoWordBuffer), - mBufferLength(NS_TEXT_TRANSFORMER_AUTO_WORD_BUF_SIZE), - mHasMultibyte(PR_FALSE), + : mHasMultibyte(PR_FALSE), + mFrag(nsnull), + mOffset(0), + mTextTransform(NS_STYLE_TEXT_TRANSFORM_NONE), + mMode(eNormal), mLineBreaker(aLineBreaker), mWordBreaker(aWordBreaker) { MOZ_COUNT_CTOR(nsTextTransformer); - if (gCaseConvRefCnt++ == 0) { - nsresult res; - res = nsServiceManager::GetService(kUnicharUtilCID, kICaseConversionIID, - (nsISupports**)&gCaseConv); - NS_ASSERTION( NS_SUCCEEDED(res), "cannot get UnicharUtil"); - NS_ASSERTION( gCaseConv != NULL, "cannot get UnicharUtil"); + +#ifdef DEBUG + static PRBool firstTime = PR_TRUE; + if (firstTime) { + firstTime = PR_FALSE; + SelfTest(aLineBreaker, aWordBreaker); } +#endif } nsTextTransformer::~nsTextTransformer() { MOZ_COUNT_DTOR(nsTextTransformer); - if (mBuffer != mAutoWordBuffer) { - delete [] mBuffer; - } - if (--gCaseConvRefCnt == 0) { - nsServiceManager::ReleaseService(kUnicharUtilCID, gCaseConv); - gCaseConv = nsnull; - } } nsresult @@ -77,549 +140,847 @@ nsTextTransformer::Init(nsIFrame* aFrame, nsIContent* aContent, PRInt32 aStartingOffset) { - // Get the frames text content - nsITextContent* tc; - if (NS_OK != aContent->QueryInterface(kITextContentIID, (void**) &tc)) { - return NS_OK; + // Get the contents text content + nsresult rv; + nsCOMPtr tc = do_QueryInterface(aContent, &rv); + if (tc.get()) { + tc->GetText(&mFrag); + + // Sanitize aStartingOffset + if (NS_WARN_IF_FALSE(aStartingOffset >= 0, "bad starting offset")) { + aStartingOffset = 0; + } + else if (NS_WARN_IF_FALSE(aStartingOffset <= mFrag->GetLength(), + "bad starting offset")) { + aStartingOffset = mFrag->GetLength(); + } + mOffset = aStartingOffset; + + // Get the frames text style information + const nsStyleText* styleText; + aFrame->GetStyleData(eStyleStruct_Text, (const nsStyleStruct*&) styleText); + if (NS_STYLE_WHITESPACE_PRE == styleText->mWhiteSpace) { + mMode = ePreformatted; + } + else if (NS_STYLE_WHITESPACE_MOZ_PRE_WRAP == styleText->mWhiteSpace) { + mMode = ePreWrap; + } + mTextTransform = styleText->mTextTransform; } - tc->GetText(&mFrag); - NS_RELEASE(tc); - mStartingOffset = aStartingOffset; - mOffset = mStartingOffset; - - // Compute the total length of the text content. - mContentLength = mFrag->GetLength(); - - // Set current fragment offset - mCurrentFragOffset = aStartingOffset; - - // Get the frames style and choose a transform proc - const nsStyleText* styleText; - aFrame->GetStyleData(eStyleStruct_Text, (const nsStyleStruct*&) styleText); - mPreformatted = (NS_STYLE_WHITESPACE_PRE == styleText->mWhiteSpace) || - (NS_STYLE_WHITESPACE_MOZ_PRE_WRAP == styleText->mWhiteSpace); - mTextTransform = styleText->mTextTransform; - return NS_OK; + return rv; } -PRBool -nsTextTransformer::GrowBuffer(PRBool aForNextWord) +//---------------------------------------------------------------------- + +// wordlen==1, contentlen=newOffset-currentOffset, isWhitespace=t +PRInt32 +nsTextTransformer::ScanNormalWhiteSpace_F() { - PRInt32 newLen = mBufferLength * 2; - if (newLen <= 100) { - newLen = 100; - } - PRUnichar* newBuffer = new PRUnichar[newLen]; - if (nsnull == newBuffer) { - return PR_FALSE; - } - if (0 != mBufferLength) { - if(aForNextWord) - nsCRT::memcpy(newBuffer, mBuffer, sizeof(PRUnichar) * mBufferLength); - else - nsCRT::memcpy(&newBuffer[mBufferLength], mBuffer, - sizeof(PRUnichar) * mBufferLength); - if (mBuffer != mAutoWordBuffer) { - delete [] mBuffer; + const nsTextFragment* frag = mFrag; + PRInt32 fragLen = frag->GetLength(); + PRInt32 offset = mOffset; + + for (; offset < fragLen; offset++) { + PRUnichar ch = frag->CharAt(offset); + if (!XP_IS_SPACE(ch)) { + break; } } - mBuffer = newBuffer; - mBufferLength = newLen; - return PR_TRUE; + + mTransformBuf.mBuffer[0] = ' '; + return offset; } +// wordlen==*aWordLen, contentlen=newOffset-currentOffset, isWhitespace=f +PRInt32 +nsTextTransformer::ScanNormalAsciiText_F(PRInt32* aWordLen) +{ + const nsTextFragment* frag = mFrag; + PRInt32 fragLen = frag->GetLength(); + PRInt32 offset = mOffset; + PRUnichar* bp = mTransformBuf.GetBuffer(); + PRUnichar* endbp = mTransformBuf.GetBufferEnd(); + + for (; offset < fragLen; offset++) { + PRUnichar ch = frag->CharAt(offset); + if (XP_IS_SPACE(ch)) { + break; + } + if (bp == endbp) { + PRInt32 oldLength = bp - mTransformBuf.GetBuffer(); + nsresult rv = mTransformBuf.GrowBy(1000); + if (NS_FAILED(rv)) { + // If we run out of space (unlikely) then just chop the input + break; + } + bp = mTransformBuf.GetBuffer() + oldLength; + endbp = mTransformBuf.GetBufferEnd(); + } + *bp++ = ch; + } + + *aWordLen = bp - mTransformBuf.GetBuffer(); + return offset; +} + +// wordlen==*aWordLen, contentlen=newOffset-currentOffset, isWhitespace=f +PRInt32 +nsTextTransformer::ScanNormalUnicodeText_F(PRBool aForLineBreak, + PRInt32* aWordLen) +{ + const nsTextFragment* frag = mFrag; + const PRUnichar* cp0 = frag->Get2b(); + PRInt32 fragLen = frag->GetLength(); + PRInt32 offset = mOffset; + + PRUnichar firstChar = frag->CharAt(offset++); + mTransformBuf.mBuffer[0] = firstChar; + if (firstChar > MAX_UNIBYTE) mHasMultibyte = PR_TRUE; + + // Only evaluate complex breaking logic if there are more characters + // beyond the first to look at. + PRInt32 numChars = 1; + if (offset < fragLen) { + const PRUnichar* cp = cp0 + offset; + PRBool breakBetween = PR_FALSE; + if (aForLineBreak) { + mLineBreaker->BreakInBetween(mTransformBuf.GetBuffer(), 1, + cp, (fragLen-offset), &breakBetween); + } + else { + mWordBreaker->BreakInBetween(mTransformBuf.GetBuffer(), 1, + cp, (fragLen-offset), &breakBetween); + } + + if (!breakBetween) { + // Find next position + PRBool tryNextFrag; + PRUint32 next; + if (aForLineBreak) { + mLineBreaker->Next(cp0, fragLen, offset, &next, &tryNextFrag); + } + else { + mWordBreaker->Next(cp0, fragLen, offset, &next, &tryNextFrag); + } + numChars = (PRInt32) (next - (PRUint32) offset) + 1; + + // Grow buffer before copying + nsresult rv = mTransformBuf.GrowTo(numChars); + if (NS_FAILED(rv)) { + numChars = mTransformBuf.GetBufferLength(); + } + + // 1. convert nbsp into space + // 2. check mHasMultibyte flag + // 3. copy buffer + PRUnichar* bp = mTransformBuf.GetBuffer() + 1; + const PRUnichar* end = cp + numChars - 1; + while (cp < end) { + PRUnichar ch = *cp++; + if (CH_NBSP == ch) ch = ' '; + if (ch > MAX_UNIBYTE) mHasMultibyte = PR_TRUE; + *bp++ = ch; + } + } + + } + + *aWordLen = numChars; + return offset + numChars - 1; +} + +// wordlen==*aWordLen, contentlen=newOffset-currentOffset, isWhitespace=t +PRInt32 +nsTextTransformer::ScanPreWrapWhiteSpace_F(PRInt32* aWordLen) +{ + const nsTextFragment* frag = mFrag; + PRInt32 fragLen = frag->GetLength(); + PRInt32 offset = mOffset; + PRUnichar* bp = mTransformBuf.GetBuffer(); + PRUnichar* endbp = mTransformBuf.GetBufferEnd(); + + for (; offset < fragLen; offset++) { + PRUnichar ch = frag->CharAt(offset); + if (!XP_IS_SPACE(ch) || (ch == '\t') || (ch == '\n')) { + break; + } + if (bp == endbp) { + PRInt32 oldLength = bp - mTransformBuf.GetBuffer(); + nsresult rv = mTransformBuf.GrowBy(1000); + if (NS_FAILED(rv)) { + // If we run out of space (unlikely) then just chop the input + break; + } + bp = mTransformBuf.GetBuffer() + oldLength; + endbp = mTransformBuf.GetBufferEnd(); + } + *bp++ = ' '; + } + + *aWordLen = bp - mTransformBuf.GetBuffer(); + return offset; +} + +// wordlen==*aWordLen, contentlen=newOffset-currentOffset, isWhitespace=f +PRInt32 +nsTextTransformer::ScanPreData_F(PRInt32* aWordLen) +{ + const nsTextFragment* frag = mFrag; + PRInt32 fragLen = frag->GetLength(); + PRInt32 offset = mOffset; + PRUnichar* bp = mTransformBuf.GetBuffer(); + PRUnichar* endbp = mTransformBuf.GetBufferEnd(); + + for (; offset < fragLen; offset++) { + PRUnichar ch = frag->CharAt(offset); + if ((ch == '\t') || (ch == '\n')) { + break; + } + if (ch > MAX_UNIBYTE) mHasMultibyte = PR_TRUE; + if (bp == endbp) { + PRInt32 oldLength = bp - mTransformBuf.GetBuffer(); + nsresult rv = mTransformBuf.GrowBy(1000); + if (NS_FAILED(rv)) { + // If we run out of space (unlikely) then just chop the input + break; + } + bp = mTransformBuf.GetBuffer() + oldLength; + endbp = mTransformBuf.GetBufferEnd(); + } + *bp++ = ch; + } + + *aWordLen = bp - mTransformBuf.GetBuffer(); + return offset; +} + +// wordlen==*aWordLen, contentlen=newOffset-currentOffset, isWhitespace=f +PRInt32 +nsTextTransformer::ScanPreAsciiData_F(PRInt32* aWordLen) +{ + const nsTextFragment* frag = mFrag; + PRUnichar* bp = mTransformBuf.GetBuffer(); + PRUnichar* endbp = mTransformBuf.GetBufferEnd(); + const char* cp = frag->Get1b(); + const char* end = cp + frag->GetLength(); + cp += mOffset; + + while (cp < end) { + PRUnichar ch = (PRUnichar) *cp++; + if ((ch == '\t') || (ch == '\n')) { + cp--; + break; + } + if (bp == endbp) { + PRInt32 oldLength = bp - mTransformBuf.GetBuffer(); + nsresult rv = mTransformBuf.GrowBy(1000); + if (NS_FAILED(rv)) { + // If we run out of space (unlikely) then just chop the input + break; + } + bp = mTransformBuf.GetBuffer() + oldLength; + endbp = mTransformBuf.GetBufferEnd(); + } + *bp++ = ch; + } + + *aWordLen = bp - mTransformBuf.GetBuffer(); + return cp - frag->Get1b(); +} + +//---------------------------------------- + PRUnichar* nsTextTransformer::GetNextWord(PRBool aInWord, - PRInt32& aWordLenResult, - PRInt32& aContentLenResult, - PRBool& aIsWhitespaceResult, + PRInt32* aWordLenResult, + PRInt32* aContentLenResult, + PRBool* aIsWhiteSpaceResult, PRBool aForLineBreak) { - NS_PRECONDITION(mOffset <= mContentLength, "bad offset"); - NS_PRECONDITION(((nsnull != mLineBreaker)||(!aForLineBreak)), "null in line breaker"); - NS_PRECONDITION(((nsnull != mWordBreaker)||( aForLineBreak)), "null in word breaker"); - - // See if the content has been exhausted - if (mOffset == mContentLength) { - aWordLenResult = 0; - aContentLenResult = 0; - return nsnull; - } - - PRInt32 numChars; - PRInt32 fragLen; - PRUnichar* bp = mBuffer; - PRUnichar* bufEnd = mBuffer + mBufferLength; const nsTextFragment* frag = mFrag; - PRInt32 wordLen = 1; - PRInt32 contentLen = 1; + PRInt32 fragLen = frag->GetLength(); + PRInt32 offset = mOffset; + PRInt32 wordLen = 0; + PRBool isWhitespace = PR_FALSE; + PRUnichar* result = nsnull; - // Set the isWhitespace flag by examining the next character in the - // text fragment. - PRInt32 offset = mCurrentFragOffset; - PRUnichar firstChar; - if (frag->Is2b()) { - const PRUnichar* up = frag->Get2b(); - firstChar = up[offset]; - } - else { - const unsigned char* cp = (const unsigned char*) frag->Get1b(); - firstChar = PRUnichar(cp[offset]); - } - PRBool isWhitespace = XP_IS_SPACE(firstChar); - offset++; - if (isWhitespace) { - if (mPreformatted) { - if ('\t' == firstChar) { - // Leave tab alone so that caller can expand it + if (offset < fragLen) { + PRUnichar firstChar = frag->CharAt(offset); + switch (mMode) { + default: + case eNormal: + if (XP_IS_SPACE(firstChar)) { + offset = ScanNormalWhiteSpace_F(); + wordLen = 1; + isWhitespace = PR_TRUE; + } + else if (frag->Is2b()) { + offset = ScanNormalUnicodeText_F(aForLineBreak, &wordLen); + } + else { + offset = ScanNormalAsciiText_F(&wordLen); + } + break; + + case ePreformatted: + if (('\n' == firstChar) || ('\t' == firstChar)) { + mTransformBuf.mBuffer[0] = firstChar; + offset++; + wordLen = 1; + isWhitespace = PR_TRUE; + } + else if (frag->Is2b()) { + offset = ScanPreData_F(&wordLen); + } + else { + offset = ScanPreAsciiData_F(&wordLen); + } + break; + + case ePreWrap: + if (XP_IS_SPACE(firstChar)) { + if (('\n' == firstChar) || ('\t' == firstChar)) { + mTransformBuf.mBuffer[0] = firstChar; + offset++; + wordLen = 1; + } + else { + offset = ScanPreWrapWhiteSpace_F(&wordLen); + } + isWhitespace = PR_TRUE; + } + else if (frag->Is2b()) { + offset = ScanNormalUnicodeText_F(aForLineBreak, &wordLen); + } + else { + offset = ScanNormalAsciiText_F(&wordLen); + } + break; + } + result = mTransformBuf.GetBuffer(); + + if (!isWhitespace) { + switch (mTextTransform) { + case NS_STYLE_TEXT_TRANSFORM_CAPITALIZE: + gCaseConv->ToTitle(result, result, wordLen, !aInWord); + break; + case NS_STYLE_TEXT_TRANSFORM_LOWERCASE: + gCaseConv->ToLower(result, result, wordLen); + break; + case NS_STYLE_TEXT_TRANSFORM_UPPERCASE: + gCaseConv->ToUpper(result, result, wordLen); + break; } - else if ('\n' == firstChar) { - // Advance content past newline but do not allow newline to - // remain in the word. - wordLen--; + } + } + + *aWordLenResult = wordLen; + *aContentLenResult = offset - mOffset; + *aIsWhiteSpaceResult = isWhitespace; + + mOffset = offset; + + return result; +} + +//---------------------------------------------------------------------- + +// wordlen==1, contentlen=newOffset-currentOffset, isWhitespace=t +PRInt32 +nsTextTransformer::ScanNormalWhiteSpace_B() +{ + const nsTextFragment* frag = mFrag; + PRInt32 offset = mOffset; + + while (--offset >= 0) { + PRUnichar ch = frag->CharAt(offset); + if (!XP_IS_SPACE(ch)) { + break; + } + } + + mTransformBuf.mBuffer[mTransformBuf.mBufferLen - 1] = ' '; + return offset; +} + +// wordlen==*aWordLen, contentlen=newOffset-currentOffset, isWhitespace=f +PRInt32 +nsTextTransformer::ScanNormalAsciiText_B(PRInt32* aWordLen) +{ + const nsTextFragment* frag = mFrag; + PRInt32 offset = mOffset; + PRUnichar* bp = mTransformBuf.GetBufferEnd(); + PRUnichar* startbp = mTransformBuf.GetBuffer(); + + while (--offset >= 0) { + PRUnichar ch = frag->CharAt(offset); + if (XP_IS_SPACE(ch)) { + break; + } + if (bp == startbp) { + PRInt32 oldLength = mTransformBuf.mBufferLen; + nsresult rv = mTransformBuf.GrowBy(1000); + if (NS_FAILED(rv)) { + // If we run out of space (unlikely) then just chop the input + break; + } + bp = mTransformBuf.GetBufferEnd() - oldLength; + startbp = mTransformBuf.GetBuffer(); + } + *--bp = ch; + } + + *aWordLen = mTransformBuf.GetBufferEnd() - bp; + return offset; +} + +// wordlen==*aWordLen, contentlen=newOffset-currentOffset, isWhitespace=f +PRInt32 +nsTextTransformer::ScanNormalUnicodeText_B(PRBool aForLineBreak, + PRInt32* aWordLen) +{ + const nsTextFragment* frag = mFrag; + const PRUnichar* cp0 = frag->Get2b(); + PRInt32 offset = mOffset - 1; + + PRUnichar firstChar = frag->CharAt(offset); + mTransformBuf.mBuffer[mTransformBuf.mBufferLen - 1] = firstChar; + if (firstChar > MAX_UNIBYTE) mHasMultibyte = PR_TRUE; + + PRInt32 numChars = 1; + if (offset > 0) { + const PRUnichar* cp = cp0 + offset; + PRBool breakBetween = PR_FALSE; + if (aForLineBreak) { + mLineBreaker->BreakInBetween(cp0, offset + 1, + mTransformBuf.GetBufferEnd()-1, 1, + &breakBetween); + } + else { + mWordBreaker->BreakInBetween(cp0, offset + 1, + mTransformBuf.GetBufferEnd()-1, 1, + &breakBetween); + } + + if (!breakBetween) { + // Find next position + PRBool tryPrevFrag; + PRUint32 prev; + if (aForLineBreak) { + mLineBreaker->Prev(cp0, offset, offset, &prev, &tryPrevFrag); } else { - firstChar = ' '; + mWordBreaker->Prev(cp0, offset, offset, &prev, &tryPrevFrag); } - } - else { - firstChar = ' '; - } - } - else if (CH_NBSP == firstChar) { - firstChar = ' '; - } - if (firstChar > MAX_UNIBYTE) mHasMultibyte = PR_TRUE; - *bp++ = firstChar; - mCurrentFragOffset = offset; - if (isWhitespace && mPreformatted) { - goto really_done; - } + numChars = (PRInt32) ((PRUint32) offset - prev) + 1; - fragLen = frag->GetLength(); - - // Scan characters in this fragment that are the same kind as the - // isWhitespace flag indicates. - if (frag->Is2b()) { - const PRUnichar* cp0 = frag->Get2b(); - const PRUnichar* end = cp0 + fragLen; - const PRUnichar* cp = cp0 + offset; - if (isWhitespace) { - while (cp < end) { - PRUnichar ch = *cp; - if (XP_IS_SPACE(ch)) { - cp++; - continue; - } - numChars = (cp - offset) - cp0; - contentLen += numChars; - mCurrentFragOffset += numChars; - goto done; + // Grow buffer before copying + nsresult rv = mTransformBuf.GrowTo(numChars); + if (NS_FAILED(rv)) { + numChars = mTransformBuf.GetBufferLength(); } - numChars = (cp - offset) - cp0; - contentLen += numChars; - } - else { - if(wordLen > 0) { - nsresult res = NS_OK; - PRBool breakBetween = PR_FALSE; - if(aForLineBreak) - res = mLineBreaker->BreakInBetween(mBuffer, wordLen, - cp, (fragLen-offset), &breakBetween); - else - res = mWordBreaker->BreakInBetween(mBuffer, wordLen, - cp, (fragLen-offset), &breakBetween); - if ( breakBetween ) - goto done; - PRBool tryNextFrag = PR_FALSE; - PRUint32 next; - - // Find next position - - if(aForLineBreak) - res = mLineBreaker->Next(cp0, fragLen, offset, &next, &tryNextFrag); - else - res = mWordBreaker->Next(cp0, fragLen, offset, &next, &tryNextFrag); - - - numChars = (next - offset); - // check buffer size before copy - while((bp + numChars ) > bufEnd) { - PRInt32 delta = bp - mBuffer; - if(!GrowBuffer()) { - goto done; - } - bp = mBuffer + delta; - bufEnd = mBuffer + mBufferLength; - } - - wordLen += numChars; - mCurrentFragOffset += numChars; - contentLen += numChars; - end = cp + numChars; - - // 1. convert nbsp into space - // 2. check mHasMultibyte flag - // 3. copy buffer - - while(cp < end) { - PRUnichar ch = *cp++; - if (CH_NBSP == ch) ch = ' '; - if (ch > MAX_UNIBYTE) mHasMultibyte = PR_TRUE; - *bp++ = ch; - } - if(! tryNextFrag) { - // can decide break position inside this TextFrag - goto done; - } + // 1. convert nbsp into space + // 2. check mHasMultibyte flag + // 3. copy buffer + PRUnichar* bp = mTransformBuf.GetBufferEnd() - 1; + const PRUnichar* end = cp - numChars; + while (cp > end) { + PRUnichar ch = *--cp; + if (CH_NBSP == ch) ch = ' '; + if (ch > MAX_UNIBYTE) mHasMultibyte = PR_TRUE; + *--bp = ch; } } } - else { - const unsigned char* cp0 = (const unsigned char*) frag->Get1b(); - const unsigned char* end = cp0 + fragLen; - const unsigned char* cp = cp0 + offset; - if (isWhitespace) { - while (cp < end) { - PRUnichar ch = PRUnichar(*cp); - if (XP_IS_SPACE(ch)) { - cp++; - continue; - } - numChars = (cp - offset) - cp0; - contentLen += numChars; - mCurrentFragOffset += numChars; - goto done; - } - numChars = (cp - offset) - cp0; - contentLen += numChars; - } - else { - while (cp < end) { - PRUnichar ch = PRUnichar(*cp); - if (!XP_IS_SPACE(ch)) { - if (CH_NBSP == ch) ch = ' '; - if (ch > MAX_UNIBYTE) mHasMultibyte = PR_TRUE; - cp++; - // Store character in buffer; grow buffer if we have to - NS_ASSERTION(bp < bufEnd, "whoops"); - *bp++ = ch; - if (bp == bufEnd) { - PRInt32 delta = bp - mBuffer; - if (!GrowBuffer()) { - goto done; - } - bp = mBuffer + delta; - bufEnd = mBuffer + mBufferLength; - } - continue; - } - numChars = (cp - offset) - cp0; - wordLen += numChars; - contentLen += numChars; - mCurrentFragOffset += numChars; - goto done; - } - numChars = (cp - offset) - cp0; - wordLen += numChars; - contentLen += numChars; - } - } - - done:; - - if (!isWhitespace) - { - switch(mTextTransform) - { - case NS_STYLE_TEXT_TRANSFORM_CAPITALIZE: - gCaseConv->ToTitle(mBuffer, mBuffer, wordLen, !aInWord); - break; - case NS_STYLE_TEXT_TRANSFORM_LOWERCASE: - gCaseConv->ToLower(mBuffer, mBuffer, wordLen ); - break; - case NS_STYLE_TEXT_TRANSFORM_UPPERCASE: - gCaseConv->ToUpper(mBuffer, mBuffer, wordLen ); - break; - default: - break; - } - } - - really_done:; - mOffset += contentLen; - NS_ASSERTION(mOffset <= mContentLength, "whoops"); - aWordLenResult = wordLen; - aContentLenResult = contentLen; - aIsWhitespaceResult = isWhitespace; - - return mBuffer; + *aWordLen = numChars; + return offset - numChars; } +// wordlen==*aWordLen, contentlen=newOffset-currentOffset, isWhitespace=t +PRInt32 +nsTextTransformer::ScanPreWrapWhiteSpace_B(PRInt32* aWordLen) +{ + const nsTextFragment* frag = mFrag; + PRInt32 offset = mOffset; + PRUnichar* bp = mTransformBuf.GetBufferEnd(); + PRUnichar* startbp = mTransformBuf.GetBuffer(); + + while (--offset >= 0) { + PRUnichar ch = frag->CharAt(offset); + if (!XP_IS_SPACE(ch) || (ch == '\t') || (ch == '\n')) { + break; + } + if (bp == startbp) { + PRInt32 oldLength = mTransformBuf.mBufferLen; + nsresult rv = mTransformBuf.GrowBy(1000); + if (NS_FAILED(rv)) { + // If we run out of space (unlikely) then just chop the input + break; + } + bp = mTransformBuf.GetBufferEnd() - oldLength; + startbp = mTransformBuf.GetBuffer(); + } + *--bp = ' '; + } + + *aWordLen = mTransformBuf.GetBufferEnd() - bp; + return offset; +} + +// wordlen==*aWordLen, contentlen=newOffset-currentOffset, isWhitespace=f +PRInt32 +nsTextTransformer::ScanPreData_B(PRInt32* aWordLen) +{ + const nsTextFragment* frag = mFrag; + PRInt32 offset = mOffset; + PRUnichar* bp = mTransformBuf.GetBufferEnd(); + PRUnichar* startbp = mTransformBuf.GetBuffer(); + + while (--offset >= 0) { + PRUnichar ch = frag->CharAt(offset); + if ((ch == '\t') || (ch == '\n')) { + break; + } + if (ch > MAX_UNIBYTE) mHasMultibyte = PR_TRUE; + if (bp == startbp) { + PRInt32 oldLength = mTransformBuf.mBufferLen; + nsresult rv = mTransformBuf.GrowBy(1000); + if (NS_FAILED(rv)) { + // If we run out of space (unlikely) then just chop the input + offset++; + break; + } + bp = mTransformBuf.GetBufferEnd() - oldLength; + startbp = mTransformBuf.GetBuffer(); + } + *--bp = ch; + } + + *aWordLen = mTransformBuf.GetBufferEnd() - bp; + return offset; +} + +//---------------------------------------- + PRUnichar* nsTextTransformer::GetPrevWord(PRBool aInWord, - PRInt32& aWordLenResult, - PRInt32& aContentLenResult, - PRBool& aIsWhitespaceResult, + PRInt32* aWordLenResult, + PRInt32* aContentLenResult, + PRBool* aIsWhiteSpaceResult, PRBool aForLineBreak) { - NS_PRECONDITION(mOffset <= mContentLength, "bad offset"); - NS_PRECONDITION(((nsnull != mLineBreaker)||(!aForLineBreak)), "null in line breaker"); - NS_PRECONDITION(((nsnull != mWordBreaker)||( aForLineBreak)), "null in word breaker"); - - // See if the content has been exhausted - if (mOffset == 0) { - aWordLenResult = 0; - aContentLenResult = 0; - return nsnull; - } - - PRUnichar* bp = mBuffer+mBufferLength-1; - PRUnichar* bufEnd = mBuffer ; const nsTextFragment* frag = mFrag; - PRInt32 wordLen = 1; - PRInt32 contentLen = 1; + PRInt32 offset = mOffset; + PRInt32 wordLen = 0; + PRBool isWhitespace = PR_FALSE; + PRUnichar* result = nsnull; - // Set the isWhitespace flag by examining the next character in the - // text fragment. - PRInt32 offset = mCurrentFragOffset-1; - PRUnichar firstChar; - if (frag->Is2b()) { - const PRUnichar* up = frag->Get2b(); - if (offset > 0) - firstChar = up[offset]; - else - firstChar = up[0]; - } - else { - const unsigned char* cp = (const unsigned char*) frag->Get1b(); - if (offset > 0) - firstChar = PRUnichar(cp[offset]); - else - firstChar = PRUnichar(cp[0]); - } - PRBool isWhitespace = XP_IS_SPACE(firstChar); - offset--; - if (isWhitespace) { - if (mPreformatted) { - if ('\t' == firstChar) { - // Leave tab alone so that caller can expand it - } - else if ('\n' == firstChar) { - // Advance content past newline but do not allow newline to - // remain in the word. - wordLen--; - } - else { - firstChar = ' '; - } - } - else { - firstChar = ' '; - } - } - else if (CH_NBSP == firstChar) { - firstChar = ' '; - } - if(firstChar > MAX_UNIBYTE) mHasMultibyte = PR_TRUE; - *bp-- = firstChar; - mCurrentFragOffset = offset +1; - if (offset < 0) { - goto really_done; - } - if (isWhitespace && mPreformatted) { - goto really_done; - } - - PRInt32 numChars; - - // Scan characters in this fragment that are the same kind as the - // isWhitespace flag indicates. - if (frag->Is2b()) { - const PRUnichar* cp0 = frag->Get2b(); - const PRUnichar* end = cp0; - const PRUnichar* cp = cp0 + offset; - if (isWhitespace) { - while (cp > end) { - PRUnichar ch = *cp; - if (XP_IS_SPACE(ch)) { - cp--; - continue; + if (--offset >= 0) { + PRUnichar firstChar = frag->CharAt(offset); + switch (mMode) { + default: + case eNormal: + if (XP_IS_SPACE(firstChar)) { + offset = ScanNormalWhiteSpace_B(); + wordLen = 1; + isWhitespace = PR_TRUE; } - numChars = (cp0 + offset) - cp; - contentLen += numChars; - mCurrentFragOffset -= numChars; - goto done; - } - numChars = (cp0 + offset) - cp; - contentLen += numChars; - } - else { - if(wordLen > 0) { - nsresult res = NS_OK; - PRBool breakBetween = PR_FALSE; - if(aForLineBreak) - res = mLineBreaker->BreakInBetween( - cp0, offset+1, - &(mBuffer[mBufferLength-wordLen]), wordLen, - &breakBetween); - else - res = mWordBreaker->BreakInBetween( - cp0, offset+1, - &(mBuffer[mBufferLength-wordLen]), wordLen, - &breakBetween); - if ( breakBetween ) - goto done; + else if (frag->Is2b()) { + offset = ScanNormalUnicodeText_B(aForLineBreak, &wordLen); + } + else { + offset = ScanNormalAsciiText_B(&wordLen); + } + break; - PRBool tryPrevFrag = PR_FALSE; - PRUint32 prev; + case ePreformatted: + if (('\n' == firstChar) || ('\t' == firstChar)) { + mTransformBuf.mBuffer[mTransformBuf.mBufferLen-1] = firstChar; + offset--; // make sure we overshoot + wordLen = 1; + isWhitespace = PR_TRUE; + } + else { + offset = ScanPreData_B(&wordLen); + } + break; - // Find prev position - - if(aForLineBreak) - res = mLineBreaker->Prev(cp0, offset, offset, &prev, &tryPrevFrag); - else - res = mWordBreaker->Prev(cp0, offset, offset, &prev, &tryPrevFrag); - - - numChars = (offset - prev)+1; - // check buffer size before copy - while((bp - numChars ) < bufEnd) { - PRInt32 delta = (&(mBuffer[mBufferLength])) - bp -1 ; - if(!GrowBuffer()) { - goto done; + case ePreWrap: + if (XP_IS_SPACE(firstChar)) { + if (('\n' == firstChar) || ('\t' == firstChar)) { + mTransformBuf.mBuffer[mTransformBuf.mBufferLen-1] = firstChar; + offset--; // make sure we overshoot + wordLen = 1; } - bp = (&(mBuffer[mBufferLength])) - delta - 1; - bufEnd = mBuffer; - } - - wordLen += numChars; - mCurrentFragOffset -= numChars; - contentLen += numChars; - end = cp - numChars; - - // 1. convert nbsp into space - // 2. check mHasMultibyte flag - // 3. copy buffer - - while(cp > end) { - PRUnichar ch = *cp--; - if (CH_NBSP == ch) ch = ' '; - if (ch > MAX_UNIBYTE) mHasMultibyte = PR_TRUE; - *bp-- = ch; - } - if(! tryPrevFrag) { - // can decide break position inside this TextFrag - goto done; - } - } - } - } - else { - const unsigned char* cp0 = (const unsigned char*) frag->Get1b(); - const unsigned char* end = cp0; - const unsigned char* cp = cp0 + offset; - if (isWhitespace) { - while (cp > end) { - PRUnichar ch = PRUnichar(*cp); - if (XP_IS_SPACE(ch)) { - cp--; - continue; - } - numChars = (cp0 + offset) - cp; - contentLen += numChars; - mCurrentFragOffset -= numChars; - goto done; - } - numChars = (cp0 + offset) - cp; - contentLen += numChars; - } - else { - while (cp >= end) { - PRUnichar ch = PRUnichar(*cp); - if (!XP_IS_SPACE(ch)) { - if (CH_NBSP == ch) ch = ' '; - if (ch > MAX_UNIBYTE) mHasMultibyte = PR_TRUE; - cp--; - - // Store character in buffer; grow buffer if we have to - NS_ASSERTION(bp > bufEnd, "whoops"); - *bp-- = ch; - if (bp == bufEnd) { - PRInt32 delta = (&(mBuffer[mBufferLength])) - bp - 1; - if (!GrowBuffer(PR_FALSE)) { - goto done; - } - bp = (&(mBuffer[mBufferLength])) - delta - 1; - bufEnd = mBuffer; + else { + offset = ScanPreWrapWhiteSpace_B(&wordLen); } - continue; + isWhitespace = PR_TRUE; } - numChars = (cp0 + offset) - cp; - wordLen += numChars; - contentLen += numChars; - mCurrentFragOffset -= numChars; - goto done; + else if (frag->Is2b()) { + offset = ScanNormalUnicodeText_B(aForLineBreak, &wordLen); + } + else { + offset = ScanNormalAsciiText_B(&wordLen); + } + break; + } + + // Backwards scanning routines *always* overshoot by one for the + // returned offset value. + offset = offset + 1; + + result = mTransformBuf.GetBufferEnd() - wordLen; + + if (!isWhitespace) { + switch (mTextTransform) { + case NS_STYLE_TEXT_TRANSFORM_CAPITALIZE: + gCaseConv->ToTitle(result, result, wordLen, !aInWord); + break; + case NS_STYLE_TEXT_TRANSFORM_LOWERCASE: + gCaseConv->ToLower(result, result, wordLen); + break; + case NS_STYLE_TEXT_TRANSFORM_UPPERCASE: + gCaseConv->ToUpper(result, result, wordLen); + break; } - numChars = (cp0 + offset) - cp; - wordLen += numChars; - contentLen += numChars; } } - done:; + *aWordLenResult = wordLen; + *aContentLenResult = mOffset - offset; + *aIsWhiteSpaceResult = isWhitespace; - if (!isWhitespace) - { - switch(mTextTransform) - { - case NS_STYLE_TEXT_TRANSFORM_CAPITALIZE: - gCaseConv->ToTitle(&(mBuffer[mBufferLength-wordLen]), - &(mBuffer[mBufferLength-wordLen]), - wordLen, !aInWord); - break; - case NS_STYLE_TEXT_TRANSFORM_LOWERCASE: - gCaseConv->ToLower(&(mBuffer[mBufferLength-wordLen]), - &(mBuffer[mBufferLength-wordLen]), - wordLen ); - break; - case NS_STYLE_TEXT_TRANSFORM_UPPERCASE: - gCaseConv->ToUpper(&(mBuffer[mBufferLength-wordLen]), - &(mBuffer[mBufferLength-wordLen]), - wordLen ); - break; - default: - break; - } - } + mOffset = offset; - - really_done:; - mOffset -= contentLen; - NS_ASSERTION(mOffset >= 0, "whoops"); - aWordLenResult = wordLen; - aContentLenResult = contentLen; - aIsWhitespaceResult = isWhitespace; - - -#ifdef DEBUG_GETPREVWORD -{ - printf(aIsWhitespaceResult ? "#1 WHITESPACE\n": "NOT WHITESPACE\n"); - if(! aIsWhitespaceResult) - { - PRUnichar* wordBufMem = &(mBuffer[mBufferLength-wordLen]); - PRInt32 ax; - for(ax=0; axtext; + while (*cp) { + if (*cp > 255) { + isAsciiTest = PR_FALSE; + break; + } + cp++; + } + + nsTextFragment frag(st->text); + nsTextTransformer tx(aLineBreaker, aWordBreaker); + + for (PRInt32 preMode = 0; preMode < NUM_MODES; preMode++) { + // Do forwards test + if (gNoisy) { + nsAutoString uc2(st->text); + printf("%s forwards test: '", isAsciiTest ? "ascii" : "unicode"); + fputs(uc2, stdout); + printf("'\n"); + } + tx.Init2(&frag, 0, preModeValue[preMode], NS_STYLE_TEXT_TRANSFORM_NONE); + + int* expectedResults = st->modes[preMode].data; + int resultsLen = st->modes[preMode].length; + + while ((bp = tx.GetNextWord(PR_FALSE, &wordLen, &contentLen, &ws))) { + if (gNoisy) { + nsAutoString tmp(bp, wordLen); + printf(" '"); + fputs(tmp, stdout); + printf("': ws=%s wordLen=%d (%d) contentLen=%d (offset=%d)\n", + ws ? "yes" : "no", + wordLen, *expectedResults, contentLen, tx.mOffset); + } + if (*expectedResults != wordLen) { + error = PR_TRUE; + break; + } + expectedResults++; + } + if (expectedResults != st->modes[preMode].data + resultsLen) { + error = PR_TRUE; + } + + // Do backwards test + if (gNoisy) { + nsAutoString uc2(st->text); + printf("%s backwards test: '", isAsciiTest ? "ascii" : "unicode"); + fputs(uc2, stdout); + printf("'\n"); + } + tx.Init2(&frag, frag.GetLength(), NS_STYLE_WHITESPACE_NORMAL, + NS_STYLE_TEXT_TRANSFORM_NONE); + expectedResults = st->modes[preMode].data + resultsLen; + while ((bp = tx.GetPrevWord(PR_FALSE, &wordLen, &contentLen, &ws))) { + --expectedResults; + if (gNoisy) { + nsAutoString tmp(bp, wordLen); + printf(" '"); + fputs(tmp, stdout); + printf("': ws=%s wordLen=%d contentLen=%d (offset=%d)\n", + ws ? "yes" : "no", + wordLen, contentLen, tx.mOffset); + } + if (*expectedResults != wordLen) { + error = PR_TRUE; + break; + } + } + if (expectedResults != st->modes[preMode].data) { + error = PR_TRUE; + } + + if (error) { + fprintf(stderr, "nsTextTransformer: self test %d failed\n", testNum); + } + testNum++; + } + } + if (error) { + NS_ABORT(); + } } + +nsresult +nsTextTransformer::Init2(const nsTextFragment* aFrag, + PRInt32 aStartingOffset, + PRUint8 aWhiteSpace, + PRUint8 aTextTransform) +{ + mFrag = aFrag; + + // Sanitize aStartingOffset + if (NS_WARN_IF_FALSE(aStartingOffset >= 0, "bad starting offset")) { + aStartingOffset = 0; + } + else if (NS_WARN_IF_FALSE(aStartingOffset <= mFrag->GetLength(), + "bad starting offset")) { + aStartingOffset = mFrag->GetLength(); + } + mOffset = aStartingOffset; + + // Get the frames text style information + if (NS_STYLE_WHITESPACE_PRE == aWhiteSpace) { + mMode = ePreformatted; + } + else if (NS_STYLE_WHITESPACE_MOZ_PRE_WRAP == aWhiteSpace) { + mMode = ePreWrap; + } + mTextTransform = aTextTransform; + + return NS_OK; +} +#endif /* DEBUG */ diff --git a/layout/generic/nsTextTransformer.h b/layout/generic/nsTextTransformer.h index 433f18cf059f..38e279ae110b 100644 --- a/layout/generic/nsTextTransformer.h +++ b/layout/generic/nsTextTransformer.h @@ -30,6 +30,28 @@ class nsIWordBreaker; #define NS_TEXT_TRANSFORMER_AUTO_WORD_BUF_SIZE 100 +// A growable text buffer that tries to avoid using malloc by having a +// builtin buffer. Ideally used as an automatic variable. +class nsAutoTextBuffer { +public: + nsAutoTextBuffer(); + ~nsAutoTextBuffer(); + + nsresult GrowBy(PRInt32 aAtLeast, PRBool aCopyToHead = PR_TRUE); + + nsresult GrowTo(PRInt32 aNewSize, PRBool aCopyToHead = PR_TRUE); + + PRUnichar* GetBuffer() { return mBuffer; } + PRUnichar* GetBufferEnd() { return mBuffer + mBufferLen; } + PRInt32 GetBufferLength() const { return mBufferLen; } + + PRUnichar* mBuffer; + PRInt32 mBufferLen; + PRUnichar mAutoBuffer[NS_TEXT_TRANSFORMER_AUTO_WORD_BUF_SIZE]; +}; + +//---------------------------------------- + /** * This object manages the transformation of text: * @@ -51,7 +73,7 @@ public: // Note: The text transformer does not hold a reference to the line // breaker and work breaker objects nsTextTransformer(nsILineBreaker* aLineBreaker, - nsIWordBreaker *aWordBreaker); + nsIWordBreaker* aWordBreaker); ~nsTextTransformer(); @@ -65,53 +87,90 @@ public: PRInt32 aStartingOffset); PRInt32 GetContentLength() const { - return mContentLength; + return mFrag ? mFrag->GetLength() : 0; } PRUnichar* GetNextWord(PRBool aInWord, - PRInt32& aWordLenResult, - PRInt32& aContentLenResult, - PRBool& aIsWhitespaceResult, + PRInt32* aWordLenResult, + PRInt32* aContentLenResult, + PRBool* aIsWhitespaceResult, PRBool aForLineBreak = PR_TRUE); PRUnichar* GetPrevWord(PRBool aInWord, - PRInt32& aWordLenResult, - PRInt32& aContentLenResult, - PRBool& aIsWhitespaceResult, - PRBool aForLineBreak = PR_TRUE); + PRInt32* aWordLenResult, + PRInt32* aContentLenResult, + PRBool* aIsWhitespaceResult, + PRBool aForLineBreak = PR_TRUE); + PRBool HasMultibyte() const { return mHasMultibyte; } PRUnichar* GetWordBuffer() { - return mBuffer; + return mTransformBuf.GetBuffer(); } PRInt32 GetWordBufferLength() const { - return mBufferLength; + return mTransformBuf.GetBufferLength(); } -protected: - PRBool GrowBuffer(PRBool aForNextWord = PR_TRUE); + static nsresult Initialize(); - PRUnichar* mBuffer; - PRInt32 mBufferLength; + static void Shutdown(); + +protected: + // Helper methods for GetNextWord (F == forwards) + PRInt32 ScanNormalWhiteSpace_F(); + PRInt32 ScanNormalAsciiText_F(PRInt32* aWordLen); + PRInt32 ScanNormalUnicodeText_F(PRBool aForLineBreak, PRInt32* aWordLen); + PRInt32 ScanPreWrapWhiteSpace_F(PRInt32* aWordLen); + PRInt32 ScanPreAsciiData_F(PRInt32* aWordLen); + PRInt32 ScanPreData_F(PRInt32* aWordLen); + + // Helper methods for GetPrevWord (B == backwards) + PRInt32 ScanNormalWhiteSpace_B(); + PRInt32 ScanNormalAsciiText_B(PRInt32* aWordLen); + PRInt32 ScanNormalUnicodeText_B(PRBool aForLineBreak, PRInt32* aWordLen); + PRInt32 ScanPreWrapWhiteSpace_B(PRInt32* aWordLen); + PRInt32 ScanPreData_B(PRInt32* aWordLen); + + // Set to true if at any point during GetNextWord or GetPrevWord we + // run across a multibyte (> 127) unicode character. PRBool mHasMultibyte; - PRInt32 mContentLength; - PRInt32 mStartingOffset; + // The text fragment that we are looking at + const nsTextFragment* mFrag; + + // Our current offset into the text fragment PRInt32 mOffset; - const nsTextFragment* mFrag; - PRInt32 mCurrentFragOffset; - + // The frame's text-transform state PRUint8 mTextTransform; - PRUint8 mPreformatted; - nsILineBreaker* mLineBreaker; // does NOT hold reference - nsIWordBreaker* mWordBreaker; // does NOT hold reference + // The frame's white-space mode we are using to process text + enum { + eNormal, + ePreformatted, + ePreWrap + } mMode; - PRUnichar mAutoWordBuffer[NS_TEXT_TRANSFORMER_AUTO_WORD_BUF_SIZE]; + nsILineBreaker* mLineBreaker; // [WEAK] + + nsIWordBreaker* mWordBreaker; // [WEAK] + + // Buffer used to hold the transformed words from GetNextWord or + // GetPrevWord + nsAutoTextBuffer mTransformBuf; + +#ifdef DEBUG + static void SelfTest(nsILineBreaker* aLineBreaker, + nsIWordBreaker* aWordBreaker); + + nsresult Init2(const nsTextFragment* aFrag, + PRInt32 aStartingOffset, + PRUint8 aWhiteSpace, + PRUint8 aTextTransform); +#endif }; #endif /* nsTextTransformer_h___ */ diff --git a/layout/html/base/src/nsTextTransformer.cpp b/layout/html/base/src/nsTextTransformer.cpp index 6f74a63fa57a..9205c1e8041c 100644 --- a/layout/html/base/src/nsTextTransformer.cpp +++ b/layout/html/base/src/nsTextTransformer.cpp @@ -16,6 +16,7 @@ * Corporation. Portions created by Netscape are Copyright (C) 1998 * Netscape Communications Corporation. All Rights Reserved. */ +#include "nsCOMPtr.h" #include "nsTextTransformer.h" #include "nsIContent.h" #include "nsIFrame.h" @@ -28,13 +29,79 @@ #include "nsIServiceManager.h" #include "nsUnicharUtilCIID.h" #include "nsICaseConversion.h" +#include "prenv.h" + +nsAutoTextBuffer::nsAutoTextBuffer() + : mBuffer(mAutoBuffer), + mBufferLen(NS_TEXT_TRANSFORMER_AUTO_WORD_BUF_SIZE) +{ +} + +nsAutoTextBuffer::~nsAutoTextBuffer() +{ + if (mBuffer && (mBuffer != mAutoBuffer)) { + delete [] mBuffer; + } +} + +nsresult +nsAutoTextBuffer::GrowBy(PRInt32 aAtLeast, PRBool aCopyToHead) +{ + PRInt32 newSize = mBufferLen * 2; + if (newSize < mBufferLen + aAtLeast) { + newSize = mBufferLen + aAtLeast + 100; + } + return GrowTo(newSize, aCopyToHead); +} + +nsresult +nsAutoTextBuffer::GrowTo(PRInt32 aNewSize, PRBool aCopyToHead) +{ + if (aNewSize > mBufferLen) { + PRUnichar* newBuffer = new PRUnichar[aNewSize]; + if (!newBuffer) { + return NS_ERROR_OUT_OF_MEMORY; + } + nsCRT::memcpy(&newBuffer[aCopyToHead ? 0 : mBufferLen], + mBuffer, sizeof(PRUnichar) * mBufferLen); + if (mBuffer != mAutoBuffer) { + delete [] mBuffer; + } + mBuffer = newBuffer; + mBufferLen = aNewSize; + } + return NS_OK; +} + +//---------------------------------------------------------------------- static NS_DEFINE_IID(kUnicharUtilCID, NS_UNICHARUTIL_CID); static NS_DEFINE_IID(kICaseConversionIID, NS_ICASECONVERSION_IID); -static nsICaseConversion* gCaseConv = nsnull; -static nsrefcnt gCaseConvRefCnt = 0; -//#define DEBUG_GETPREVWORD +static nsICaseConversion* gCaseConv = nsnull; + +nsresult +nsTextTransformer::Initialize() +{ + nsresult res = NS_OK; + if (!gCaseConv) { + res = nsServiceManager::GetService(kUnicharUtilCID, kICaseConversionIID, + (nsISupports**)&gCaseConv); + NS_ASSERTION( NS_SUCCEEDED(res), "cannot get UnicharUtil"); + NS_ASSERTION( gCaseConv != NULL, "cannot get UnicharUtil"); + } + return res; +} + +void +nsTextTransformer::Shutdown() +{ + if (gCaseConv) { + nsServiceManager::ReleaseService(kUnicharUtilCID, gCaseConv); + gCaseConv = nsnull; + } +} + // XXX I'm sure there are other special characters #define CH_NBSP 160 @@ -44,32 +111,28 @@ MOZ_DECL_CTOR_COUNTER(nsTextTransformer); nsTextTransformer::nsTextTransformer(nsILineBreaker* aLineBreaker, nsIWordBreaker* aWordBreaker) - : mBuffer(mAutoWordBuffer), - mBufferLength(NS_TEXT_TRANSFORMER_AUTO_WORD_BUF_SIZE), - mHasMultibyte(PR_FALSE), + : mHasMultibyte(PR_FALSE), + mFrag(nsnull), + mOffset(0), + mTextTransform(NS_STYLE_TEXT_TRANSFORM_NONE), + mMode(eNormal), mLineBreaker(aLineBreaker), mWordBreaker(aWordBreaker) { MOZ_COUNT_CTOR(nsTextTransformer); - if (gCaseConvRefCnt++ == 0) { - nsresult res; - res = nsServiceManager::GetService(kUnicharUtilCID, kICaseConversionIID, - (nsISupports**)&gCaseConv); - NS_ASSERTION( NS_SUCCEEDED(res), "cannot get UnicharUtil"); - NS_ASSERTION( gCaseConv != NULL, "cannot get UnicharUtil"); + +#ifdef DEBUG + static PRBool firstTime = PR_TRUE; + if (firstTime) { + firstTime = PR_FALSE; + SelfTest(aLineBreaker, aWordBreaker); } +#endif } nsTextTransformer::~nsTextTransformer() { MOZ_COUNT_DTOR(nsTextTransformer); - if (mBuffer != mAutoWordBuffer) { - delete [] mBuffer; - } - if (--gCaseConvRefCnt == 0) { - nsServiceManager::ReleaseService(kUnicharUtilCID, gCaseConv); - gCaseConv = nsnull; - } } nsresult @@ -77,549 +140,847 @@ nsTextTransformer::Init(nsIFrame* aFrame, nsIContent* aContent, PRInt32 aStartingOffset) { - // Get the frames text content - nsITextContent* tc; - if (NS_OK != aContent->QueryInterface(kITextContentIID, (void**) &tc)) { - return NS_OK; + // Get the contents text content + nsresult rv; + nsCOMPtr tc = do_QueryInterface(aContent, &rv); + if (tc.get()) { + tc->GetText(&mFrag); + + // Sanitize aStartingOffset + if (NS_WARN_IF_FALSE(aStartingOffset >= 0, "bad starting offset")) { + aStartingOffset = 0; + } + else if (NS_WARN_IF_FALSE(aStartingOffset <= mFrag->GetLength(), + "bad starting offset")) { + aStartingOffset = mFrag->GetLength(); + } + mOffset = aStartingOffset; + + // Get the frames text style information + const nsStyleText* styleText; + aFrame->GetStyleData(eStyleStruct_Text, (const nsStyleStruct*&) styleText); + if (NS_STYLE_WHITESPACE_PRE == styleText->mWhiteSpace) { + mMode = ePreformatted; + } + else if (NS_STYLE_WHITESPACE_MOZ_PRE_WRAP == styleText->mWhiteSpace) { + mMode = ePreWrap; + } + mTextTransform = styleText->mTextTransform; } - tc->GetText(&mFrag); - NS_RELEASE(tc); - mStartingOffset = aStartingOffset; - mOffset = mStartingOffset; - - // Compute the total length of the text content. - mContentLength = mFrag->GetLength(); - - // Set current fragment offset - mCurrentFragOffset = aStartingOffset; - - // Get the frames style and choose a transform proc - const nsStyleText* styleText; - aFrame->GetStyleData(eStyleStruct_Text, (const nsStyleStruct*&) styleText); - mPreformatted = (NS_STYLE_WHITESPACE_PRE == styleText->mWhiteSpace) || - (NS_STYLE_WHITESPACE_MOZ_PRE_WRAP == styleText->mWhiteSpace); - mTextTransform = styleText->mTextTransform; - return NS_OK; + return rv; } -PRBool -nsTextTransformer::GrowBuffer(PRBool aForNextWord) +//---------------------------------------------------------------------- + +// wordlen==1, contentlen=newOffset-currentOffset, isWhitespace=t +PRInt32 +nsTextTransformer::ScanNormalWhiteSpace_F() { - PRInt32 newLen = mBufferLength * 2; - if (newLen <= 100) { - newLen = 100; - } - PRUnichar* newBuffer = new PRUnichar[newLen]; - if (nsnull == newBuffer) { - return PR_FALSE; - } - if (0 != mBufferLength) { - if(aForNextWord) - nsCRT::memcpy(newBuffer, mBuffer, sizeof(PRUnichar) * mBufferLength); - else - nsCRT::memcpy(&newBuffer[mBufferLength], mBuffer, - sizeof(PRUnichar) * mBufferLength); - if (mBuffer != mAutoWordBuffer) { - delete [] mBuffer; + const nsTextFragment* frag = mFrag; + PRInt32 fragLen = frag->GetLength(); + PRInt32 offset = mOffset; + + for (; offset < fragLen; offset++) { + PRUnichar ch = frag->CharAt(offset); + if (!XP_IS_SPACE(ch)) { + break; } } - mBuffer = newBuffer; - mBufferLength = newLen; - return PR_TRUE; + + mTransformBuf.mBuffer[0] = ' '; + return offset; } +// wordlen==*aWordLen, contentlen=newOffset-currentOffset, isWhitespace=f +PRInt32 +nsTextTransformer::ScanNormalAsciiText_F(PRInt32* aWordLen) +{ + const nsTextFragment* frag = mFrag; + PRInt32 fragLen = frag->GetLength(); + PRInt32 offset = mOffset; + PRUnichar* bp = mTransformBuf.GetBuffer(); + PRUnichar* endbp = mTransformBuf.GetBufferEnd(); + + for (; offset < fragLen; offset++) { + PRUnichar ch = frag->CharAt(offset); + if (XP_IS_SPACE(ch)) { + break; + } + if (bp == endbp) { + PRInt32 oldLength = bp - mTransformBuf.GetBuffer(); + nsresult rv = mTransformBuf.GrowBy(1000); + if (NS_FAILED(rv)) { + // If we run out of space (unlikely) then just chop the input + break; + } + bp = mTransformBuf.GetBuffer() + oldLength; + endbp = mTransformBuf.GetBufferEnd(); + } + *bp++ = ch; + } + + *aWordLen = bp - mTransformBuf.GetBuffer(); + return offset; +} + +// wordlen==*aWordLen, contentlen=newOffset-currentOffset, isWhitespace=f +PRInt32 +nsTextTransformer::ScanNormalUnicodeText_F(PRBool aForLineBreak, + PRInt32* aWordLen) +{ + const nsTextFragment* frag = mFrag; + const PRUnichar* cp0 = frag->Get2b(); + PRInt32 fragLen = frag->GetLength(); + PRInt32 offset = mOffset; + + PRUnichar firstChar = frag->CharAt(offset++); + mTransformBuf.mBuffer[0] = firstChar; + if (firstChar > MAX_UNIBYTE) mHasMultibyte = PR_TRUE; + + // Only evaluate complex breaking logic if there are more characters + // beyond the first to look at. + PRInt32 numChars = 1; + if (offset < fragLen) { + const PRUnichar* cp = cp0 + offset; + PRBool breakBetween = PR_FALSE; + if (aForLineBreak) { + mLineBreaker->BreakInBetween(mTransformBuf.GetBuffer(), 1, + cp, (fragLen-offset), &breakBetween); + } + else { + mWordBreaker->BreakInBetween(mTransformBuf.GetBuffer(), 1, + cp, (fragLen-offset), &breakBetween); + } + + if (!breakBetween) { + // Find next position + PRBool tryNextFrag; + PRUint32 next; + if (aForLineBreak) { + mLineBreaker->Next(cp0, fragLen, offset, &next, &tryNextFrag); + } + else { + mWordBreaker->Next(cp0, fragLen, offset, &next, &tryNextFrag); + } + numChars = (PRInt32) (next - (PRUint32) offset) + 1; + + // Grow buffer before copying + nsresult rv = mTransformBuf.GrowTo(numChars); + if (NS_FAILED(rv)) { + numChars = mTransformBuf.GetBufferLength(); + } + + // 1. convert nbsp into space + // 2. check mHasMultibyte flag + // 3. copy buffer + PRUnichar* bp = mTransformBuf.GetBuffer() + 1; + const PRUnichar* end = cp + numChars - 1; + while (cp < end) { + PRUnichar ch = *cp++; + if (CH_NBSP == ch) ch = ' '; + if (ch > MAX_UNIBYTE) mHasMultibyte = PR_TRUE; + *bp++ = ch; + } + } + + } + + *aWordLen = numChars; + return offset + numChars - 1; +} + +// wordlen==*aWordLen, contentlen=newOffset-currentOffset, isWhitespace=t +PRInt32 +nsTextTransformer::ScanPreWrapWhiteSpace_F(PRInt32* aWordLen) +{ + const nsTextFragment* frag = mFrag; + PRInt32 fragLen = frag->GetLength(); + PRInt32 offset = mOffset; + PRUnichar* bp = mTransformBuf.GetBuffer(); + PRUnichar* endbp = mTransformBuf.GetBufferEnd(); + + for (; offset < fragLen; offset++) { + PRUnichar ch = frag->CharAt(offset); + if (!XP_IS_SPACE(ch) || (ch == '\t') || (ch == '\n')) { + break; + } + if (bp == endbp) { + PRInt32 oldLength = bp - mTransformBuf.GetBuffer(); + nsresult rv = mTransformBuf.GrowBy(1000); + if (NS_FAILED(rv)) { + // If we run out of space (unlikely) then just chop the input + break; + } + bp = mTransformBuf.GetBuffer() + oldLength; + endbp = mTransformBuf.GetBufferEnd(); + } + *bp++ = ' '; + } + + *aWordLen = bp - mTransformBuf.GetBuffer(); + return offset; +} + +// wordlen==*aWordLen, contentlen=newOffset-currentOffset, isWhitespace=f +PRInt32 +nsTextTransformer::ScanPreData_F(PRInt32* aWordLen) +{ + const nsTextFragment* frag = mFrag; + PRInt32 fragLen = frag->GetLength(); + PRInt32 offset = mOffset; + PRUnichar* bp = mTransformBuf.GetBuffer(); + PRUnichar* endbp = mTransformBuf.GetBufferEnd(); + + for (; offset < fragLen; offset++) { + PRUnichar ch = frag->CharAt(offset); + if ((ch == '\t') || (ch == '\n')) { + break; + } + if (ch > MAX_UNIBYTE) mHasMultibyte = PR_TRUE; + if (bp == endbp) { + PRInt32 oldLength = bp - mTransformBuf.GetBuffer(); + nsresult rv = mTransformBuf.GrowBy(1000); + if (NS_FAILED(rv)) { + // If we run out of space (unlikely) then just chop the input + break; + } + bp = mTransformBuf.GetBuffer() + oldLength; + endbp = mTransformBuf.GetBufferEnd(); + } + *bp++ = ch; + } + + *aWordLen = bp - mTransformBuf.GetBuffer(); + return offset; +} + +// wordlen==*aWordLen, contentlen=newOffset-currentOffset, isWhitespace=f +PRInt32 +nsTextTransformer::ScanPreAsciiData_F(PRInt32* aWordLen) +{ + const nsTextFragment* frag = mFrag; + PRUnichar* bp = mTransformBuf.GetBuffer(); + PRUnichar* endbp = mTransformBuf.GetBufferEnd(); + const char* cp = frag->Get1b(); + const char* end = cp + frag->GetLength(); + cp += mOffset; + + while (cp < end) { + PRUnichar ch = (PRUnichar) *cp++; + if ((ch == '\t') || (ch == '\n')) { + cp--; + break; + } + if (bp == endbp) { + PRInt32 oldLength = bp - mTransformBuf.GetBuffer(); + nsresult rv = mTransformBuf.GrowBy(1000); + if (NS_FAILED(rv)) { + // If we run out of space (unlikely) then just chop the input + break; + } + bp = mTransformBuf.GetBuffer() + oldLength; + endbp = mTransformBuf.GetBufferEnd(); + } + *bp++ = ch; + } + + *aWordLen = bp - mTransformBuf.GetBuffer(); + return cp - frag->Get1b(); +} + +//---------------------------------------- + PRUnichar* nsTextTransformer::GetNextWord(PRBool aInWord, - PRInt32& aWordLenResult, - PRInt32& aContentLenResult, - PRBool& aIsWhitespaceResult, + PRInt32* aWordLenResult, + PRInt32* aContentLenResult, + PRBool* aIsWhiteSpaceResult, PRBool aForLineBreak) { - NS_PRECONDITION(mOffset <= mContentLength, "bad offset"); - NS_PRECONDITION(((nsnull != mLineBreaker)||(!aForLineBreak)), "null in line breaker"); - NS_PRECONDITION(((nsnull != mWordBreaker)||( aForLineBreak)), "null in word breaker"); - - // See if the content has been exhausted - if (mOffset == mContentLength) { - aWordLenResult = 0; - aContentLenResult = 0; - return nsnull; - } - - PRInt32 numChars; - PRInt32 fragLen; - PRUnichar* bp = mBuffer; - PRUnichar* bufEnd = mBuffer + mBufferLength; const nsTextFragment* frag = mFrag; - PRInt32 wordLen = 1; - PRInt32 contentLen = 1; + PRInt32 fragLen = frag->GetLength(); + PRInt32 offset = mOffset; + PRInt32 wordLen = 0; + PRBool isWhitespace = PR_FALSE; + PRUnichar* result = nsnull; - // Set the isWhitespace flag by examining the next character in the - // text fragment. - PRInt32 offset = mCurrentFragOffset; - PRUnichar firstChar; - if (frag->Is2b()) { - const PRUnichar* up = frag->Get2b(); - firstChar = up[offset]; - } - else { - const unsigned char* cp = (const unsigned char*) frag->Get1b(); - firstChar = PRUnichar(cp[offset]); - } - PRBool isWhitespace = XP_IS_SPACE(firstChar); - offset++; - if (isWhitespace) { - if (mPreformatted) { - if ('\t' == firstChar) { - // Leave tab alone so that caller can expand it + if (offset < fragLen) { + PRUnichar firstChar = frag->CharAt(offset); + switch (mMode) { + default: + case eNormal: + if (XP_IS_SPACE(firstChar)) { + offset = ScanNormalWhiteSpace_F(); + wordLen = 1; + isWhitespace = PR_TRUE; + } + else if (frag->Is2b()) { + offset = ScanNormalUnicodeText_F(aForLineBreak, &wordLen); + } + else { + offset = ScanNormalAsciiText_F(&wordLen); + } + break; + + case ePreformatted: + if (('\n' == firstChar) || ('\t' == firstChar)) { + mTransformBuf.mBuffer[0] = firstChar; + offset++; + wordLen = 1; + isWhitespace = PR_TRUE; + } + else if (frag->Is2b()) { + offset = ScanPreData_F(&wordLen); + } + else { + offset = ScanPreAsciiData_F(&wordLen); + } + break; + + case ePreWrap: + if (XP_IS_SPACE(firstChar)) { + if (('\n' == firstChar) || ('\t' == firstChar)) { + mTransformBuf.mBuffer[0] = firstChar; + offset++; + wordLen = 1; + } + else { + offset = ScanPreWrapWhiteSpace_F(&wordLen); + } + isWhitespace = PR_TRUE; + } + else if (frag->Is2b()) { + offset = ScanNormalUnicodeText_F(aForLineBreak, &wordLen); + } + else { + offset = ScanNormalAsciiText_F(&wordLen); + } + break; + } + result = mTransformBuf.GetBuffer(); + + if (!isWhitespace) { + switch (mTextTransform) { + case NS_STYLE_TEXT_TRANSFORM_CAPITALIZE: + gCaseConv->ToTitle(result, result, wordLen, !aInWord); + break; + case NS_STYLE_TEXT_TRANSFORM_LOWERCASE: + gCaseConv->ToLower(result, result, wordLen); + break; + case NS_STYLE_TEXT_TRANSFORM_UPPERCASE: + gCaseConv->ToUpper(result, result, wordLen); + break; } - else if ('\n' == firstChar) { - // Advance content past newline but do not allow newline to - // remain in the word. - wordLen--; + } + } + + *aWordLenResult = wordLen; + *aContentLenResult = offset - mOffset; + *aIsWhiteSpaceResult = isWhitespace; + + mOffset = offset; + + return result; +} + +//---------------------------------------------------------------------- + +// wordlen==1, contentlen=newOffset-currentOffset, isWhitespace=t +PRInt32 +nsTextTransformer::ScanNormalWhiteSpace_B() +{ + const nsTextFragment* frag = mFrag; + PRInt32 offset = mOffset; + + while (--offset >= 0) { + PRUnichar ch = frag->CharAt(offset); + if (!XP_IS_SPACE(ch)) { + break; + } + } + + mTransformBuf.mBuffer[mTransformBuf.mBufferLen - 1] = ' '; + return offset; +} + +// wordlen==*aWordLen, contentlen=newOffset-currentOffset, isWhitespace=f +PRInt32 +nsTextTransformer::ScanNormalAsciiText_B(PRInt32* aWordLen) +{ + const nsTextFragment* frag = mFrag; + PRInt32 offset = mOffset; + PRUnichar* bp = mTransformBuf.GetBufferEnd(); + PRUnichar* startbp = mTransformBuf.GetBuffer(); + + while (--offset >= 0) { + PRUnichar ch = frag->CharAt(offset); + if (XP_IS_SPACE(ch)) { + break; + } + if (bp == startbp) { + PRInt32 oldLength = mTransformBuf.mBufferLen; + nsresult rv = mTransformBuf.GrowBy(1000); + if (NS_FAILED(rv)) { + // If we run out of space (unlikely) then just chop the input + break; + } + bp = mTransformBuf.GetBufferEnd() - oldLength; + startbp = mTransformBuf.GetBuffer(); + } + *--bp = ch; + } + + *aWordLen = mTransformBuf.GetBufferEnd() - bp; + return offset; +} + +// wordlen==*aWordLen, contentlen=newOffset-currentOffset, isWhitespace=f +PRInt32 +nsTextTransformer::ScanNormalUnicodeText_B(PRBool aForLineBreak, + PRInt32* aWordLen) +{ + const nsTextFragment* frag = mFrag; + const PRUnichar* cp0 = frag->Get2b(); + PRInt32 offset = mOffset - 1; + + PRUnichar firstChar = frag->CharAt(offset); + mTransformBuf.mBuffer[mTransformBuf.mBufferLen - 1] = firstChar; + if (firstChar > MAX_UNIBYTE) mHasMultibyte = PR_TRUE; + + PRInt32 numChars = 1; + if (offset > 0) { + const PRUnichar* cp = cp0 + offset; + PRBool breakBetween = PR_FALSE; + if (aForLineBreak) { + mLineBreaker->BreakInBetween(cp0, offset + 1, + mTransformBuf.GetBufferEnd()-1, 1, + &breakBetween); + } + else { + mWordBreaker->BreakInBetween(cp0, offset + 1, + mTransformBuf.GetBufferEnd()-1, 1, + &breakBetween); + } + + if (!breakBetween) { + // Find next position + PRBool tryPrevFrag; + PRUint32 prev; + if (aForLineBreak) { + mLineBreaker->Prev(cp0, offset, offset, &prev, &tryPrevFrag); } else { - firstChar = ' '; + mWordBreaker->Prev(cp0, offset, offset, &prev, &tryPrevFrag); } - } - else { - firstChar = ' '; - } - } - else if (CH_NBSP == firstChar) { - firstChar = ' '; - } - if (firstChar > MAX_UNIBYTE) mHasMultibyte = PR_TRUE; - *bp++ = firstChar; - mCurrentFragOffset = offset; - if (isWhitespace && mPreformatted) { - goto really_done; - } + numChars = (PRInt32) ((PRUint32) offset - prev) + 1; - fragLen = frag->GetLength(); - - // Scan characters in this fragment that are the same kind as the - // isWhitespace flag indicates. - if (frag->Is2b()) { - const PRUnichar* cp0 = frag->Get2b(); - const PRUnichar* end = cp0 + fragLen; - const PRUnichar* cp = cp0 + offset; - if (isWhitespace) { - while (cp < end) { - PRUnichar ch = *cp; - if (XP_IS_SPACE(ch)) { - cp++; - continue; - } - numChars = (cp - offset) - cp0; - contentLen += numChars; - mCurrentFragOffset += numChars; - goto done; + // Grow buffer before copying + nsresult rv = mTransformBuf.GrowTo(numChars); + if (NS_FAILED(rv)) { + numChars = mTransformBuf.GetBufferLength(); } - numChars = (cp - offset) - cp0; - contentLen += numChars; - } - else { - if(wordLen > 0) { - nsresult res = NS_OK; - PRBool breakBetween = PR_FALSE; - if(aForLineBreak) - res = mLineBreaker->BreakInBetween(mBuffer, wordLen, - cp, (fragLen-offset), &breakBetween); - else - res = mWordBreaker->BreakInBetween(mBuffer, wordLen, - cp, (fragLen-offset), &breakBetween); - if ( breakBetween ) - goto done; - PRBool tryNextFrag = PR_FALSE; - PRUint32 next; - - // Find next position - - if(aForLineBreak) - res = mLineBreaker->Next(cp0, fragLen, offset, &next, &tryNextFrag); - else - res = mWordBreaker->Next(cp0, fragLen, offset, &next, &tryNextFrag); - - - numChars = (next - offset); - // check buffer size before copy - while((bp + numChars ) > bufEnd) { - PRInt32 delta = bp - mBuffer; - if(!GrowBuffer()) { - goto done; - } - bp = mBuffer + delta; - bufEnd = mBuffer + mBufferLength; - } - - wordLen += numChars; - mCurrentFragOffset += numChars; - contentLen += numChars; - end = cp + numChars; - - // 1. convert nbsp into space - // 2. check mHasMultibyte flag - // 3. copy buffer - - while(cp < end) { - PRUnichar ch = *cp++; - if (CH_NBSP == ch) ch = ' '; - if (ch > MAX_UNIBYTE) mHasMultibyte = PR_TRUE; - *bp++ = ch; - } - if(! tryNextFrag) { - // can decide break position inside this TextFrag - goto done; - } + // 1. convert nbsp into space + // 2. check mHasMultibyte flag + // 3. copy buffer + PRUnichar* bp = mTransformBuf.GetBufferEnd() - 1; + const PRUnichar* end = cp - numChars; + while (cp > end) { + PRUnichar ch = *--cp; + if (CH_NBSP == ch) ch = ' '; + if (ch > MAX_UNIBYTE) mHasMultibyte = PR_TRUE; + *--bp = ch; } } } - else { - const unsigned char* cp0 = (const unsigned char*) frag->Get1b(); - const unsigned char* end = cp0 + fragLen; - const unsigned char* cp = cp0 + offset; - if (isWhitespace) { - while (cp < end) { - PRUnichar ch = PRUnichar(*cp); - if (XP_IS_SPACE(ch)) { - cp++; - continue; - } - numChars = (cp - offset) - cp0; - contentLen += numChars; - mCurrentFragOffset += numChars; - goto done; - } - numChars = (cp - offset) - cp0; - contentLen += numChars; - } - else { - while (cp < end) { - PRUnichar ch = PRUnichar(*cp); - if (!XP_IS_SPACE(ch)) { - if (CH_NBSP == ch) ch = ' '; - if (ch > MAX_UNIBYTE) mHasMultibyte = PR_TRUE; - cp++; - // Store character in buffer; grow buffer if we have to - NS_ASSERTION(bp < bufEnd, "whoops"); - *bp++ = ch; - if (bp == bufEnd) { - PRInt32 delta = bp - mBuffer; - if (!GrowBuffer()) { - goto done; - } - bp = mBuffer + delta; - bufEnd = mBuffer + mBufferLength; - } - continue; - } - numChars = (cp - offset) - cp0; - wordLen += numChars; - contentLen += numChars; - mCurrentFragOffset += numChars; - goto done; - } - numChars = (cp - offset) - cp0; - wordLen += numChars; - contentLen += numChars; - } - } - - done:; - - if (!isWhitespace) - { - switch(mTextTransform) - { - case NS_STYLE_TEXT_TRANSFORM_CAPITALIZE: - gCaseConv->ToTitle(mBuffer, mBuffer, wordLen, !aInWord); - break; - case NS_STYLE_TEXT_TRANSFORM_LOWERCASE: - gCaseConv->ToLower(mBuffer, mBuffer, wordLen ); - break; - case NS_STYLE_TEXT_TRANSFORM_UPPERCASE: - gCaseConv->ToUpper(mBuffer, mBuffer, wordLen ); - break; - default: - break; - } - } - - really_done:; - mOffset += contentLen; - NS_ASSERTION(mOffset <= mContentLength, "whoops"); - aWordLenResult = wordLen; - aContentLenResult = contentLen; - aIsWhitespaceResult = isWhitespace; - - return mBuffer; + *aWordLen = numChars; + return offset - numChars; } +// wordlen==*aWordLen, contentlen=newOffset-currentOffset, isWhitespace=t +PRInt32 +nsTextTransformer::ScanPreWrapWhiteSpace_B(PRInt32* aWordLen) +{ + const nsTextFragment* frag = mFrag; + PRInt32 offset = mOffset; + PRUnichar* bp = mTransformBuf.GetBufferEnd(); + PRUnichar* startbp = mTransformBuf.GetBuffer(); + + while (--offset >= 0) { + PRUnichar ch = frag->CharAt(offset); + if (!XP_IS_SPACE(ch) || (ch == '\t') || (ch == '\n')) { + break; + } + if (bp == startbp) { + PRInt32 oldLength = mTransformBuf.mBufferLen; + nsresult rv = mTransformBuf.GrowBy(1000); + if (NS_FAILED(rv)) { + // If we run out of space (unlikely) then just chop the input + break; + } + bp = mTransformBuf.GetBufferEnd() - oldLength; + startbp = mTransformBuf.GetBuffer(); + } + *--bp = ' '; + } + + *aWordLen = mTransformBuf.GetBufferEnd() - bp; + return offset; +} + +// wordlen==*aWordLen, contentlen=newOffset-currentOffset, isWhitespace=f +PRInt32 +nsTextTransformer::ScanPreData_B(PRInt32* aWordLen) +{ + const nsTextFragment* frag = mFrag; + PRInt32 offset = mOffset; + PRUnichar* bp = mTransformBuf.GetBufferEnd(); + PRUnichar* startbp = mTransformBuf.GetBuffer(); + + while (--offset >= 0) { + PRUnichar ch = frag->CharAt(offset); + if ((ch == '\t') || (ch == '\n')) { + break; + } + if (ch > MAX_UNIBYTE) mHasMultibyte = PR_TRUE; + if (bp == startbp) { + PRInt32 oldLength = mTransformBuf.mBufferLen; + nsresult rv = mTransformBuf.GrowBy(1000); + if (NS_FAILED(rv)) { + // If we run out of space (unlikely) then just chop the input + offset++; + break; + } + bp = mTransformBuf.GetBufferEnd() - oldLength; + startbp = mTransformBuf.GetBuffer(); + } + *--bp = ch; + } + + *aWordLen = mTransformBuf.GetBufferEnd() - bp; + return offset; +} + +//---------------------------------------- + PRUnichar* nsTextTransformer::GetPrevWord(PRBool aInWord, - PRInt32& aWordLenResult, - PRInt32& aContentLenResult, - PRBool& aIsWhitespaceResult, + PRInt32* aWordLenResult, + PRInt32* aContentLenResult, + PRBool* aIsWhiteSpaceResult, PRBool aForLineBreak) { - NS_PRECONDITION(mOffset <= mContentLength, "bad offset"); - NS_PRECONDITION(((nsnull != mLineBreaker)||(!aForLineBreak)), "null in line breaker"); - NS_PRECONDITION(((nsnull != mWordBreaker)||( aForLineBreak)), "null in word breaker"); - - // See if the content has been exhausted - if (mOffset == 0) { - aWordLenResult = 0; - aContentLenResult = 0; - return nsnull; - } - - PRUnichar* bp = mBuffer+mBufferLength-1; - PRUnichar* bufEnd = mBuffer ; const nsTextFragment* frag = mFrag; - PRInt32 wordLen = 1; - PRInt32 contentLen = 1; + PRInt32 offset = mOffset; + PRInt32 wordLen = 0; + PRBool isWhitespace = PR_FALSE; + PRUnichar* result = nsnull; - // Set the isWhitespace flag by examining the next character in the - // text fragment. - PRInt32 offset = mCurrentFragOffset-1; - PRUnichar firstChar; - if (frag->Is2b()) { - const PRUnichar* up = frag->Get2b(); - if (offset > 0) - firstChar = up[offset]; - else - firstChar = up[0]; - } - else { - const unsigned char* cp = (const unsigned char*) frag->Get1b(); - if (offset > 0) - firstChar = PRUnichar(cp[offset]); - else - firstChar = PRUnichar(cp[0]); - } - PRBool isWhitespace = XP_IS_SPACE(firstChar); - offset--; - if (isWhitespace) { - if (mPreformatted) { - if ('\t' == firstChar) { - // Leave tab alone so that caller can expand it - } - else if ('\n' == firstChar) { - // Advance content past newline but do not allow newline to - // remain in the word. - wordLen--; - } - else { - firstChar = ' '; - } - } - else { - firstChar = ' '; - } - } - else if (CH_NBSP == firstChar) { - firstChar = ' '; - } - if(firstChar > MAX_UNIBYTE) mHasMultibyte = PR_TRUE; - *bp-- = firstChar; - mCurrentFragOffset = offset +1; - if (offset < 0) { - goto really_done; - } - if (isWhitespace && mPreformatted) { - goto really_done; - } - - PRInt32 numChars; - - // Scan characters in this fragment that are the same kind as the - // isWhitespace flag indicates. - if (frag->Is2b()) { - const PRUnichar* cp0 = frag->Get2b(); - const PRUnichar* end = cp0; - const PRUnichar* cp = cp0 + offset; - if (isWhitespace) { - while (cp > end) { - PRUnichar ch = *cp; - if (XP_IS_SPACE(ch)) { - cp--; - continue; + if (--offset >= 0) { + PRUnichar firstChar = frag->CharAt(offset); + switch (mMode) { + default: + case eNormal: + if (XP_IS_SPACE(firstChar)) { + offset = ScanNormalWhiteSpace_B(); + wordLen = 1; + isWhitespace = PR_TRUE; } - numChars = (cp0 + offset) - cp; - contentLen += numChars; - mCurrentFragOffset -= numChars; - goto done; - } - numChars = (cp0 + offset) - cp; - contentLen += numChars; - } - else { - if(wordLen > 0) { - nsresult res = NS_OK; - PRBool breakBetween = PR_FALSE; - if(aForLineBreak) - res = mLineBreaker->BreakInBetween( - cp0, offset+1, - &(mBuffer[mBufferLength-wordLen]), wordLen, - &breakBetween); - else - res = mWordBreaker->BreakInBetween( - cp0, offset+1, - &(mBuffer[mBufferLength-wordLen]), wordLen, - &breakBetween); - if ( breakBetween ) - goto done; + else if (frag->Is2b()) { + offset = ScanNormalUnicodeText_B(aForLineBreak, &wordLen); + } + else { + offset = ScanNormalAsciiText_B(&wordLen); + } + break; - PRBool tryPrevFrag = PR_FALSE; - PRUint32 prev; + case ePreformatted: + if (('\n' == firstChar) || ('\t' == firstChar)) { + mTransformBuf.mBuffer[mTransformBuf.mBufferLen-1] = firstChar; + offset--; // make sure we overshoot + wordLen = 1; + isWhitespace = PR_TRUE; + } + else { + offset = ScanPreData_B(&wordLen); + } + break; - // Find prev position - - if(aForLineBreak) - res = mLineBreaker->Prev(cp0, offset, offset, &prev, &tryPrevFrag); - else - res = mWordBreaker->Prev(cp0, offset, offset, &prev, &tryPrevFrag); - - - numChars = (offset - prev)+1; - // check buffer size before copy - while((bp - numChars ) < bufEnd) { - PRInt32 delta = (&(mBuffer[mBufferLength])) - bp -1 ; - if(!GrowBuffer()) { - goto done; + case ePreWrap: + if (XP_IS_SPACE(firstChar)) { + if (('\n' == firstChar) || ('\t' == firstChar)) { + mTransformBuf.mBuffer[mTransformBuf.mBufferLen-1] = firstChar; + offset--; // make sure we overshoot + wordLen = 1; } - bp = (&(mBuffer[mBufferLength])) - delta - 1; - bufEnd = mBuffer; - } - - wordLen += numChars; - mCurrentFragOffset -= numChars; - contentLen += numChars; - end = cp - numChars; - - // 1. convert nbsp into space - // 2. check mHasMultibyte flag - // 3. copy buffer - - while(cp > end) { - PRUnichar ch = *cp--; - if (CH_NBSP == ch) ch = ' '; - if (ch > MAX_UNIBYTE) mHasMultibyte = PR_TRUE; - *bp-- = ch; - } - if(! tryPrevFrag) { - // can decide break position inside this TextFrag - goto done; - } - } - } - } - else { - const unsigned char* cp0 = (const unsigned char*) frag->Get1b(); - const unsigned char* end = cp0; - const unsigned char* cp = cp0 + offset; - if (isWhitespace) { - while (cp > end) { - PRUnichar ch = PRUnichar(*cp); - if (XP_IS_SPACE(ch)) { - cp--; - continue; - } - numChars = (cp0 + offset) - cp; - contentLen += numChars; - mCurrentFragOffset -= numChars; - goto done; - } - numChars = (cp0 + offset) - cp; - contentLen += numChars; - } - else { - while (cp >= end) { - PRUnichar ch = PRUnichar(*cp); - if (!XP_IS_SPACE(ch)) { - if (CH_NBSP == ch) ch = ' '; - if (ch > MAX_UNIBYTE) mHasMultibyte = PR_TRUE; - cp--; - - // Store character in buffer; grow buffer if we have to - NS_ASSERTION(bp > bufEnd, "whoops"); - *bp-- = ch; - if (bp == bufEnd) { - PRInt32 delta = (&(mBuffer[mBufferLength])) - bp - 1; - if (!GrowBuffer(PR_FALSE)) { - goto done; - } - bp = (&(mBuffer[mBufferLength])) - delta - 1; - bufEnd = mBuffer; + else { + offset = ScanPreWrapWhiteSpace_B(&wordLen); } - continue; + isWhitespace = PR_TRUE; } - numChars = (cp0 + offset) - cp; - wordLen += numChars; - contentLen += numChars; - mCurrentFragOffset -= numChars; - goto done; + else if (frag->Is2b()) { + offset = ScanNormalUnicodeText_B(aForLineBreak, &wordLen); + } + else { + offset = ScanNormalAsciiText_B(&wordLen); + } + break; + } + + // Backwards scanning routines *always* overshoot by one for the + // returned offset value. + offset = offset + 1; + + result = mTransformBuf.GetBufferEnd() - wordLen; + + if (!isWhitespace) { + switch (mTextTransform) { + case NS_STYLE_TEXT_TRANSFORM_CAPITALIZE: + gCaseConv->ToTitle(result, result, wordLen, !aInWord); + break; + case NS_STYLE_TEXT_TRANSFORM_LOWERCASE: + gCaseConv->ToLower(result, result, wordLen); + break; + case NS_STYLE_TEXT_TRANSFORM_UPPERCASE: + gCaseConv->ToUpper(result, result, wordLen); + break; } - numChars = (cp0 + offset) - cp; - wordLen += numChars; - contentLen += numChars; } } - done:; + *aWordLenResult = wordLen; + *aContentLenResult = mOffset - offset; + *aIsWhiteSpaceResult = isWhitespace; - if (!isWhitespace) - { - switch(mTextTransform) - { - case NS_STYLE_TEXT_TRANSFORM_CAPITALIZE: - gCaseConv->ToTitle(&(mBuffer[mBufferLength-wordLen]), - &(mBuffer[mBufferLength-wordLen]), - wordLen, !aInWord); - break; - case NS_STYLE_TEXT_TRANSFORM_LOWERCASE: - gCaseConv->ToLower(&(mBuffer[mBufferLength-wordLen]), - &(mBuffer[mBufferLength-wordLen]), - wordLen ); - break; - case NS_STYLE_TEXT_TRANSFORM_UPPERCASE: - gCaseConv->ToUpper(&(mBuffer[mBufferLength-wordLen]), - &(mBuffer[mBufferLength-wordLen]), - wordLen ); - break; - default: - break; - } - } + mOffset = offset; - - really_done:; - mOffset -= contentLen; - NS_ASSERTION(mOffset >= 0, "whoops"); - aWordLenResult = wordLen; - aContentLenResult = contentLen; - aIsWhitespaceResult = isWhitespace; - - -#ifdef DEBUG_GETPREVWORD -{ - printf(aIsWhitespaceResult ? "#1 WHITESPACE\n": "NOT WHITESPACE\n"); - if(! aIsWhitespaceResult) - { - PRUnichar* wordBufMem = &(mBuffer[mBufferLength-wordLen]); - PRInt32 ax; - for(ax=0; axtext; + while (*cp) { + if (*cp > 255) { + isAsciiTest = PR_FALSE; + break; + } + cp++; + } + + nsTextFragment frag(st->text); + nsTextTransformer tx(aLineBreaker, aWordBreaker); + + for (PRInt32 preMode = 0; preMode < NUM_MODES; preMode++) { + // Do forwards test + if (gNoisy) { + nsAutoString uc2(st->text); + printf("%s forwards test: '", isAsciiTest ? "ascii" : "unicode"); + fputs(uc2, stdout); + printf("'\n"); + } + tx.Init2(&frag, 0, preModeValue[preMode], NS_STYLE_TEXT_TRANSFORM_NONE); + + int* expectedResults = st->modes[preMode].data; + int resultsLen = st->modes[preMode].length; + + while ((bp = tx.GetNextWord(PR_FALSE, &wordLen, &contentLen, &ws))) { + if (gNoisy) { + nsAutoString tmp(bp, wordLen); + printf(" '"); + fputs(tmp, stdout); + printf("': ws=%s wordLen=%d (%d) contentLen=%d (offset=%d)\n", + ws ? "yes" : "no", + wordLen, *expectedResults, contentLen, tx.mOffset); + } + if (*expectedResults != wordLen) { + error = PR_TRUE; + break; + } + expectedResults++; + } + if (expectedResults != st->modes[preMode].data + resultsLen) { + error = PR_TRUE; + } + + // Do backwards test + if (gNoisy) { + nsAutoString uc2(st->text); + printf("%s backwards test: '", isAsciiTest ? "ascii" : "unicode"); + fputs(uc2, stdout); + printf("'\n"); + } + tx.Init2(&frag, frag.GetLength(), NS_STYLE_WHITESPACE_NORMAL, + NS_STYLE_TEXT_TRANSFORM_NONE); + expectedResults = st->modes[preMode].data + resultsLen; + while ((bp = tx.GetPrevWord(PR_FALSE, &wordLen, &contentLen, &ws))) { + --expectedResults; + if (gNoisy) { + nsAutoString tmp(bp, wordLen); + printf(" '"); + fputs(tmp, stdout); + printf("': ws=%s wordLen=%d contentLen=%d (offset=%d)\n", + ws ? "yes" : "no", + wordLen, contentLen, tx.mOffset); + } + if (*expectedResults != wordLen) { + error = PR_TRUE; + break; + } + } + if (expectedResults != st->modes[preMode].data) { + error = PR_TRUE; + } + + if (error) { + fprintf(stderr, "nsTextTransformer: self test %d failed\n", testNum); + } + testNum++; + } + } + if (error) { + NS_ABORT(); + } } + +nsresult +nsTextTransformer::Init2(const nsTextFragment* aFrag, + PRInt32 aStartingOffset, + PRUint8 aWhiteSpace, + PRUint8 aTextTransform) +{ + mFrag = aFrag; + + // Sanitize aStartingOffset + if (NS_WARN_IF_FALSE(aStartingOffset >= 0, "bad starting offset")) { + aStartingOffset = 0; + } + else if (NS_WARN_IF_FALSE(aStartingOffset <= mFrag->GetLength(), + "bad starting offset")) { + aStartingOffset = mFrag->GetLength(); + } + mOffset = aStartingOffset; + + // Get the frames text style information + if (NS_STYLE_WHITESPACE_PRE == aWhiteSpace) { + mMode = ePreformatted; + } + else if (NS_STYLE_WHITESPACE_MOZ_PRE_WRAP == aWhiteSpace) { + mMode = ePreWrap; + } + mTextTransform = aTextTransform; + + return NS_OK; +} +#endif /* DEBUG */ diff --git a/layout/html/base/src/nsTextTransformer.h b/layout/html/base/src/nsTextTransformer.h index 433f18cf059f..38e279ae110b 100644 --- a/layout/html/base/src/nsTextTransformer.h +++ b/layout/html/base/src/nsTextTransformer.h @@ -30,6 +30,28 @@ class nsIWordBreaker; #define NS_TEXT_TRANSFORMER_AUTO_WORD_BUF_SIZE 100 +// A growable text buffer that tries to avoid using malloc by having a +// builtin buffer. Ideally used as an automatic variable. +class nsAutoTextBuffer { +public: + nsAutoTextBuffer(); + ~nsAutoTextBuffer(); + + nsresult GrowBy(PRInt32 aAtLeast, PRBool aCopyToHead = PR_TRUE); + + nsresult GrowTo(PRInt32 aNewSize, PRBool aCopyToHead = PR_TRUE); + + PRUnichar* GetBuffer() { return mBuffer; } + PRUnichar* GetBufferEnd() { return mBuffer + mBufferLen; } + PRInt32 GetBufferLength() const { return mBufferLen; } + + PRUnichar* mBuffer; + PRInt32 mBufferLen; + PRUnichar mAutoBuffer[NS_TEXT_TRANSFORMER_AUTO_WORD_BUF_SIZE]; +}; + +//---------------------------------------- + /** * This object manages the transformation of text: * @@ -51,7 +73,7 @@ public: // Note: The text transformer does not hold a reference to the line // breaker and work breaker objects nsTextTransformer(nsILineBreaker* aLineBreaker, - nsIWordBreaker *aWordBreaker); + nsIWordBreaker* aWordBreaker); ~nsTextTransformer(); @@ -65,53 +87,90 @@ public: PRInt32 aStartingOffset); PRInt32 GetContentLength() const { - return mContentLength; + return mFrag ? mFrag->GetLength() : 0; } PRUnichar* GetNextWord(PRBool aInWord, - PRInt32& aWordLenResult, - PRInt32& aContentLenResult, - PRBool& aIsWhitespaceResult, + PRInt32* aWordLenResult, + PRInt32* aContentLenResult, + PRBool* aIsWhitespaceResult, PRBool aForLineBreak = PR_TRUE); PRUnichar* GetPrevWord(PRBool aInWord, - PRInt32& aWordLenResult, - PRInt32& aContentLenResult, - PRBool& aIsWhitespaceResult, - PRBool aForLineBreak = PR_TRUE); + PRInt32* aWordLenResult, + PRInt32* aContentLenResult, + PRBool* aIsWhitespaceResult, + PRBool aForLineBreak = PR_TRUE); + PRBool HasMultibyte() const { return mHasMultibyte; } PRUnichar* GetWordBuffer() { - return mBuffer; + return mTransformBuf.GetBuffer(); } PRInt32 GetWordBufferLength() const { - return mBufferLength; + return mTransformBuf.GetBufferLength(); } -protected: - PRBool GrowBuffer(PRBool aForNextWord = PR_TRUE); + static nsresult Initialize(); - PRUnichar* mBuffer; - PRInt32 mBufferLength; + static void Shutdown(); + +protected: + // Helper methods for GetNextWord (F == forwards) + PRInt32 ScanNormalWhiteSpace_F(); + PRInt32 ScanNormalAsciiText_F(PRInt32* aWordLen); + PRInt32 ScanNormalUnicodeText_F(PRBool aForLineBreak, PRInt32* aWordLen); + PRInt32 ScanPreWrapWhiteSpace_F(PRInt32* aWordLen); + PRInt32 ScanPreAsciiData_F(PRInt32* aWordLen); + PRInt32 ScanPreData_F(PRInt32* aWordLen); + + // Helper methods for GetPrevWord (B == backwards) + PRInt32 ScanNormalWhiteSpace_B(); + PRInt32 ScanNormalAsciiText_B(PRInt32* aWordLen); + PRInt32 ScanNormalUnicodeText_B(PRBool aForLineBreak, PRInt32* aWordLen); + PRInt32 ScanPreWrapWhiteSpace_B(PRInt32* aWordLen); + PRInt32 ScanPreData_B(PRInt32* aWordLen); + + // Set to true if at any point during GetNextWord or GetPrevWord we + // run across a multibyte (> 127) unicode character. PRBool mHasMultibyte; - PRInt32 mContentLength; - PRInt32 mStartingOffset; + // The text fragment that we are looking at + const nsTextFragment* mFrag; + + // Our current offset into the text fragment PRInt32 mOffset; - const nsTextFragment* mFrag; - PRInt32 mCurrentFragOffset; - + // The frame's text-transform state PRUint8 mTextTransform; - PRUint8 mPreformatted; - nsILineBreaker* mLineBreaker; // does NOT hold reference - nsIWordBreaker* mWordBreaker; // does NOT hold reference + // The frame's white-space mode we are using to process text + enum { + eNormal, + ePreformatted, + ePreWrap + } mMode; - PRUnichar mAutoWordBuffer[NS_TEXT_TRANSFORMER_AUTO_WORD_BUF_SIZE]; + nsILineBreaker* mLineBreaker; // [WEAK] + + nsIWordBreaker* mWordBreaker; // [WEAK] + + // Buffer used to hold the transformed words from GetNextWord or + // GetPrevWord + nsAutoTextBuffer mTransformBuf; + +#ifdef DEBUG + static void SelfTest(nsILineBreaker* aLineBreaker, + nsIWordBreaker* aWordBreaker); + + nsresult Init2(const nsTextFragment* aFrag, + PRInt32 aStartingOffset, + PRUint8 aWhiteSpace, + PRUint8 aTextTransform); +#endif }; #endif /* nsTextTransformer_h___ */