Back to index

lightning-sunbird  0.9+nobinonly
nsTextTransformer.cpp
Go to the documentation of this file.
00001 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
00002 /* ***** BEGIN LICENSE BLOCK *****
00003  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
00004  *
00005  * The contents of this file are subject to the Mozilla Public License Version
00006  * 1.1 (the "License"); you may not use this file except in compliance with
00007  * the License. You may obtain a copy of the License at
00008  * http://www.mozilla.org/MPL/
00009  *
00010  * Software distributed under the License is distributed on an "AS IS" basis,
00011  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
00012  * for the specific language governing rights and limitations under the
00013  * License.
00014  *
00015  * The Original Code is Mozilla Communicator client code.
00016  *
00017  * The Initial Developer of the Original Code is
00018  * Netscape Communications Corporation.
00019  * Portions created by the Initial Developer are Copyright (C) 1998
00020  * the Initial Developer. All Rights Reserved.
00021  *
00022  * Contributor(s):
00023  *   Mats Palmgren <mats.palmgren@bredband.net>
00024  *
00025  * Alternatively, the contents of this file may be used under the terms of
00026  * either of the GNU General Public License Version 2 or later (the "GPL"),
00027  * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
00028  * in which case the provisions of the GPL or the LGPL are applicable instead
00029  * of those above. If you wish to allow use of your version of this file only
00030  * under the terms of either the GPL or the LGPL, and not to allow others to
00031  * use your version of this file under the terms of the MPL, indicate your
00032  * decision by deleting the provisions above and replace them with the notice
00033  * and other provisions required by the GPL or the LGPL. If you do not delete
00034  * the provisions above, a recipient may use your version of this file under
00035  * the terms of any one of the MPL, the GPL or the LGPL.
00036  *
00037  * ***** END LICENSE BLOCK ***** */
00038 #include <ctype.h>
00039 #include "nsCOMPtr.h"
00040 #include "nsTextTransformer.h"
00041 #include "nsContentUtils.h"
00042 #include "nsIContent.h"
00043 #include "nsIFrame.h"
00044 #include "nsITextContent.h"
00045 #include "nsStyleConsts.h"
00046 #include "nsILineBreaker.h"
00047 #include "nsIWordBreaker.h"
00048 #include "nsIServiceManager.h"
00049 #include "nsUnicharUtilCIID.h"
00050 #include "nsUnicharUtils.h"
00051 #include "nsICaseConversion.h"
00052 #include "prenv.h"
00053 #ifdef IBMBIDI
00054 #include "nsLayoutAtoms.h"
00055 #endif
00056 
00057 
00058 PRBool nsTextTransformer::sWordSelectListenerPrefChecked = PR_FALSE;
00059 PRBool nsTextTransformer::sWordSelectEatSpaceAfter = PR_FALSE;
00060 PRBool nsTextTransformer::sWordSelectStopAtPunctuation = PR_FALSE;
00061 static const char kWordSelectEatSpaceAfterPref[] = "layout.word_select.eat_space_to_next_word";
00062 static const char kWordSelectStopAtPunctuationPref[] = "layout.word_select.stop_at_punctuation";
00063 
00064 // static
00065 int
00066 nsTextTransformer::WordSelectPrefCallback(const char* aPref, void* aClosure)
00067 {
00068   sWordSelectEatSpaceAfter = nsContentUtils::GetBoolPref(kWordSelectEatSpaceAfterPref);
00069   sWordSelectStopAtPunctuation = nsContentUtils::GetBoolPref(kWordSelectStopAtPunctuationPref);
00070 
00071   return 0;
00072 }
00073 
00074 nsAutoTextBuffer::nsAutoTextBuffer()
00075   : mBuffer(mAutoBuffer),
00076     mBufferLen(NS_TEXT_TRANSFORMER_AUTO_WORD_BUF_SIZE)
00077 {
00078 }
00079 
00080 nsAutoTextBuffer::~nsAutoTextBuffer()
00081 {
00082   if (mBuffer && (mBuffer != mAutoBuffer)) {
00083     delete [] mBuffer;
00084   }
00085 }
00086 
00087 nsresult
00088 nsAutoTextBuffer::GrowBy(PRInt32 aAtLeast, PRBool aCopyToHead)
00089 {
00090   PRInt32 newSize = mBufferLen * 2;
00091   if (newSize < mBufferLen + aAtLeast) {
00092     newSize = mBufferLen + aAtLeast + 100;
00093   }
00094   return GrowTo(newSize, aCopyToHead);
00095 }
00096 
00097 nsresult
00098 nsAutoTextBuffer::GrowTo(PRInt32 aNewSize, PRBool aCopyToHead)
00099 {
00100   if (aNewSize > mBufferLen) {
00101     PRUnichar* newBuffer = new PRUnichar[aNewSize];
00102     if (!newBuffer) {
00103       return NS_ERROR_OUT_OF_MEMORY;
00104     }
00105     memcpy(&newBuffer[aCopyToHead ? 0 : mBufferLen],
00106            mBuffer, sizeof(PRUnichar) * mBufferLen);
00107     if (mBuffer != mAutoBuffer) {
00108       delete [] mBuffer;
00109     }
00110     mBuffer = newBuffer;
00111     mBufferLen = aNewSize;
00112   }
00113   return NS_OK;
00114 }
00115 
00116 //----------------------------------------------------------------------
00117 
00118 static NS_DEFINE_CID(kUnicharUtilCID, NS_UNICHARUTIL_CID);
00119 
00120 static nsICaseConversion* gCaseConv =  nsnull;
00121 
00122 nsresult
00123 nsTextTransformer::Initialize()
00124 {
00125   // read in our global word selection prefs
00126   if ( !sWordSelectListenerPrefChecked ) {
00127     sWordSelectListenerPrefChecked = PR_TRUE;
00128 
00129     sWordSelectEatSpaceAfter =
00130       nsContentUtils::GetBoolPref(kWordSelectEatSpaceAfterPref);
00131     sWordSelectStopAtPunctuation =
00132       nsContentUtils::GetBoolPref(kWordSelectStopAtPunctuationPref);
00133 
00134     nsContentUtils::RegisterPrefCallback(kWordSelectEatSpaceAfterPref,
00135                                          WordSelectPrefCallback, nsnull);
00136     nsContentUtils::RegisterPrefCallback(kWordSelectStopAtPunctuationPref,
00137                                          WordSelectPrefCallback, nsnull);
00138   }
00139 
00140   return NS_OK;
00141 }
00142 static nsresult EnsureCaseConv()
00143 {
00144   nsresult res = NS_OK;
00145   if (!gCaseConv) {
00146     res = CallGetService(kUnicharUtilCID, &gCaseConv);
00147     NS_ASSERTION( NS_SUCCEEDED(res), "cannot get UnicharUtil");
00148     NS_ASSERTION( gCaseConv != NULL, "cannot get UnicharUtil");
00149   }
00150   return res;
00151 }
00152 
00153 void
00154 nsTextTransformer::Shutdown()
00155 {
00156   nsContentUtils::UnregisterPrefCallback(kWordSelectEatSpaceAfterPref,
00157                                          WordSelectPrefCallback, nsnull);
00158   nsContentUtils::UnregisterPrefCallback(kWordSelectStopAtPunctuationPref,
00159                                          WordSelectPrefCallback, nsnull);
00160 
00161   NS_IF_RELEASE(gCaseConv);
00162 }
00163 
00164 
00165 #define MAX_UNIBYTE 127
00166 
00167 MOZ_DECL_CTOR_COUNTER(nsTextTransformer)
00168 
00169 nsTextTransformer::nsTextTransformer(nsILineBreaker* aLineBreaker,
00170                                      nsIWordBreaker* aWordBreaker,
00171                                      nsPresContext* aPresContext)
00172   : mFrag(nsnull),
00173     mOffset(0),
00174     mMode(eNormal),
00175     mLineBreaker(aLineBreaker),
00176     mWordBreaker(aWordBreaker),
00177     mBufferPos(0),
00178     mTextTransform(NS_STYLE_TEXT_TRANSFORM_NONE),
00179     mFlags(0)
00180 {
00181   MOZ_COUNT_CTOR(nsTextTransformer);
00182 
00183   mLanguageSpecificTransformType =
00184     aPresContext->LanguageSpecificTransformType();
00185 
00186 #ifdef IBMBIDI
00187   mPresContext = aPresContext;
00188 #endif
00189   if (aLineBreaker == nsnull && aWordBreaker == nsnull )
00190     NS_ASSERTION(0, "invalid creation of nsTextTransformer");
00191   
00192 #ifdef DEBUG
00193   static PRBool firstTime = PR_TRUE;
00194   if (firstTime) {
00195     firstTime = PR_FALSE;
00196     SelfTest(aLineBreaker, aWordBreaker, aPresContext);
00197   }
00198 #endif
00199 }
00200 
00201 nsTextTransformer::~nsTextTransformer()
00202 {
00203   MOZ_COUNT_DTOR(nsTextTransformer);
00204 }
00205 
00206 nsresult
00207 nsTextTransformer::Init(nsIFrame* aFrame,
00208                         nsIContent* aContent,
00209                         PRInt32 aStartingOffset,
00210                         PRBool aForceArabicShaping,
00211                         PRBool aLeaveAsAscii)
00212 {
00213   /*
00214    * If the document has Bidi content, check whether we need to do
00215    * Arabic shaping.
00216    *
00217    *  Does the frame contains Arabic characters
00218    *   (mCharType == eCharType_RightToLeftArabic)?
00219    *  Are we rendering character by character (aForceArabicShaping ==
00220    *   PR_TRUE)? If so, we always do our own Arabic shaping, even if
00221    *   the platform has native shaping support. Otherwise, we only do
00222    *   shaping if the platform has no shaping support.
00223    *
00224    *  We do numeric shaping in all Bidi documents.
00225    */
00226   if (mPresContext->BidiEnabled()) {
00227     mCharType = (nsCharType)NS_PTR_TO_INT32(mPresContext->PropertyTable()->GetProperty(aFrame, nsLayoutAtoms::charType));
00228     if (mCharType == eCharType_RightToLeftArabic) {
00229       if (aForceArabicShaping) {
00230         SetNeedsArabicShaping(PR_TRUE);
00231       }
00232       else {
00233         if (!mPresContext->IsBidiSystem()) {
00234           SetNeedsArabicShaping(PR_TRUE);
00235         }
00236       }
00237     }
00238     SetNeedsNumericShaping(PR_TRUE);
00239   }
00240 
00241   // Get the contents text content
00242   nsresult rv;
00243   nsCOMPtr<nsITextContent> tc = do_QueryInterface(aContent, &rv);
00244   if (tc.get()) {
00245     mFrag = tc->Text();
00246 
00247     // Sanitize aStartingOffset
00248     if (aStartingOffset < 0) {
00249       NS_WARNING("bad starting offset");
00250       aStartingOffset = 0;
00251     }
00252     else if (aStartingOffset > mFrag->GetLength()) {
00253       NS_WARNING("bad starting offset");
00254       aStartingOffset = mFrag->GetLength();
00255     }
00256     mOffset = aStartingOffset;
00257 
00258     // Get the frames text style information
00259     const nsStyleText* styleText = aFrame->GetStyleText();
00260     if (NS_STYLE_WHITESPACE_PRE == styleText->mWhiteSpace) {
00261       mMode = ePreformatted;
00262     }
00263     else if (NS_STYLE_WHITESPACE_MOZ_PRE_WRAP == styleText->mWhiteSpace) {
00264       mMode = ePreWrap;
00265     }
00266     mTextTransform = styleText->mTextTransform;
00267     
00268     if (aLeaveAsAscii) { // See if the text fragment is 1-byte text
00269       SetLeaveAsAscii(PR_TRUE);        
00270       // XXX Currently we only leave it as ascii for normal text and not for preformatted
00271       // or preformatted wrapped text or language specific transforms
00272       if (mFrag->Is2b() || (eNormal != mMode) ||
00273           (mLanguageSpecificTransformType !=
00274            eLanguageSpecificTransformType_None))
00275         // We don't step down from Unicode to ascii
00276         SetLeaveAsAscii(PR_FALSE);           
00277     } 
00278     else 
00279       SetLeaveAsAscii(PR_FALSE);
00280   }
00281   return rv;
00282 }
00283 
00284 //----------------------------------------------------------------------
00285 
00286 // wordlen==1, contentlen=newOffset-currentOffset, isWhitespace=t
00287 PRInt32
00288 nsTextTransformer::ScanNormalWhiteSpace_F(PRInt32 aFragLen)
00289 {
00290   const nsTextFragment* frag = mFrag;
00291   PRInt32 offset = mOffset;
00292 
00293   for (; offset < aFragLen; offset++) {
00294     PRUnichar ch = frag->CharAt(offset);
00295     if (!XP_IS_SPACE(ch)) {
00296       // If character is not discardable then stop looping, otherwise
00297       // let the discarded character collapse with the other spaces.
00298       if (!IS_DISCARDED(ch)) {
00299         break;
00300       }
00301     }
00302   }
00303 
00304   // Make sure we have enough room in the transform buffer
00305   if (mBufferPos >= mTransformBuf.mBufferLen) {
00306     mTransformBuf.GrowBy(128);
00307   }
00308 
00309   if (TransformedTextIsAscii()) {
00310     unsigned char*  bp = (unsigned char*)mTransformBuf.mBuffer;
00311     bp[mBufferPos++] = ' ';
00312   } else {
00313     mTransformBuf.mBuffer[mBufferPos++] = PRUnichar(' ');
00314   }
00315   return offset;
00316 }
00317   
00318 void
00319 nsTextTransformer::ConvertTransformedTextToUnicode()
00320 {
00321   // Go backwards over the characters and convert them.
00322   PRInt32         lastChar = mBufferPos - 1;
00323   unsigned char*  cp1 = (unsigned char*)mTransformBuf.mBuffer + lastChar;
00324   PRUnichar*      cp2 = mTransformBuf.mBuffer + lastChar;
00325   
00326   NS_ASSERTION(mTransformBuf.mBufferLen >= mBufferPos,
00327                "transform buffer is too small");
00328   for (PRInt32 count = mBufferPos; count > 0; count--) {
00329     *cp2-- = PRUnichar(*cp1--);
00330   }
00331 }
00332 
00333 // wordlen==*aWordLen, contentlen=newOffset-currentOffset, isWhitespace=f
00334 PRInt32
00335 nsTextTransformer::ScanNormalAsciiText_F(PRInt32  aFragLen,
00336                                          PRInt32* aWordLen,
00337                                          PRBool*  aWasTransformed)
00338 {
00339   const nsTextFragment* frag = mFrag;
00340   PRInt32 offset = mOffset;
00341   PRInt32 prevBufferPos = mBufferPos;
00342   const unsigned char* cp = (const unsigned char*)frag->Get1b() + offset;
00343   union {
00344     unsigned char* bp1;
00345     PRUnichar* bp2;
00346   };
00347   bp2 = mTransformBuf.GetBuffer();
00348   if (TransformedTextIsAscii()) {
00349     bp1 += mBufferPos;
00350   } else {
00351     bp2 += mBufferPos;
00352   }
00353 
00354   for (; offset < aFragLen; offset++) {
00355     unsigned char ch = *cp++;
00356     if (XP_IS_SPACE(ch)) {
00357       break;
00358     }
00359     if (CH_NBSP == ch) {
00360       ch = ' ';
00361       *aWasTransformed = PR_TRUE;
00362     }
00363     else if (IS_DISCARDED(ch)) {
00364       // Strip discarded characters from the transformed output
00365       continue;
00366     }
00367     if (ch > MAX_UNIBYTE) {
00368       // The text has a multibyte character so we can no longer leave the
00369       // text as ascii text
00370       SetHasMultibyte(PR_TRUE);        
00371               
00372       if (TransformedTextIsAscii()) { 
00373         SetTransformedTextIsAscii(PR_FALSE);
00374         *aWasTransformed = PR_TRUE;
00375 
00376         // Transform any existing ascii text to Unicode
00377         if (mBufferPos > 0) {
00378           ConvertTransformedTextToUnicode();
00379           bp2 = mTransformBuf.GetBuffer() + mBufferPos;
00380         }
00381       }
00382     }
00383     if (mBufferPos >= mTransformBuf.mBufferLen) {
00384       nsresult rv = mTransformBuf.GrowBy(128);
00385       if (NS_FAILED(rv)) {
00386         // If we run out of space then just truncate the text
00387         break;
00388       }
00389       bp2 = mTransformBuf.GetBuffer();
00390       if (TransformedTextIsAscii()) {
00391         bp1 += mBufferPos;
00392       } else {
00393         bp2 += mBufferPos;
00394       }
00395     }
00396     if (TransformedTextIsAscii()) {
00397       *bp1++ = ch;
00398     } else {
00399       *bp2++ = PRUnichar(ch);
00400     }
00401     mBufferPos++;
00402   }
00403 
00404   *aWordLen = mBufferPos - prevBufferPos;
00405   return offset;
00406 }
00407 
00408 PRInt32
00409 nsTextTransformer::ScanNormalAsciiText_F_ForWordBreak(PRInt32  aFragLen,
00410                                                       PRInt32* aWordLen,
00411                                                       PRBool*  aWasTransformed,
00412                                                       PRBool   aIsKeyboardSelect)
00413 {
00414   const nsTextFragment* frag = mFrag;
00415   PRInt32 offset = mOffset;
00416   PRInt32 prevBufferPos = mBufferPos;
00417   PRBool breakAfterThis = PR_FALSE;
00418   const unsigned char* cp = (const unsigned char*)frag->Get1b() + offset;
00419   union {
00420     unsigned char* bp1;
00421     PRUnichar* bp2;
00422   };
00423   bp2 = mTransformBuf.GetBuffer();
00424   if (TransformedTextIsAscii()) {
00425     bp1 += mBufferPos;
00426   } else {
00427     bp2 += mBufferPos;
00428   }
00429   PRBool readingAlphaNumeric = PR_TRUE; //only used in sWordSelectStopAtPunctuation
00430 
00431   // We must know if we are starting in alpha numerics.
00432   // Treat high bit chars as alphanumeric, otherwise we get stuck on accented letters
00433   // We can't trust isalnum() results for isalnum()
00434   // Therefore we don't stop at non-ascii (high bit) punctuation,
00435   // which is just fine. The punctuation we care about is low bit.
00436   if (sWordSelectStopAtPunctuation && offset < aFragLen)
00437     readingAlphaNumeric = isalnum((unsigned char)*cp) || !IS_ASCII_CHAR(*cp);
00438   
00439   for (; offset < aFragLen && !breakAfterThis; offset++) {
00440     unsigned char ch = *cp++;
00441     if (CH_NBSP == ch) {
00442       ch = ' ';
00443       *aWasTransformed = PR_TRUE;
00444       if (offset == mOffset)
00445         breakAfterThis = PR_TRUE;
00446       else
00447         break;
00448     }
00449     else if (XP_IS_SPACE(ch)) {
00450       break;
00451     }
00452     else if (sWordSelectStopAtPunctuation && 
00453              readingAlphaNumeric && !isalnum(ch) && IS_ASCII_CHAR(ch)) {
00454       if (!aIsKeyboardSelect)
00455         break;
00456       // For keyboard move-by-word, need to pass by at least
00457       // one alphanumeric char before stopping at punct
00458       readingAlphaNumeric = PR_FALSE;
00459     }
00460     else if (sWordSelectStopAtPunctuation && 
00461             !readingAlphaNumeric && (isalnum(ch) || !IS_ASCII_CHAR(ch))) {
00462       // On some platforms, punctuation breaks for word selection
00463       break;
00464     }
00465     else if (IS_DISCARDED(ch)) {
00466       // Strip discarded characters from the transformed output
00467       continue;
00468     }
00469     if (ch > MAX_UNIBYTE) {
00470       // The text has a multibyte character so we can no longer leave the
00471       // text as ascii text
00472       SetHasMultibyte(PR_TRUE);
00473 
00474       if (TransformedTextIsAscii()) {
00475         SetTransformedTextIsAscii(PR_FALSE);
00476         *aWasTransformed = PR_TRUE;
00477 
00478         // Transform any existing ascii text to Unicode
00479         if (mBufferPos > 0) {
00480           ConvertTransformedTextToUnicode();
00481           bp2 = mTransformBuf.GetBuffer() + mBufferPos;
00482         }
00483       }
00484     }
00485     if (mBufferPos >= mTransformBuf.mBufferLen) {
00486       nsresult rv = mTransformBuf.GrowBy(128);
00487       if (NS_FAILED(rv)) {
00488         // If we run out of space then just truncate the text
00489         break;
00490       }
00491       bp2 = mTransformBuf.GetBuffer();
00492       if (TransformedTextIsAscii()) {
00493         bp1 += mBufferPos;
00494       } else {
00495         bp2 += mBufferPos;
00496       }
00497     }
00498     if (TransformedTextIsAscii()) {
00499       *bp1++ = ch;
00500     } else {
00501       *bp2++ = PRUnichar(ch);
00502     }
00503     mBufferPos++;
00504   }
00505 
00506   *aWordLen = mBufferPos - prevBufferPos;
00507   return offset;
00508 }
00509 
00510 
00511 // wordlen==*aWordLen, contentlen=newOffset-currentOffset, isWhitespace=f
00512 PRInt32
00513 nsTextTransformer::ScanNormalUnicodeText_F(PRInt32  aFragLen,
00514                                            PRBool   aForLineBreak,
00515                                            PRInt32* aWordLen,
00516                                            PRBool*  aWasTransformed)
00517 {
00518   const nsTextFragment* frag = mFrag;
00519   const PRUnichar* cp0 = frag->Get2b();
00520   PRInt32 offset = mOffset;
00521 
00522   PRUnichar firstChar = frag->CharAt(offset++);
00523 
00524 #ifdef IBMBIDI
00525   // Need to strip BIDI controls even when those are 'firstChars'.
00526   // This doesn't seem to produce bug 14280 (or similar bugs).
00527   while (offset < aFragLen && IS_BIDI_CONTROL(firstChar) ) {
00528     firstChar = frag->CharAt(offset++);
00529   }
00530 #endif // IBMBIDI
00531 
00532   if (firstChar > MAX_UNIBYTE) SetHasMultibyte(PR_TRUE);
00533 
00534   // Only evaluate complex breaking logic if there are more characters
00535   // beyond the first to look at.
00536   PRInt32 numChars = 1;
00537   if (offset < aFragLen) {
00538     const PRUnichar* cp = cp0 + offset;
00539     PRBool breakBetween = PR_FALSE;
00540     if (aForLineBreak) {
00541       mLineBreaker->BreakInBetween(&firstChar, 1, cp, (aFragLen-offset), &breakBetween);
00542     }
00543     else {
00544       mWordBreaker->BreakInBetween(&firstChar, 1, cp, (aFragLen-offset), &breakBetween);
00545     }
00546 
00547     // don't transform the first character until after BreakInBetween is called
00548     // Kipp originally did this at the top of the function, which was too early.
00549     // see bug 14280
00550     if (CH_NBSP == firstChar) {
00551       firstChar = ' ';
00552       *aWasTransformed = PR_TRUE;
00553     }
00554     nsresult rv = mTransformBuf.GrowTo(mBufferPos + 1);
00555     if (NS_FAILED(rv)) {
00556               *aWordLen = 0;
00557               return offset - 1;
00558        }
00559 
00560     mTransformBuf.mBuffer[mBufferPos++] = firstChar;
00561 
00562     if (!breakBetween) {
00563       // Find next position
00564       PRBool tryNextFrag;
00565       PRUint32 next;
00566       if (aForLineBreak) {
00567         mLineBreaker->Next(cp0, aFragLen, offset, &next, &tryNextFrag);
00568       }
00569       else {
00570         mWordBreaker->NextWord(cp0, aFragLen, offset, &next, &tryNextFrag);
00571       }
00572       numChars = (PRInt32) (next - (PRUint32) offset) + 1;
00573 
00574       // Since we know the number of characters we're adding grow the buffer
00575       // now before we start copying
00576       nsresult rv = mTransformBuf.GrowTo(mBufferPos + numChars);
00577       if (NS_FAILED(rv)) {
00578         numChars = mTransformBuf.GetBufferLength() - mBufferPos;
00579       }
00580 
00581       offset += numChars - 1;
00582 
00583       // 1. convert nbsp into space
00584       // 2. check for discarded characters
00585       // 3. check mHasMultibyte flag
00586       // 4. copy buffer
00587       PRUnichar* bp = &mTransformBuf.mBuffer[mBufferPos];
00588       const PRUnichar* end = cp + numChars - 1;
00589       while (cp < end) {
00590         PRUnichar ch = *cp++;
00591         if (CH_NBSP == ch) {
00592           ch = ' ';
00593         }
00594         else if (IS_DISCARDED(ch) || (ch == 0x0a) || (ch == 0x0d)) {
00595           // Strip discarded characters from the transformed output
00596           numChars--;
00597           continue;
00598         }
00599         if (ch > MAX_UNIBYTE) SetHasMultibyte(PR_TRUE);
00600         *bp++ = ch;
00601         mBufferPos++;
00602       }
00603     }
00604   }
00605   else 
00606   { // transform the first character
00607     // we do this here, rather than at the top of the function (like Kipp originally had it)
00608     // because if we must call BreakInBetween, then we must do so before the transformation
00609     // this is the case where BreakInBetween does not need to be called at all.
00610     // see bug 14280
00611     if (CH_NBSP == firstChar) {
00612       firstChar = ' ';
00613       *aWasTransformed = PR_TRUE;
00614     }
00615     nsresult rv = mTransformBuf.GrowTo(mBufferPos + 1);
00616     if (NS_FAILED(rv)) {
00617               *aWordLen = 0;
00618               return offset - 1;
00619        }
00620     mTransformBuf.mBuffer[mBufferPos++] = firstChar;
00621   }
00622 
00623   *aWordLen = numChars;
00624   return offset;
00625 }
00626 
00627 // wordlen==*aWordLen, contentlen=newOffset-currentOffset, isWhitespace=t
00628 PRInt32
00629 nsTextTransformer::ScanPreWrapWhiteSpace_F(PRInt32 aFragLen, PRInt32* aWordLen)
00630 {
00631   const nsTextFragment* frag = mFrag;
00632   PRInt32 offset = mOffset;
00633   PRUnichar* bp = mTransformBuf.GetBuffer() + mBufferPos;
00634   PRUnichar* endbp = mTransformBuf.GetBufferEnd();
00635   PRInt32 prevBufferPos = mBufferPos;
00636 
00637   for (; offset < aFragLen; offset++) {
00638     // This function is used for both Unicode and ascii strings so don't
00639     // make any assumptions about what kind of data it is
00640     PRUnichar ch = frag->CharAt(offset);
00641     if (!XP_IS_SPACE(ch) || (ch == '\t') || (ch == '\n')) {
00642       if (IS_DISCARDED(ch)) {
00643         // Keep looping if this is a discarded character
00644         continue;
00645       }
00646       break;
00647     }
00648     if (bp == endbp) {
00649       PRInt32 oldLength = bp - mTransformBuf.GetBuffer();
00650       nsresult rv = mTransformBuf.GrowBy(1000);
00651       if (NS_FAILED(rv)) {
00652         // If we run out of space (unlikely) then just chop the input
00653         break;
00654       }
00655       bp = mTransformBuf.GetBuffer() + oldLength;
00656       endbp = mTransformBuf.GetBufferEnd();
00657     }
00658     *bp++ = ' ';
00659     mBufferPos++;
00660   }
00661 
00662   *aWordLen = mBufferPos - prevBufferPos;
00663   return offset;
00664 }
00665 
00666 // wordlen==*aWordLen, contentlen=newOffset-currentOffset, isWhitespace=f
00667 PRInt32
00668 nsTextTransformer::ScanPreData_F(PRInt32  aFragLen,
00669                                  PRInt32* aWordLen,
00670                                  PRBool*  aWasTransformed)
00671 {
00672   const nsTextFragment* frag = mFrag;
00673   PRInt32 offset = mOffset;
00674   PRUnichar* bp = mTransformBuf.GetBuffer() + mBufferPos;
00675   PRUnichar* endbp = mTransformBuf.GetBufferEnd();
00676   PRInt32 prevBufferPos = mBufferPos;
00677 
00678   for (; offset < aFragLen; offset++) {
00679     // This function is used for both Unicode and ascii strings so don't
00680     // make any assumptions about what kind of data it is
00681     PRUnichar ch = frag->CharAt(offset);
00682     if ((ch == '\t') || (ch == '\n')) {
00683       break;
00684     }
00685     if (CH_NBSP == ch) {
00686       ch = ' ';
00687       *aWasTransformed = PR_TRUE;
00688     }
00689     else if (IS_DISCARDED(ch)) {
00690       continue;
00691     }
00692     if (ch > MAX_UNIBYTE) SetHasMultibyte(PR_TRUE);
00693     if (bp == endbp) {
00694       PRInt32 oldLength = bp - mTransformBuf.GetBuffer();
00695       nsresult rv = mTransformBuf.GrowBy(1000);
00696       if (NS_FAILED(rv)) {
00697         // If we run out of space (unlikely) then just chop the input
00698         break;
00699       }
00700       bp = mTransformBuf.GetBuffer() + oldLength;
00701       endbp = mTransformBuf.GetBufferEnd();
00702     }
00703     *bp++ = ch;
00704     mBufferPos++;
00705   }
00706 
00707   *aWordLen = mBufferPos - prevBufferPos;
00708   return offset;
00709 }
00710 
00711 // wordlen==*aWordLen, contentlen=newOffset-currentOffset, isWhitespace=f
00712 PRInt32
00713 nsTextTransformer::ScanPreAsciiData_F(PRInt32  aFragLen,
00714                                       PRInt32* aWordLen,
00715                                       PRBool*  aWasTransformed)
00716 {
00717   const nsTextFragment* frag = mFrag;
00718   PRUnichar* bp = mTransformBuf.GetBuffer() + mBufferPos;
00719   PRUnichar* endbp = mTransformBuf.GetBufferEnd();
00720   const unsigned char* cp = (const unsigned char*) frag->Get1b();
00721   const unsigned char* end = cp + aFragLen;
00722   PRInt32 prevBufferPos = mBufferPos;
00723   cp += mOffset;
00724 
00725   while (cp < end) {
00726     PRUnichar ch = (PRUnichar) *cp++;
00727     if ((ch == '\t') || (ch == '\n')) {
00728       cp--;
00729       break;
00730     }
00731     if (CH_NBSP == ch) {
00732       ch = ' ';
00733       *aWasTransformed = PR_TRUE;
00734     }
00735     else if (IS_DISCARDED(ch)) {
00736       continue;
00737     }
00738     if (ch > MAX_UNIBYTE) SetHasMultibyte(PR_TRUE);
00739     if (bp == endbp) {
00740       PRInt32 oldLength = bp - mTransformBuf.GetBuffer();
00741       nsresult rv = mTransformBuf.GrowBy(1000);
00742       if (NS_FAILED(rv)) {
00743         // If we run out of space (unlikely) then just chop the input
00744         break;
00745       }
00746       bp = mTransformBuf.GetBuffer() + oldLength;
00747       endbp = mTransformBuf.GetBufferEnd();
00748     }
00749     *bp++ = ch;
00750     mBufferPos++;
00751   }
00752 
00753   *aWordLen = mBufferPos - prevBufferPos;
00754   return cp - ((const unsigned char*)frag->Get1b());
00755 }
00756 
00757 //----------------------------------------
00758 
00759 static void
00760 AsciiToLowerCase(unsigned char* aText, PRInt32 aWordLen)
00761 {
00762   while (aWordLen-- > 0) {
00763     *aText = tolower(*aText);
00764     aText++;
00765   }
00766 }
00767 
00768 static void
00769 AsciiToUpperCase(unsigned char* aText, PRInt32 aWordLen)
00770 {
00771   while (aWordLen-- > 0) {
00772     *aText = toupper(*aText);
00773     aText++;
00774   }
00775 }
00776 
00777 #define kSzlig 0x00DF
00778 static PRInt32 CountGermanSzlig(const PRUnichar* aText, PRInt32 len)
00779 {
00780   PRInt32 i,cnt;
00781   for(i=0,cnt=0; i<len; i++, aText++)
00782   {
00783      if(kSzlig == *aText)
00784          cnt++;
00785   }
00786   return cnt;
00787 }
00788 static void ReplaceGermanSzligToSS(PRUnichar* aText, PRInt32 len, PRInt32 szCnt)
00789 {
00790   PRUnichar *src, *dest;
00791   src = aText + len - 1;
00792   dest = src + szCnt;
00793   while( (src!=dest) && (src >= aText) )
00794   {
00795       if(kSzlig == *src )
00796       {     
00797         *dest-- = PRUnichar('S');
00798         *dest-- = PRUnichar('S');
00799         src--;
00800       } else {
00801         *dest-- = *src--;
00802       }
00803   }
00804 }
00805 
00806 void
00807 nsTextTransformer::LanguageSpecificTransform(PRUnichar* aText, PRInt32 aLen,
00808                                              PRBool* aWasTransformed)
00809 {
00810   if (mLanguageSpecificTransformType ==
00811       eLanguageSpecificTransformType_Japanese) {
00812     for (PRInt32 i = 0; i < aLen; i++) {
00813       if (aText[i] == 0x5C) { // BACKSLASH
00814         aText[i] = 0xA5; // YEN SIGN
00815         SetHasMultibyte(PR_TRUE);        
00816         *aWasTransformed = PR_TRUE;
00817       }
00818 #if 0
00819       /*
00820        * We considered doing this, but since some systems may not have fonts
00821        * with this OVERLINE glyph, we decided not to do this.
00822        */
00823       else if (aText[i] == 0x7E) { // TILDE
00824         aText[i] = 0x203E; // OVERLINE
00825         SetHasMultibyte(PR_TRUE);        
00826         *aWasTransformed = PR_TRUE;
00827       }
00828 #endif
00829     }
00830   }
00831   /* we once do transformation for Korean, but later decide to remove it */
00832   /* see bug 88050 for more information */
00833 }
00834 
00835 PRUnichar*
00836 nsTextTransformer::GetNextWord(PRBool aInWord,
00837                                PRInt32* aWordLenResult,
00838                                PRInt32* aContentLenResult,
00839                                PRBool* aIsWhiteSpaceResult,
00840                                PRBool* aWasTransformed,
00841                                PRBool aResetTransformBuf,
00842                                PRBool aForLineBreak,
00843                                PRBool aIsKeyboardSelect)
00844 {
00845   const nsTextFragment* frag = mFrag;
00846   PRInt32 fragLen = frag->GetLength();
00847   if (*aWordLenResult > 0 && *aWordLenResult < fragLen) {
00848     fragLen = *aWordLenResult;
00849   }
00850 
00851   PRInt32 offset = mOffset;
00852   PRInt32 wordLen = 0;
00853   PRBool isWhitespace = PR_FALSE;
00854   PRUnichar* result = nsnull;
00855   PRInt32 prevBufferPos;
00856   PRBool skippedWhitespace = PR_FALSE;
00857 
00858   // Initialize OUT parameter
00859   *aWasTransformed = PR_FALSE;
00860 
00861   // See if we should reset the current buffer position back to the
00862   // beginning of the buffer
00863   if (aResetTransformBuf) {
00864     mBufferPos = 0;
00865     SetTransformedTextIsAscii(LeaveAsAscii());
00866   }
00867   prevBufferPos = mBufferPos;
00868 
00869   // Fix word breaking problem w/ PREFORMAT and PREWRAP
00870   // for word breaking, we should really go to the normal code
00871   if((! aForLineBreak) && (eNormal != mMode))
00872      mMode = eNormal;
00873 
00874   while (offset < fragLen) {
00875     PRUnichar firstChar = frag->CharAt(offset);
00876 
00877     // Eat up any discarded characters before dispatching
00878     if (IS_DISCARDED(firstChar)) {
00879       offset++;
00880       continue;
00881     }
00882 
00883     switch (mMode) {
00884       default:
00885       case eNormal:
00886         if (XP_IS_SPACE(firstChar)) {
00887           offset = ScanNormalWhiteSpace_F(fragLen);
00888 
00889           // if this is just a '\n', and characters before and after it are CJK chars, 
00890           // we will skip this one.
00891           if (firstChar == '\n' && 
00892               offset - mOffset == 1 && 
00893               mOffset > 0 &&
00894               offset < fragLen) 
00895           {
00896             PRUnichar lastChar = frag->CharAt(mOffset - 1);
00897             PRUnichar nextChar = frag->CharAt(offset);
00898             if (IS_CJ_CHAR(lastChar) && IS_CJ_CHAR(nextChar)) {
00899               skippedWhitespace = PR_TRUE;
00900               --mBufferPos;
00901               mOffset = offset;
00902               continue;            }
00903           }
00904           if (firstChar != ' ') {
00905             *aWasTransformed = PR_TRUE;
00906           }
00907           wordLen = 1;
00908           isWhitespace = PR_TRUE;
00909         }
00910         else if (CH_NBSP == firstChar && !aForLineBreak) {
00911           wordLen = 1;
00912           isWhitespace = PR_TRUE;
00913           *aWasTransformed = PR_TRUE;
00914 
00915           // Make sure we have enough room in the transform buffer
00916           if (mBufferPos >= mTransformBuf.mBufferLen) {
00917              mTransformBuf.GrowBy(128);
00918           }
00919 
00920           offset++;
00921           if (TransformedTextIsAscii()) {
00922             ((unsigned char*)mTransformBuf.mBuffer)[mBufferPos++] = ' ';
00923           } else {
00924             mTransformBuf.mBuffer[mBufferPos++] = PRUnichar(' ');
00925           }
00926         }
00927         else if (frag->Is2b()) {
00928 #ifdef IBMBIDI
00929           wordLen = *aWordLenResult;
00930 #endif
00931           offset = ScanNormalUnicodeText_F(fragLen, aForLineBreak, &wordLen, aWasTransformed);
00932         }
00933         else {
00934           if (!aForLineBreak)
00935             offset = ScanNormalAsciiText_F_ForWordBreak(fragLen, &wordLen, 
00936                                                         aWasTransformed, 
00937                                                         aIsKeyboardSelect);
00938           else
00939             offset = ScanNormalAsciiText_F(fragLen, &wordLen, aWasTransformed);
00940         }
00941         break;
00942 
00943       case ePreformatted:
00944         if (('\n' == firstChar) || ('\t' == firstChar)) {
00945           mTransformBuf.mBuffer[mBufferPos++] = firstChar;
00946           offset++;
00947           wordLen = 1;
00948           isWhitespace = PR_TRUE;
00949         }
00950         else if (frag->Is2b()) {
00951           offset = ScanPreData_F(fragLen, &wordLen, aWasTransformed);
00952         }
00953         else {
00954           offset = ScanPreAsciiData_F(fragLen, &wordLen, aWasTransformed);
00955         }
00956         break;
00957 
00958       case ePreWrap:
00959         if (XP_IS_SPACE(firstChar)) {
00960           if (('\n' == firstChar) || ('\t' == firstChar)) {
00961             mTransformBuf.mBuffer[mBufferPos++] = firstChar;
00962             offset++;
00963             wordLen = 1;
00964           }
00965           else {
00966             offset = ScanPreWrapWhiteSpace_F(fragLen, &wordLen);
00967           }
00968           isWhitespace = PR_TRUE;
00969         }
00970         else if (frag->Is2b()) {
00971 #ifdef IBMBIDI
00972           wordLen = *aWordLenResult;
00973 #endif
00974           offset = ScanNormalUnicodeText_F(fragLen, aForLineBreak, &wordLen, aWasTransformed);
00975         }
00976         else {
00977           if (!aForLineBreak)
00978             offset = ScanNormalAsciiText_F_ForWordBreak(fragLen, &wordLen, aWasTransformed, 
00979                                                         aIsKeyboardSelect);
00980           else
00981             offset = ScanNormalAsciiText_F(fragLen, &wordLen, aWasTransformed);
00982         }
00983         break;
00984     }
00985 
00986     if (TransformedTextIsAscii()) {
00987       unsigned char* wordPtr = (unsigned char*)mTransformBuf.mBuffer + prevBufferPos;
00988       
00989       if (!isWhitespace) {
00990         switch (mTextTransform) {
00991         case NS_STYLE_TEXT_TRANSFORM_CAPITALIZE:
00992           if (!aInWord)
00993             *wordPtr = toupper(*wordPtr);
00994           break;
00995         case NS_STYLE_TEXT_TRANSFORM_LOWERCASE:
00996           AsciiToLowerCase(wordPtr, wordLen);
00997           break;
00998         case NS_STYLE_TEXT_TRANSFORM_UPPERCASE:
00999           AsciiToUpperCase(wordPtr, wordLen);
01000           break;
01001         }
01002         NS_ASSERTION(mLanguageSpecificTransformType ==
01003                      eLanguageSpecificTransformType_None,
01004                      "should not be ASCII for language specific transforms");
01005       }
01006       result = (PRUnichar*)wordPtr;
01007 
01008     } else {
01009       result = &mTransformBuf.mBuffer[prevBufferPos];
01010 
01011       if (!isWhitespace) {
01012         switch (mTextTransform) {
01013         case NS_STYLE_TEXT_TRANSFORM_CAPITALIZE:
01014           if(NS_SUCCEEDED(EnsureCaseConv()))
01015             gCaseConv->ToTitle(result, result, wordLen, !aInWord);
01016           // if the first character is szlig
01017           if(kSzlig == *result)
01018           {
01019               if (mBufferPos + 1 >= mTransformBuf.mBufferLen) {
01020                 mTransformBuf.GrowBy(128);
01021                 result = &mTransformBuf.mBuffer[prevBufferPos];
01022               }
01023               PRUnichar* src = result +  mBufferPos-prevBufferPos;
01024               while(src>result) 
01025               {
01026                 *src = *(src-1);
01027                 src--;
01028               }
01029               result[0] = PRUnichar('S');
01030               result[1] = PRUnichar('S');
01031               wordLen++;
01032           }
01033           break;
01034         case NS_STYLE_TEXT_TRANSFORM_LOWERCASE:
01035           if(NS_SUCCEEDED(EnsureCaseConv()))
01036             gCaseConv->ToLower(result, result, wordLen);
01037           break;
01038         case NS_STYLE_TEXT_TRANSFORM_UPPERCASE:
01039           {
01040             if(NS_SUCCEEDED(EnsureCaseConv()))
01041               gCaseConv->ToUpper(result, result, wordLen);
01042 
01043             // first we search for German Szlig
01044             PRInt32 szligCnt = CountGermanSzlig(result, wordLen);
01045             if(szligCnt > 0) {
01046               // Make sure we have enough room in the transform buffer
01047               if (mBufferPos + szligCnt >= mTransformBuf.mBufferLen)
01048               {
01049                 mTransformBuf.GrowBy(128);
01050                 result = &mTransformBuf.mBuffer[prevBufferPos];
01051               }
01052               ReplaceGermanSzligToSS(result, mBufferPos-prevBufferPos, szligCnt);
01053               wordLen += szligCnt;
01054             }
01055           }
01056           break;
01057         }
01058         if (mLanguageSpecificTransformType !=
01059             eLanguageSpecificTransformType_None) {
01060           LanguageSpecificTransform(result, wordLen, aWasTransformed);
01061         }
01062         if (NeedsArabicShaping()) {
01063           DoArabicShaping(result, wordLen, aWasTransformed);
01064         }
01065         if (NeedsNumericShaping()) {
01066           DoNumericShaping(result, wordLen, aWasTransformed);
01067         }
01068       }
01069     }
01070 
01071     break;
01072   }
01073 
01074   *aIsWhiteSpaceResult = isWhitespace;
01075   *aWordLenResult = wordLen;
01076   *aContentLenResult = offset - mOffset;
01077 
01078   // we need to adjust the length if a '\n' has been skip between CJK chars
01079   *aContentLenResult += (skippedWhitespace ? 1 : 0);
01080 
01081   // If the word length doesn't match the content length then we transformed
01082   // the text
01083   if ((mTextTransform != NS_STYLE_TEXT_TRANSFORM_NONE) ||
01084       (*aWordLenResult != *aContentLenResult)) {
01085     *aWasTransformed = PR_TRUE;
01086     mBufferPos = prevBufferPos + *aWordLenResult;
01087   }
01088 
01089   mOffset = offset;
01090 
01091   NS_ASSERTION(mBufferPos == prevBufferPos + *aWordLenResult, "internal error");
01092   return result;
01093 }
01094 
01095 //----------------------------------------------------------------------
01096 
01097 // wordlen==1, contentlen=newOffset-currentOffset, isWhitespace=t
01098 PRInt32
01099 nsTextTransformer::ScanNormalWhiteSpace_B()
01100 {
01101   const nsTextFragment* frag = mFrag;
01102   PRInt32 offset = mOffset;
01103 
01104   while (--offset >= 0) {
01105     PRUnichar ch = frag->CharAt(offset);
01106     if (!XP_IS_SPACE(ch)) {
01107       // If character is not discardable then stop looping, otherwise
01108       // let the discarded character collapse with the other spaces.
01109       if (!IS_DISCARDED(ch)) {
01110         break;
01111       }
01112     }
01113   }
01114 
01115   mTransformBuf.mBuffer[mTransformBuf.mBufferLen - 1] = ' ';
01116   return offset;
01117 }
01118 
01119 // wordlen==*aWordLen, contentlen=newOffset-currentOffset, isWhitespace=f
01120 PRInt32
01121 nsTextTransformer::ScanNormalAsciiText_B(PRInt32* aWordLen, PRBool aIsKeyboardSelect)
01122 {
01123   const nsTextFragment* frag = mFrag;
01124   PRInt32 offset = mOffset;
01125   PRUnichar* bp = mTransformBuf.GetBufferEnd();
01126   PRUnichar* startbp = mTransformBuf.GetBuffer();
01127 
01128   PRUnichar ch = frag->CharAt(offset - 1);
01129   // Treat high bit chars as alphanumeric, otherwise we get stuck on accented letters
01130   // We can't trust isalnum() results for isalnum()
01131   // Therefore we don't stop at non-ascii (high bit) punctuation,
01132   // which is just fine. The punctuation we care about is low bit.
01133   PRBool readingAlphaNumeric = isalnum(ch) || !IS_ASCII_CHAR(ch);
01134 
01135   while (--offset >= 0) {
01136     PRUnichar ch = frag->CharAt(offset);
01137     if (CH_NBSP == ch) {
01138       ch = ' ';
01139     }
01140     if (XP_IS_SPACE(ch)) {
01141       break;
01142     }
01143     else if (IS_DISCARDED(ch)) {
01144       continue;
01145     } 
01146     else if (sWordSelectStopAtPunctuation && readingAlphaNumeric && 
01147              !isalnum(ch) && IS_ASCII_CHAR(ch)) {
01148       // Break on ascii punctuation
01149       break;
01150     }
01151     else if (sWordSelectStopAtPunctuation && !readingAlphaNumeric &&
01152              (isalnum(ch) || !IS_ASCII_CHAR(ch))) {
01153       if (!aIsKeyboardSelect)
01154         break;
01155       readingAlphaNumeric = PR_TRUE;
01156     }
01157     
01158     if (ch > MAX_UNIBYTE) SetHasMultibyte(PR_TRUE);
01159     if (bp == startbp) {
01160       PRInt32 oldLength = mTransformBuf.mBufferLen;
01161       nsresult rv = mTransformBuf.GrowBy(1000);
01162       if (NS_FAILED(rv)) {
01163         // If we run out of space (unlikely) then just chop the input
01164         break;
01165       }
01166       bp = mTransformBuf.GetBufferEnd() - oldLength;
01167       startbp = mTransformBuf.GetBuffer();
01168     }
01169     *--bp = ch;
01170   }
01171 
01172   *aWordLen = mTransformBuf.GetBufferEnd() - bp;
01173   return offset;
01174 }
01175 
01176 // wordlen==*aWordLen, contentlen=newOffset-currentOffset, isWhitespace=f
01177 PRInt32
01178 nsTextTransformer::ScanNormalUnicodeText_B(PRBool aForLineBreak,
01179                                            PRInt32* aWordLen)
01180 {
01181   const nsTextFragment* frag = mFrag;
01182   const PRUnichar* cp0 = frag->Get2b();
01183   PRInt32 offset = mOffset - 1;
01184 
01185   PRUnichar firstChar = frag->CharAt(offset);
01186 
01187 #ifdef IBMBIDI
01188   PRInt32 limit = (*aWordLen > 0) ? *aWordLen : 0;
01189   
01190   while (offset > limit && IS_BIDI_CONTROL(firstChar) ) {
01191     firstChar = frag->CharAt(--offset);
01192   }
01193 #endif
01194 
01195   mTransformBuf.mBuffer[mTransformBuf.mBufferLen - 1] = firstChar;
01196   if (firstChar > MAX_UNIBYTE) SetHasMultibyte(PR_TRUE);
01197 
01198   PRInt32 numChars = 1;
01199 
01200 #ifdef IBMBIDI
01201   if (offset > limit) {
01202 #else
01203   if (offset > 0) {
01204 #endif
01205     const PRUnichar* cp = cp0 + offset;
01206     PRBool breakBetween = PR_FALSE;
01207     if (aForLineBreak) {
01208       mLineBreaker->BreakInBetween(cp0, offset + 1,
01209                                    mTransformBuf.GetBufferEnd()-1, 1,
01210                                    &breakBetween);
01211     }
01212     else {
01213       mWordBreaker->BreakInBetween(cp0, offset + 1,
01214                                    mTransformBuf.GetBufferEnd()-1, 1,
01215                                    &breakBetween);
01216     }
01217 
01218     if (!breakBetween) {
01219       // Find next position
01220       PRBool tryPrevFrag;
01221       PRUint32 prev;
01222       if (aForLineBreak) {
01223         mLineBreaker->Prev(cp0, offset, offset, &prev, &tryPrevFrag);
01224       }
01225       else {
01226         mWordBreaker->PrevWord(cp0, offset, offset, &prev, &tryPrevFrag);
01227       }
01228       numChars = (PRInt32) ((PRUint32) offset - prev) + 1;
01229 
01230       // Grow buffer before copying
01231       nsresult rv = mTransformBuf.GrowTo(numChars);
01232       if (NS_FAILED(rv)) {
01233         numChars = mTransformBuf.GetBufferLength();
01234       }
01235 
01236       // 1. convert nbsp into space
01237       // 2. check mHasMultibyte flag
01238       // 3. copy buffer
01239       PRUnichar* bp = mTransformBuf.GetBufferEnd() - 1;
01240       const PRUnichar* end = cp - numChars + 1;
01241       while (cp > end) {
01242         PRUnichar ch = *--cp;
01243         if (CH_NBSP == ch) {
01244           ch = ' ';
01245         }
01246         else if (IS_DISCARDED(ch)) {
01247           continue;
01248         }
01249         if (ch > MAX_UNIBYTE) SetHasMultibyte(PR_TRUE);
01250         *--bp = ch;
01251       }
01252 
01253       // Recompute offset and numChars in case we stripped something
01254       offset = offset - numChars;
01255       numChars =  mTransformBuf.GetBufferEnd() - bp;
01256     }
01257   }
01258   else 
01259          offset--;
01260 
01261   *aWordLen = numChars;
01262   return offset;
01263 }
01264 
01265 // wordlen==*aWordLen, contentlen=newOffset-currentOffset, isWhitespace=t
01266 PRInt32
01267 nsTextTransformer::ScanPreWrapWhiteSpace_B(PRInt32* aWordLen)
01268 {
01269   const nsTextFragment* frag = mFrag;
01270   PRInt32 offset = mOffset;
01271   PRUnichar* bp = mTransformBuf.GetBufferEnd();
01272   PRUnichar* startbp = mTransformBuf.GetBuffer();
01273 
01274   while (--offset >= 0) {
01275     PRUnichar ch = frag->CharAt(offset);
01276     if (!XP_IS_SPACE(ch) || (ch == '\t') || (ch == '\n')) {
01277       // Keep looping if this is a discarded character
01278       if (IS_DISCARDED(ch)) {
01279         continue;
01280       }
01281       break;
01282     }
01283     if (bp == startbp) {
01284       PRInt32 oldLength = mTransformBuf.mBufferLen;
01285       nsresult rv = mTransformBuf.GrowBy(1000);
01286       if (NS_FAILED(rv)) {
01287         // If we run out of space (unlikely) then just chop the input
01288         break;
01289       }
01290       bp = mTransformBuf.GetBufferEnd() - oldLength;
01291       startbp = mTransformBuf.GetBuffer();
01292     }
01293     *--bp = ' ';
01294   }
01295 
01296   *aWordLen = mTransformBuf.GetBufferEnd() - bp;
01297   return offset;
01298 }
01299 
01300 // wordlen==*aWordLen, contentlen=newOffset-currentOffset, isWhitespace=f
01301 PRInt32
01302 nsTextTransformer::ScanPreData_B(PRInt32* aWordLen)
01303 {
01304   const nsTextFragment* frag = mFrag;
01305   PRInt32 offset = mOffset;
01306   PRUnichar* bp = mTransformBuf.GetBufferEnd();
01307   PRUnichar* startbp = mTransformBuf.GetBuffer();
01308 
01309   while (--offset >= 0) {
01310     PRUnichar ch = frag->CharAt(offset);
01311     if ((ch == '\t') || (ch == '\n')) {
01312       break;
01313     }
01314     if (CH_NBSP == ch) {
01315       ch = ' ';
01316     }
01317     else if (IS_DISCARDED(ch)) {
01318       continue;
01319     }
01320     if (ch > MAX_UNIBYTE) SetHasMultibyte(PR_TRUE);
01321     if (bp == startbp) {
01322       PRInt32 oldLength = mTransformBuf.mBufferLen;
01323       nsresult rv = mTransformBuf.GrowBy(1000);
01324       if (NS_FAILED(rv)) {
01325         // If we run out of space (unlikely) then just chop the input
01326         offset++;
01327         break;
01328       }
01329       bp = mTransformBuf.GetBufferEnd() - oldLength;
01330       startbp = mTransformBuf.GetBuffer();
01331     }
01332     *--bp = ch;
01333   }
01334 
01335   *aWordLen = mTransformBuf.GetBufferEnd() - bp;
01336   return offset;
01337 }
01338 
01339 //----------------------------------------
01340 
01341 PRUnichar*
01342 nsTextTransformer::GetPrevWord(PRBool aInWord,
01343                                PRInt32* aWordLenResult,
01344                                PRInt32* aContentLenResult,
01345                                PRBool* aIsWhiteSpaceResult,
01346                                PRBool aForLineBreak,
01347                                PRBool aIsKeyboardSelect)
01348 {
01349   const nsTextFragment* frag = mFrag;
01350   PRInt32 offset = mOffset;
01351   PRInt32 wordLen = 0;
01352   PRBool isWhitespace = PR_FALSE;
01353   PRUnichar* result = nsnull;
01354 
01355   // Fix word breaking problem w/ PREFORMAT and PREWRAP
01356   // for word breaking, we should really go to the normal code
01357   if((! aForLineBreak) && (eNormal != mMode))
01358      mMode = eNormal;
01359 
01360 #ifdef IBMBIDI
01361   PRInt32 limit = (*aWordLenResult > 0) ? *aWordLenResult : 0;
01362   while (--offset >= limit) {
01363 #else
01364   while (--offset >= 0) {
01365 #endif
01366     PRUnichar firstChar = frag->CharAt(offset);
01367 
01368     // Eat up any discarded characters before dispatching
01369     if (IS_DISCARDED(firstChar)) {
01370       continue;
01371     }
01372 
01373     switch (mMode) {
01374       default:
01375       case eNormal:
01376         if (XP_IS_SPACE(firstChar)) {
01377           offset = ScanNormalWhiteSpace_B();
01378           wordLen = 1;
01379           isWhitespace = PR_TRUE;
01380         }
01381         else if (CH_NBSP == firstChar && !aForLineBreak) {
01382           wordLen = 1;
01383           isWhitespace = PR_TRUE;
01384           mTransformBuf.mBuffer[mTransformBuf.mBufferLen - 1] = ' ';
01385           offset--;
01386         } else if (frag->Is2b()) {
01387 #ifdef IBMBIDI
01388           wordLen = *aWordLenResult;
01389 #endif
01390           offset = ScanNormalUnicodeText_B(aForLineBreak, &wordLen);
01391         }
01392         else {
01393           offset = ScanNormalAsciiText_B(&wordLen, aIsKeyboardSelect);
01394         }
01395         break;
01396 
01397       case ePreformatted:
01398         if (('\n' == firstChar) || ('\t' == firstChar)) {
01399           mTransformBuf.mBuffer[mTransformBuf.mBufferLen-1] = firstChar;
01400           offset--;  // make sure we overshoot
01401           wordLen = 1;
01402           isWhitespace = PR_TRUE;
01403         }
01404         else {
01405           offset = ScanPreData_B(&wordLen);
01406         }
01407         break;
01408 
01409       case ePreWrap:
01410         if (XP_IS_SPACE(firstChar)) {
01411           if (('\n' == firstChar) || ('\t' == firstChar)) {
01412             mTransformBuf.mBuffer[mTransformBuf.mBufferLen-1] = firstChar;
01413             offset--;  // make sure we overshoot
01414             wordLen = 1;
01415           }
01416           else {
01417             offset = ScanPreWrapWhiteSpace_B(&wordLen);
01418           }
01419           isWhitespace = PR_TRUE;
01420         }
01421         else if (frag->Is2b()) {
01422 #ifdef IBMBIDI
01423           wordLen = *aWordLenResult;
01424 #endif
01425           offset = ScanNormalUnicodeText_B(aForLineBreak, &wordLen);
01426         }
01427         else {
01428           offset = ScanNormalAsciiText_B(&wordLen, aIsKeyboardSelect);
01429         }
01430         break;
01431     }
01432 
01433     // Backwards scanning routines *always* overshoot by one for the
01434     // returned offset value.
01435     offset = offset + 1;
01436 
01437     result = mTransformBuf.GetBufferEnd() - wordLen;
01438 
01439     if (!isWhitespace) {
01440       switch (mTextTransform) {
01441         case NS_STYLE_TEXT_TRANSFORM_CAPITALIZE:
01442           if(NS_SUCCEEDED(EnsureCaseConv()))
01443             gCaseConv->ToTitle(result, result, wordLen, !aInWord);
01444           break;
01445         case NS_STYLE_TEXT_TRANSFORM_LOWERCASE:
01446           if(NS_SUCCEEDED(EnsureCaseConv()))
01447             gCaseConv->ToLower(result, result, wordLen);
01448           break;
01449         case NS_STYLE_TEXT_TRANSFORM_UPPERCASE:
01450           if(NS_SUCCEEDED(EnsureCaseConv()))
01451             gCaseConv->ToUpper(result, result, wordLen);
01452           break;
01453       }
01454     }
01455     break;
01456   }
01457 
01458   *aWordLenResult = wordLen;
01459   *aContentLenResult = mOffset - offset;
01460   *aIsWhiteSpaceResult = isWhitespace;
01461 
01462   mOffset = offset;
01463   return result;
01464 }
01465 
01466 void
01467 nsTextTransformer::DoArabicShaping(PRUnichar* aText, 
01468                                    PRInt32& aTextLength, 
01469                                    PRBool* aWasTransformed)
01470 {
01471   if (aTextLength <= 0)
01472     return;
01473   
01474   PRInt32 newLen;
01475   PRBool isVisual = mPresContext->IsVisualMode();
01476 
01477   nsAutoString buf;
01478   if (!EnsureStringLength(buf, aTextLength)) {
01479     // no way to signal OOM
01480     aTextLength = 0;
01481     return;
01482   }
01483   PRUnichar* buffer = buf.BeginWriting();
01484   
01485   ArabicShaping(aText, buf.Length(), buffer, (PRUint32 *)&newLen, !isVisual, !isVisual);
01486 
01487   if (newLen <= aTextLength) {
01488     aTextLength = newLen;
01489   } else {
01490     // Increasing |aTextLength| would cause a buffer overflow on |aText|
01491     // by the memcpy() below.
01492     NS_ERROR("ArabicShaping should not have increased the text length");
01493   }
01494   *aWasTransformed = PR_TRUE;
01495 
01496   memcpy(aText, buffer, aTextLength * sizeof(PRUnichar));
01497 }
01498 
01499 void
01500 nsTextTransformer::DoNumericShaping(PRUnichar* aText, 
01501                                     PRInt32& aTextLength, 
01502                                     PRBool* aWasTransformed)
01503 {
01504   if (aTextLength <= 0)
01505     return;
01506 
01507   PRUint32 bidiOptions = mPresContext->GetBidi();
01508 
01509   switch (GET_BIDI_OPTION_NUMERAL(bidiOptions)) {
01510 
01511     case IBMBIDI_NUMERAL_HINDI:
01512       HandleNumbers(aText, aTextLength, IBMBIDI_NUMERAL_HINDI);
01513       break;
01514 
01515     case IBMBIDI_NUMERAL_ARABIC:
01516       HandleNumbers(aText, aTextLength, IBMBIDI_NUMERAL_ARABIC);
01517       break;
01518 
01519     case IBMBIDI_NUMERAL_REGULAR:
01520 
01521       switch (mCharType) {
01522 
01523         case eCharType_EuropeanNumber:
01524           HandleNumbers(aText, aTextLength, IBMBIDI_NUMERAL_ARABIC);
01525           break;
01526 
01527         case eCharType_ArabicNumber:
01528           HandleNumbers(aText, aTextLength, IBMBIDI_NUMERAL_HINDI);
01529           break;
01530 
01531         default:
01532           break;
01533       }
01534       break;
01535 
01536     case IBMBIDI_NUMERAL_HINDICONTEXT:
01537       if (((GET_BIDI_OPTION_DIRECTION(bidiOptions)==IBMBIDI_TEXTDIRECTION_RTL) &&
01538            (IS_ARABIC_DIGIT (aText[0]))) ||
01539           (eCharType_ArabicNumber == mCharType))
01540         HandleNumbers(aText, aTextLength, IBMBIDI_NUMERAL_HINDI);
01541       else if (eCharType_EuropeanNumber == mCharType)
01542         HandleNumbers(aText, aTextLength, IBMBIDI_NUMERAL_ARABIC);
01543       break;
01544 
01545     case IBMBIDI_NUMERAL_NOMINAL:
01546     default:
01547       break;
01548   }
01549 }
01550 
01551 //----------------------------------------------------------------------
01552 // Self test logic for this class. This will (hopefully) make sure
01553 // that the forward and backward word iterator methods continue to
01554 // function as people change things...
01555 
01556 #ifdef DEBUG
01557 struct SelfTestSection {
01558   int length;
01559   int* data;
01560 };
01561 
01562 #define NUM_MODES 3
01563 
01564 struct SelfTestData {
01565   const PRUnichar* text;
01566   SelfTestSection modes[NUM_MODES];
01567 };
01568 
01569 static PRUint8 preModeValue[NUM_MODES] = {
01570   NS_STYLE_WHITESPACE_NORMAL,
01571   NS_STYLE_WHITESPACE_PRE,
01572   NS_STYLE_WHITESPACE_MOZ_PRE_WRAP
01573 };
01574 
01575 static PRUnichar test1text[] = {
01576   'o', 'n', 'c', 'e', ' ', 'u', 'p', 'o', 'n', '\t',
01577   'a', ' ', 's', 'h', 'o', 'r', 't', ' ', 't', 'i', 'm', 'e', 0
01578 };
01579 static int test1Results[] = { 4, 1, 4, 1, 1, 1, 5, 1, 4 };
01580 static int test1PreResults[] = { 9, 1, 12 };
01581 static int test1PreWrapResults[] = { 4, 1, 4, 1, 1, 1, 5, 1, 4 };
01582 
01583 static PRUnichar test2text[] = {
01584   0xF6, 'n', 'c', 'e', ' ', 0xFB, 'p', 'o', 'n', '\t',
01585   0xE3, ' ', 's', 'h', 0xF3, 'r', 't', ' ', 't', 0xEE, 'm', 'e', ' ', 0
01586 };
01587 static int test2Results[] = { 4, 1, 4, 1, 1, 1, 5, 1, 4, 1 };
01588 static int test2PreResults[] = { 9, 1, 13 };
01589 static int test2PreWrapResults[] = { 4, 1, 4, 1, 1, 1, 5, 1, 4, 1 };
01590 
01591 static PRUnichar test3text[] = {
01592   0x0152, 'n', 'c', 'e', ' ', 'x', 'y', '\t', 'z', 'y', ' ', 0
01593 };
01594 static int test3Results[] = { 4, 1, 2, 1, 2, 1, };
01595 static int test3PreResults[] = { 7, 1, 3, };
01596 static int test3PreWrapResults[] = { 4, 1, 2, 1, 2, 1, };
01597 
01598 static PRUnichar test4text[] = {
01599   'o', 'n', CH_SHY, 'c', 'e', ' ', CH_SHY, ' ', 'u', 'p', 'o', 'n', '\t',
01600   'a', ' ', 's', 'h', 'o', 'r', 't', ' ', 't', 'i', 'm', 'e', 0
01601 };
01602 static int test4Results[] = { 4, 1, 4, 1, 1, 1, 5, 1, 4 };
01603 static int test4PreResults[] = { 10, 1, 12 };
01604 static int test4PreWrapResults[] = { 4, 2, 4, 1, 1, 1, 5, 1, 4 };
01605 
01606 static PRUnichar test5text[] = {
01607   CH_SHY, 0
01608 };
01609 static int test5Results[] = { 0 };
01610 static int test5PreResults[] = { 0 };
01611 static int test5PreWrapResults[] = { 0 };
01612 
01613 #if 0
01614 static PRUnichar test6text[] = {
01615   0x30d5, 0x30b8, 0x30c6, 0x30ec, 0x30d3, 0x306e, 0x97f3, 0x697d,
01616   0x756a, 0x7d44, 0x300c, 'H', 'E', 'Y', '!', ' ', 'H', 'E', 'Y', '!',
01617   '\t', 'H', 'E', 'Y', '!', 0x300d, 0x306e, 0x30db, 0x30fc, 0x30e0,
01618   0x30da, 0x30fc, 0x30b8, 0x3002, 0
01619 };
01620 static int test6Results[] = { 1, 1, 1, 1, 1,
01621                               1, 1, 1, 1, 1,
01622                               5, 1, 4, 1, 5,
01623                               1, 2, 1, 2, 2 };
01624 static int test6PreResults[] = { 20, 1, 13 };
01625 static int test6PreWrapResults[] = { 1, 1, 1, 1, 1,
01626                                      1, 1, 1, 1, 1,
01627                                      5, 1, 4, 1, 5,
01628                                      1, 2, 1, 2, 2 };
01629 #endif
01630 
01631 static SelfTestData tests[] = {
01632   { test1text,
01633     { { sizeof(test1Results)/sizeof(int), test1Results, },
01634       { sizeof(test1PreResults)/sizeof(int), test1PreResults, },
01635       { sizeof(test1PreWrapResults)/sizeof(int), test1PreWrapResults, } }
01636   },
01637   { test2text,
01638     { { sizeof(test2Results)/sizeof(int), test2Results, },
01639       { sizeof(test2PreResults)/sizeof(int), test2PreResults, },
01640       { sizeof(test2PreWrapResults)/sizeof(int), test2PreWrapResults, } }
01641   },
01642   { test3text,
01643     { { sizeof(test3Results)/sizeof(int), test3Results, },
01644       { sizeof(test3PreResults)/sizeof(int), test3PreResults, },
01645       { sizeof(test3PreWrapResults)/sizeof(int), test3PreWrapResults, } }
01646   },
01647   { test4text,
01648     { { sizeof(test4Results)/sizeof(int), test4Results, },
01649       { sizeof(test4PreResults)/sizeof(int), test4PreResults, },
01650       { sizeof(test4PreWrapResults)/sizeof(int), test4PreWrapResults, } }
01651   },
01652   { test5text,
01653     { { sizeof(test5Results)/sizeof(int), test5Results, },
01654       { sizeof(test5PreResults)/sizeof(int), test5PreResults, },
01655       { sizeof(test5PreWrapResults)/sizeof(int), test5PreWrapResults, } }
01656   },
01657 #if 0
01658   { test6text,
01659     { { sizeof(test6Results)/sizeof(int), test6Results, },
01660       { sizeof(test6PreResults)/sizeof(int), test6PreResults, },
01661       { sizeof(test6PreWrapResults)/sizeof(int), test6PreWrapResults, } }
01662   },
01663 #endif
01664 };
01665 
01666 #define NUM_TESTS (sizeof(tests) / sizeof(tests[0]))
01667 
01668 void
01669 nsTextTransformer::SelfTest(nsILineBreaker* aLineBreaker,
01670                             nsIWordBreaker* aWordBreaker,
01671                             nsPresContext* aPresContext)
01672 {
01673   PRBool gNoisy = PR_FALSE;
01674   if (PR_GetEnv("GECKO_TEXT_TRANSFORMER_NOISY_SELF_TEST")) {
01675     gNoisy = PR_TRUE;
01676   }
01677 
01678   PRBool error = PR_FALSE;
01679   PRInt32 testNum = 0;
01680   SelfTestData* st = tests;
01681   SelfTestData* last = st + NUM_TESTS;
01682   for (; st < last; st++) {
01683     PRUnichar* bp;
01684     PRInt32 wordLen, contentLen;
01685     PRBool ws, transformed;
01686 
01687     PRBool isAsciiTest = PR_TRUE;
01688     const PRUnichar* cp = st->text;
01689     while (*cp) {
01690       if (*cp > 255) {
01691         isAsciiTest = PR_FALSE;
01692         break;
01693       }
01694       cp++;
01695     }
01696 
01697     nsTextFragment frag(st->text);
01698     nsTextTransformer tx(aLineBreaker, aWordBreaker, aPresContext);
01699 
01700     for (PRInt32 preMode = 0; preMode < NUM_MODES; preMode++) {
01701       // Do forwards test
01702       if (gNoisy) {
01703         nsAutoString uc2(st->text);
01704         printf("%s forwards test: '", isAsciiTest ? "ascii" : "unicode");
01705         fputs(NS_ConvertUCS2toUTF8(uc2).get(), stdout);
01706         printf("'\n");
01707       }
01708       tx.Init2(&frag, 0, preModeValue[preMode], NS_STYLE_TEXT_TRANSFORM_NONE);
01709 
01710       int* expectedResults = st->modes[preMode].data;
01711       int resultsLen = st->modes[preMode].length;
01712 
01713 #ifdef IBMBIDI
01714       wordLen = -1;
01715 #endif
01716       while ((bp = tx.GetNextWord(PR_FALSE, &wordLen, &contentLen, &ws, &transformed))) {
01717         if (gNoisy) {
01718           nsAutoString tmp(bp, wordLen);
01719           printf("  '");
01720           fputs(NS_ConvertUCS2toUTF8(tmp).get(), stdout);
01721           printf("': ws=%s wordLen=%d (%d) contentLen=%d (offset=%d)\n",
01722                  ws ? "yes" : "no",
01723                  wordLen, *expectedResults, contentLen, tx.mOffset);
01724         }
01725         if (*expectedResults != wordLen) {
01726           error = PR_TRUE;
01727           break;
01728         }
01729         expectedResults++;
01730 #ifdef IBMBIDI
01731         wordLen = -1;
01732 #endif
01733       }
01734       if (expectedResults != st->modes[preMode].data + resultsLen) {
01735         if (st->modes[preMode].data[0] != 0) {
01736           error = PR_TRUE;
01737         }
01738       }
01739 
01740       // Do backwards test
01741       if (gNoisy) {
01742         nsAutoString uc2(st->text);
01743         printf("%s backwards test: '", isAsciiTest ? "ascii" : "unicode");
01744         fputs(NS_ConvertUCS2toUTF8(uc2).get(), stdout);
01745         printf("'\n");
01746       }
01747       tx.Init2(&frag, frag.GetLength(), NS_STYLE_WHITESPACE_NORMAL,
01748                NS_STYLE_TEXT_TRANSFORM_NONE);
01749       expectedResults = st->modes[preMode].data + resultsLen;
01750 #ifdef IBMBIDI
01751       wordLen = -1;
01752 #endif
01753       while ((bp = tx.GetPrevWord(PR_FALSE, &wordLen, &contentLen, &ws))) {
01754         --expectedResults;
01755         if (gNoisy) {
01756           nsAutoString tmp(bp, wordLen);
01757           printf("  '");
01758           fputs(NS_ConvertUCS2toUTF8(tmp).get(), stdout);
01759           printf("': ws=%s wordLen=%d contentLen=%d (offset=%d)\n",
01760                  ws ? "yes" : "no",
01761                  wordLen, contentLen, tx.mOffset);
01762         }
01763         if (*expectedResults != wordLen) {
01764           error = PR_TRUE;
01765           break;
01766         }
01767 #ifdef IBMBIDI
01768         wordLen = -1;
01769 #endif
01770       }
01771       if (expectedResults != st->modes[preMode].data) {
01772         if (st->modes[preMode].data[0] != 0) {
01773           error = PR_TRUE;
01774         }
01775       }
01776 
01777       if (error) {
01778         fprintf(stderr, "nsTextTransformer: self test %d failed\n", testNum);
01779       }
01780       else if (gNoisy) {
01781         fprintf(stdout, "nsTextTransformer: self test %d succeeded\n", testNum);
01782       }
01783 
01784       testNum++;
01785     }
01786   }
01787   if (error) {
01788     NS_ABORT();
01789   }
01790 }
01791 
01792 nsresult
01793 nsTextTransformer::Init2(const nsTextFragment* aFrag,
01794                          PRInt32 aStartingOffset,
01795                          PRUint8 aWhiteSpace,
01796                          PRUint8 aTextTransform)
01797 {
01798   mFrag = aFrag;
01799 
01800   // Sanitize aStartingOffset
01801   if (aStartingOffset < 0) {
01802     NS_WARNING("bad starting offset");
01803     aStartingOffset = 0;
01804   }
01805   else if (aStartingOffset > mFrag->GetLength()) {
01806     NS_WARNING("bad starting offset");
01807     aStartingOffset = mFrag->GetLength();
01808   }
01809   mOffset = aStartingOffset;
01810 
01811   // Get the frames text style information
01812   if (NS_STYLE_WHITESPACE_PRE == aWhiteSpace) {
01813     mMode = ePreformatted;
01814   }
01815   else if (NS_STYLE_WHITESPACE_MOZ_PRE_WRAP == aWhiteSpace) {
01816     mMode = ePreWrap;
01817   }
01818   mTextTransform = aTextTransform;
01819 
01820   return NS_OK;
01821 }
01822 #endif /* DEBUG */