Bug 333659. Major new-textframe update. You can really browse the Web with it now, although major bugs remain.
authorroc+@cs.cmu.edu
Thu, 22 Mar 2007 16:13:12 -0700
changeset 8 33654b51bca91fab0faed723e281c76bd65896c1
parent 7 cd100ce4677919334ec2e3ffb57b444aabf81141
child 9 2a109d05f223b3eb06487e0e600c5cbc333ef47a
push idunknown
push userunknown
push dateunknown
bugs333659
milestone1.9a3pre
Bug 333659. Major new-textframe update. You can really browse the Web with it now, although major bugs remain.
layout/generic/nsTextFrameThebes.cpp
layout/generic/nsTextFrameUtils.cpp
layout/generic/nsTextFrameUtils.h
layout/generic/nsTextRunTransformations.cpp
layout/generic/nsTextRunTransformations.h
--- a/layout/generic/nsTextFrameThebes.cpp
+++ b/layout/generic/nsTextFrameThebes.cpp
@@ -15,17 +15,17 @@
  * The Original Code is mozilla.org code.
  *
  * The Initial Developer of the Original Code is
  * Netscape Communications Corporation.
  * Portions created by the Initial Developer are Copyright (C) 1998
  * the Initial Developer. All Rights Reserved.
  *
  * Contributor(s):
- *   Robert O'Callahan <roc+moz@cs.cmu.edu>
+ *   Robert O'Callahan <robert@ocallahan.org>
  *   Roger B. Sidje <rbs@maths.uq.edu.au>
  *   Pierre Phaneuf <pp@ludusdesign.com>
  *   Prabhat Hegde <prabhat.hegde@sun.com>
  *   Tomi Leppikangas <tomi.leppikangas@oulu.fi>
  *   Roland Mainz <roland.mainz@informatik.med.uni-giessen.de>
  *   Daniel Glazman <glazman@netscape.com>
  *   Neil Deakin <neil@mozdevgroup.com>
  *   Masayuki Nakano <masayuki@d-toybox.com>
@@ -119,28 +119,32 @@
 #undef NOISY_REFLOW
 #undef NOISY_TRIM
 #endif
 
 // The following flags are set during reflow
 
 // This bit is set on the first frame in a continuation indicating
 // that it was chopped short because of :first-letter style.
-#define TEXT_FIRST_LETTER    0x00400000
+#define TEXT_FIRST_LETTER    0x00100000
 // This bit is set on frames that are logically adjacent to the start of the
 // line (i.e. no prior frame on line with actual displayed in-flow content).
-#define TEXT_START_OF_LINE   0x00800000
+#define TEXT_START_OF_LINE   0x00200000
 // This bit is set on frames that are logically adjacent to the end of the
 // line (i.e. no following on line with actual displayed in-flow content).
-#define TEXT_END_OF_LINE     0x01000000
+#define TEXT_END_OF_LINE     0x00400000
 // This bit is set on frames that end with a hyphenated break.
-#define TEXT_HYPHEN_BREAK    0x02000000
+#define TEXT_HYPHEN_BREAK    0x00800000
+// This bit is set on frames that trimmed trailing whitespace characters when
+// calculating their width during reflow.
+#define TEXT_TRIMMED_TRAILING_WHITESPACE 0x01000000
 
 #define TEXT_REFLOW_FLAGS    \
-  (TEXT_FIRST_LETTER|TEXT_START_OF_LINE|TEXT_END_OF_LINE|TEXT_HYPHEN_BREAK)
+  (TEXT_FIRST_LETTER|TEXT_START_OF_LINE|TEXT_END_OF_LINE|TEXT_HYPHEN_BREAK| \
+   TEXT_TRIMMED_TRAILING_WHITESPACE)
 
 // Cache bits for IsEmpty().
 // Set this bit if the textframe is known to be only collapsible whitespace.
 #define TEXT_IS_ONLY_WHITESPACE    0x08000000
 // Set this bit if the textframe is known to be not only collapsible whitespace.
 #define TEXT_ISNOT_ONLY_WHITESPACE 0x10000000
 
 #define TEXT_WHITESPACE_FLAGS      0x18000000
@@ -515,20 +519,24 @@ public:
                                      PRUint32* aFlowEndInTextRun = nsnull);
 
   gfxTextRun* GetTextRun() { return mTextRun; }
   void SetTextRun(gfxTextRun* aTextRun) { mTextRun = aTextRun; }
   
   PRInt32 GetColumn() { return mColumn; }
 
   // Get the DOM content range mapped by this frame after excluding
-  // whitespace subject to end-of-line trimming.
+  // whitespace subject to start-of-line and end-of-line trimming.
   // The textrun must have been created before calling this.
-  PRInt32 GetTrimmedContentLength(const nsTextFragment* aFrag,
-                                  const gfxSkipCharsIterator& aIterator);
+  struct TrimmedOffsets {
+    PRInt32 mStart;
+    PRInt32 mLength;
+  };
+  TrimmedOffsets GetTrimmedOffsets(const nsTextFragment* aFrag,
+                                   PRBool aTrimAfter);
 
 protected:
   virtual ~nsTextFrame();
   
   nsIFrame*   mNextContinuation;
   PRInt32     mContentOffset;
   PRInt32     mContentLength;
   PRInt32     mColumn;
@@ -572,47 +580,47 @@ static PRBool IsCSSWordSpacingSpace(cons
   NS_ASSERTION(aPos < aFrag->GetLength(), "No text for IsSpace!");
   PRUnichar ch = aFrag->CharAt(aPos);
   if (ch == ' ' || ch == 0x3000) { // IDEOGRAPHIC SPACE
     if (!aFrag->Is2b())
       return PR_TRUE;
     return !nsTextFrameUtils::IsSpaceCombiningSequenceTail(
         aFrag->Get2b() + aPos + 1, aFrag->GetLength() - (aPos + 1));
   } else {
-    return ch == '\t' || ch == '\n';
+    return ch == '\t' || ch == '\n' || ch == '\f';
   }
 }
 
 static PRBool IsSpace(const PRUnichar* aChars, PRUint32 aLength)
 {
   NS_ASSERTION(aLength > 0, "No text for IsSpace!");
   PRUnichar ch = *aChars;
   if (ch == ' ') {
     return !nsTextFrameUtils::IsSpaceCombiningSequenceTail(aChars + 1, aLength - 1);
   } else {
-    return ch == '\t' || ch == '\n';
+    return ch == '\t' || ch == '\n' || ch == '\f';
   }
 }
 
 static PRBool IsSpace(char aCh)
 {
-  return aCh == ' ' || aCh == '\t' || aCh == '\n';
+  return aCh == ' ' || aCh == '\t' || aCh == '\n' || aCh == '\f';
 }
 
 static PRBool IsSpace(const nsTextFragment* aFrag, PRUint32 aPos)
 {
   NS_ASSERTION(aPos < aFrag->GetLength(), "No text for IsSpace!");
   PRUnichar ch = aFrag->CharAt(aPos);
   if (ch == ' ') {
     if (!aFrag->Is2b())
       return PR_TRUE;
     return !nsTextFrameUtils::IsSpaceCombiningSequenceTail(
         aFrag->Get2b() + aPos + 1, aFrag->GetLength() - (aPos + 1));
   } else {
-    return ch == '\t' || ch == '\n';
+    return ch == '\t' || ch == '\n' || ch == '\f';
   }
 }
 
 static PRUint32 GetWhitespaceCount(const nsTextFragment* frag, PRInt32 aStartOffset,
                                    PRInt32 aLength, PRInt32 aDirection)
 {
   PRInt32 count = 0;
   if (frag->Is2b()) {
@@ -644,35 +652,42 @@ static PRUint32 GetWhitespaceCount(const
  * potential line breaks. It also records actual line breaks to store them in
  * the textruns.
  */
 class BuildTextRunsScanner {
 public:
   BuildTextRunsScanner(nsPresContext* aPresContext, nsBlockFrame* aBlockFrame,
                        gfxContext* aContext) :
     mCurrentFramesAllSameTextRun(nsnull), mBlockFrame(aBlockFrame),
-    mContext(aContext), mBidiEnabled(aPresContext->BidiEnabled()) {
+    mContext(aContext), mBidiEnabled(aPresContext->BidiEnabled()),
+    mTrimNextRunLeadingWhitespace(PR_FALSE) {
     ResetRunInfo();
   }
 
   void SetAtStartOfLine() {
     mStartOfLine = PR_TRUE;
-    mTrimNextRunLeadingWhitespace = PR_TRUE;
   }
   void SetCommonAncestorWithLastFrame(nsIFrame* aFrame) {
     mCommonAncestorWithLastFrame = aFrame;
   }
   nsIFrame* GetCommonAncestorWithLastFrame() {
     return mCommonAncestorWithLastFrame;
   }
+  void LiftCommonAncestorWithLastFrameToParent(nsIFrame* aFrame) {
+    if (mCommonAncestorWithLastFrame &&
+        mCommonAncestorWithLastFrame->GetParent() == aFrame) {
+      mCommonAncestorWithLastFrame = aFrame;
+    }
+  }
   void ScanFrame(nsIFrame* aFrame);
   void FlushFrames(PRBool aFlushLineBreaks);
   void ResetRunInfo() {
     mLastFrame = nsnull;
     mMappedFlows.Clear();
+    mLineBreakBeforeFrames.Clear();
     mMaxTextLength = 0;
     mDoubleByteText = PR_FALSE;
   }
   void AccumulateRunInfo(nsTextFrame* aFrame);
   void BuildTextRunForFrames(void* aTextBuffer);
   void AssignTextRun(gfxTextRun* aTextRun);
   nsTextFrame* GetNextBreakBeforeFrame(PRUint32* aIndex);
   void SetupBreakSinksForTextRun(gfxTextRun* aTextRun, const void* aText, PRUint32 aLength,
@@ -724,24 +739,26 @@ private:
   nsAutoTArray<nsTextFrame*,50> mLineBreakBeforeFrames;
   nsAutoTArray<BreakSink,10>    mBreakSinks;
   nsLineBreaker                 mLineBreaker;
   gfxTextRun*                   mCurrentFramesAllSameTextRun;
   nsBlockFrame*                 mBlockFrame;
   gfxContext*                   mContext;
   nsTextFrame*                  mLastFrame;
   // The common ancestor of the current frame and the previous text frame
-  // on the line, if there's no non-text frame boundaries in between.
+  // on the line, if there's no non-text frame boundaries in between. Otherwise
+  // null.
   nsIFrame*                     mCommonAncestorWithLastFrame;
   // mMaxTextLength is an upper bound on the size of the text in all mapped frames
   PRUint32                      mMaxTextLength;
   PRPackedBool                  mDoubleByteText;
   PRPackedBool                  mBidiEnabled;
   PRPackedBool                  mStartOfLine;
   PRPackedBool                  mTrimNextRunLeadingWhitespace;
+  PRPackedBool                  mCurrentRunTrimLeadingWhitespace;
 };
 
 /**
  * General routine for building text runs. This is hairy because of the need
  * to build text runs that span content nodes. Right now our strategy is fairly
  * simple. We find the lines containing aForFrame bounded by hard line breaks
  * (blocks or <br>s), and build textruns for all the frames in those lines.
  * Some might already have textruns in which case we leave those alone
@@ -771,17 +788,17 @@ BuildTextRuns(nsIRenderingContext* aRC, 
       nsLayoutUtils::FindChildContainingDescendant(aBlockFrame, aForFrame);
     line = aBlockFrame->FindLineFor(immediateChild);
     NS_ASSERTION(line != aBlockFrame->end_lines(),
                  "Frame is not in the block!!!");
   }
   nsBlockFrame::line_iterator firstLine = aBlockFrame->begin_lines();
   while (line != firstLine) {
     --line;
-    if (line->IsBlock() || !line->IsLineWrapped()) {
+    if (line->IsBlock()) {
       ++line;
       break;
     }
   }
 
   // Now iterate over all text frames starting from the current line. First-in-flow
   // text frames will be accumulated into textRunFrames as we go. When a
   // text run boundary is required we flush textRunFrames ((re)building their
@@ -793,27 +810,19 @@ BuildTextRuns(nsIRenderingContext* aRC, 
   NS_ASSERTION(line != endLines && !line->IsBlock(), "Where is this frame anyway??");
   nsIFrame* child = line->mFirstChild;
   do {
     scanner.SetAtStartOfLine();
     scanner.SetCommonAncestorWithLastFrame(nsnull);
     PRInt32 i;
     for (i = line->GetChildCount() - 1; i >= 0; --i) {
       scanner.ScanFrame(child);
-      if (scanner.GetCommonAncestorWithLastFrame()) {
-        NS_ASSERTION(aBlockFrame == scanner.GetCommonAncestorWithLastFrame()->GetParent(),
-                     "Bad mCommonAncestorWithLastFrame");
-        scanner.SetCommonAncestorWithLastFrame(aBlockFrame);
-      }
+      scanner.LiftCommonAncestorWithLastFrameToParent(aBlockFrame);
       child = child->GetNextSibling();
     }
-    if (!line->IsLineWrapped()) {
-      // Some kind of hard break. We're done.
-      break;
-    }
     ++line;
   } while (line != endLines && !line->IsBlock());
 
   // Set mStartOfLine so FlushFrames knows its textrun ends a line
   scanner.SetAtStartOfLine();
   scanner.FlushFrames(PR_TRUE);
 }
 
@@ -896,24 +905,16 @@ ReconstructTextForRun(gfxTextRun* aTextR
       length += flow->mContentLength;
     }
     if (!buffer.AppendElements(length*charSize))
       return;
 
     bufEnd = buffer.Elements();
     for (i = 0; i < userData->mMappedFlowCount; ++i) {
       TextRunMappedFlow* flow = &userData->mMappedFlows[i];
-      if (i > 0) {
-        // Note that all frames in the flow have the same style. first-line and
-        // first-letter could have violated this but for those, we won't get here
-        // because we force the textrun to remember its text
-        NS_ASSERTION(flow->mStartFrame->GetStyleContext() ==
-                     userData->mMappedFlows[i - 1].mStartFrame->GetStyleContext(),
-                     "Frames in flow should have same style contexts");
-      }
       bufEnd = TransformTextToBuffer(flow->mStartFrame, flow->mContentLength, bufEnd,
                                      charSize, &builder, aIncomingWhitespace);
       if (!bufEnd)
         return;
     }
     f = userData->mMappedFlows[0].mStartFrame;
   }
   PRUint32 transformedLength = NS_STATIC_CAST(PRUint8*, bufEnd) - buffer.Elements();
@@ -941,17 +942,17 @@ ReconstructTextForRun(gfxTextRun* aTextR
  */
 void BuildTextRunsScanner::FlushFrames(PRBool aFlushLineBreaks)
 {
   if (mMappedFlows.Length() == 0)
     return;
 
   if (mCurrentFramesAllSameTextRun &&
       ((mCurrentFramesAllSameTextRun->GetFlags() & nsTextFrameUtils::TEXT_INCOMING_WHITESPACE) != 0) ==
-      mTrimNextRunLeadingWhitespace) {
+      mCurrentRunTrimLeadingWhitespace) {
     // We do not need to (re)build the textrun.
     // Note that if the textrun included all these frames and more, and something
     // changed so that it can only cover these frames, then one of the frames
     // at the boundary would have detected the change and nuked the textrun.
 
     // Feed this run's text into the linebreaker to provide context. This also
     // updates mTrimNextRunLeadingWhitespace appropriately.
     ReconstructTextForRun(mCurrentFramesAllSameTextRun, PR_FALSE, this,
@@ -1052,16 +1053,17 @@ void BuildTextRunsScanner::ScanFrame(nsI
     mappedFlow->mContentEndOffset =
       frame->GetContentOffset() + frame->GetContentLength();
     mappedFlow->mTransformedTextOffset = 0;
     mLastFrame = frame;
 
     AccumulateRunInfo(frame);
     if (mMappedFlows.Length() == 1) {
       mCurrentFramesAllSameTextRun = frame->GetTextRun();
+      mCurrentRunTrimLeadingWhitespace = mTrimNextRunLeadingWhitespace;
     } else {
       if (mCurrentFramesAllSameTextRun != frame->GetTextRun()) {
         mCurrentFramesAllSameTextRun = nsnull;
       }
     }
     return;
   }
 
@@ -1076,21 +1078,17 @@ void BuildTextRunsScanner::ScanFrame(nsI
     mStartOfLine = PR_FALSE;
     mTrimNextRunLeadingWhitespace = PR_FALSE;
   }
 
   if (descendInto) {
     nsIFrame* f;
     for (f = aFrame->GetFirstChild(nsnull); f; f = f->GetNextSibling()) {
       ScanFrame(f);
-      if (mCommonAncestorWithLastFrame) {
-        NS_ASSERTION(aFrame == mCommonAncestorWithLastFrame->GetParent(),
-                     "Bad mCommonAncestorWithLastFrame");
-        mCommonAncestorWithLastFrame = aFrame;
-      }
+      LiftCommonAncestorWithLastFrameToParent(aFrame);
     }
   }
 
   if (!continueTextRun) {
     FlushFrames(PR_TRUE);
     mCommonAncestorWithLastFrame = nsnull;
     mTrimNextRunLeadingWhitespace = PR_FALSE;
   }
@@ -1179,18 +1177,21 @@ BuildTextRunsScanner::BuildTextRunForFra
   gfxSkipCharsBuilder builder;
 
   const void* textPtr = aTextBuffer;
   PRBool anySmallcapsStyle = PR_FALSE;
   PRBool anyTextTransformStyle = PR_FALSE;
   nsIContent* lastContent = nsnull;
   PRInt32 endOfLastContent = 0;
   PRBool anyMixedStyleFlows = PR_FALSE;
-  PRUint32 textFlags =
-    mTrimNextRunLeadingWhitespace ? nsTextFrameUtils::TEXT_INCOMING_WHITESPACE : 0;
+  PRUint32 textFlags = gfxTextRunFactory::TEXT_NEED_BOUNDING_BOX;
+
+  if (mCurrentRunTrimLeadingWhitespace) {
+    textFlags |= nsTextFrameUtils::TEXT_INCOMING_WHITESPACE;
+  }
 
   nsAutoTArray<PRUint32,50> textBreakPoints;
   // We might have a final break offset for the end of the textrun
   if (!textBreakPoints.AppendElements(mLineBreakBeforeFrames.Length() + 1))
     return;
 
   TextRunUserData dummyData;
   TextRunMappedFlow dummyMappedFlow;
@@ -1351,46 +1352,36 @@ BuildTextRunsScanner::BuildTextRunForFra
   nsTextFrame* firstFrame = mMappedFlows[0].mStartFrame;
   gfxFontGroup* fontGroup = GetFontGroupForFrame(firstFrame);
   if (!fontGroup) {
     DestroyUserData(userData);
     return;
   }
 
   // Setup factory chain
-  gfxTextRunFactory* factory = fontGroup;
-  nsRefPtr<nsTransformingTextRunFactory> transformingFactory;
-
+  nsAutoPtr<nsTransformingTextRunFactory> transformingFactory;
   if (anySmallcapsStyle) {
     transformingFactory = new nsFontVariantTextRunFactory(fontGroup);
-    factory = transformingFactory;
-  }
-
+  }
   if (anyTextTransformStyle) {
-    transformingFactory = new nsCaseTransformTextRunFactory(factory, transformingFactory);
-    factory = transformingFactory;
-  }
-
-  if (!factory) {
-    DestroyUserData(userData);
-    return;
+    transformingFactory =
+      new nsCaseTransformTextRunFactory(fontGroup, transformingFactory.forget());
   }
   nsTArray<nsStyleContext*> styles;
   if (transformingFactory) {
     for (i = 0; i < mMappedFlows.Length(); ++i) {
       MappedFlow* mappedFlow = &mMappedFlows[i];
       PRUint32 end = i == mMappedFlows.Length() - 1 ? transformedLength :
           mMappedFlows[i + 1].mTransformedTextOffset;
       nsStyleContext* sc = mappedFlow->mStartFrame->GetStyleContext();
       PRUint32 j;
       for (j = mappedFlow->mTransformedTextOffset; j < end; ++j) {
         styles.AppendElement(sc);
       }
     }
-    transformingFactory->SetStyles(styles.Elements());
   }
 
   if (textFlags & nsTextFrameUtils::TEXT_HAS_TAB) {
     textFlags |= gfxTextRunFactory::TEXT_ENABLE_SPACING;
   }
   if (textFlags & nsTextFrameUtils::TEXT_HAS_SHY) {
     textFlags |= gfxTextRunFactory::TEXT_ENABLE_HYPHEN_BREAKS;
   }
@@ -1422,28 +1413,44 @@ BuildTextRunsScanner::BuildTextRunForFra
         firstFrame->GetPresContext()->AppUnitsPerDevPixel(), textFlags };
 
   gfxTextRun* textRun;
   if (mDoubleByteText) {
     if (textFlags & gfxTextRunFactory::TEXT_IS_ASCII) {
       NS_WARNING("Hmm ... why are we taking the Unicode path when the text is all ASCII?");
     }
     const PRUnichar* text = NS_STATIC_CAST(const PRUnichar*, textPtr);
-    textRun = factory->MakeTextRun(text, transformedLength, &params);
+    if (transformingFactory) {
+      textRun = transformingFactory->MakeTextRun(text, transformedLength, &params,
+                                                 styles.Elements());
+      if (textRun) {
+        transformingFactory.forget();
+      }
+    } else {
+      textRun = fontGroup->MakeTextRun(text, transformedLength, &params);
+    }
     if (!textRun) {
       DestroyUserData(userData);
       return;
     }
     if (anyMixedStyleFlows) {
       // Make sure we're not asked to recover the text, it's too hard to do later.
       textRun->RememberText(text, transformedLength);
     }
   } else {
     const PRUint8* text = NS_STATIC_CAST(const PRUint8*, textPtr);
-    textRun = factory->MakeTextRun(text, transformedLength, &params);
+    if (transformingFactory) {
+      textRun = transformingFactory->MakeTextRun(text, transformedLength, &params,
+                                                 styles.Elements());
+      if (textRun) {
+        transformingFactory.forget();
+      }
+    } else {
+      textRun = fontGroup->MakeTextRun(text, transformedLength, &params);
+    }
     if (!textRun) {
       DestroyUserData(userData);
       return;
     }
     if (anyMixedStyleFlows) {
       // Make sure we're not asked to recover the text, it's too hard to do later.
       textRun->RememberText(text, transformedLength);
     }
@@ -1495,51 +1502,55 @@ BuildTextRunsScanner::SetupBreakSinksFor
                               length, flags, breakSink);
     } else {
       mLineBreaker.AppendText(lang, NS_STATIC_CAST(const PRUint8*, aText) + offset,
                               length, flags, breakSink);
     }
   }
 }
 
-/**
- * Helper class to reconstruct a textrun's text if required
- */
-class StaticTextProvider : public gfxTextRun::TextProvider {
-public:
-  StaticTextProvider(gfxTextRun* aTextRun) : mTextRun(aTextRun) {}
-
-  virtual void ForceRememberText() {
-    PRPackedBool incomingWhitespace =
-      (mTextRun->GetFlags() & nsTextFrameUtils::TEXT_INCOMING_WHITESPACE) != 0;
-    ReconstructTextForRun(mTextRun, PR_TRUE, nsnull, &incomingWhitespace);
-  }
-
-protected:
-  gfxTextRun* mTextRun;
-};
-
 void
 BuildTextRunsScanner::AssignTextRun(gfxTextRun* aTextRun)
 {
   nsIContent* lastContent = nsnull;
   PRUint32 i;
   for (i = 0; i < mMappedFlows.Length(); ++i) {
     MappedFlow* mappedFlow = &mMappedFlows[i];
-    nsTextFrame* f = mappedFlow->mStartFrame;
-    nsIContent* content = f->GetContent();
-    if (content != lastContent) {
-      f->AddStateBits(TEXT_IS_RUN_OWNER);
-      content = lastContent;
-    }
-    for (; f != mappedFlow->mEndFrame;
+    nsTextFrame* startFrame = mappedFlow->mStartFrame;
+    nsTextFrame* endFrame = mappedFlow->mEndFrame;
+    nsTextFrame* f;
+    for (f = startFrame; f != endFrame;
          f = NS_STATIC_CAST(nsTextFrame*, f->GetNextContinuation())) {
+#ifdef DEBUG
+      if (f->GetTextRun()) {
+        gfxTextRun* textRun = f->GetTextRun();
+        if (textRun->GetFlags() & nsTextFrameUtils::TEXT_IS_SIMPLE_FLOW) {
+          if (mMappedFlows[0].mStartFrame != NS_STATIC_CAST(nsTextFrame*, textRun->GetUserData())) {
+            NS_WARNING("REASSIGNING SIMPLE FLOW TEXT RUN!");
+          }
+        } else {
+          TextRunUserData* userData =
+            NS_STATIC_CAST(TextRunUserData*, textRun->GetUserData());
+         
+          if (PRUint32(userData->mMappedFlowCount) >= mMappedFlows.Length() ||
+              userData->mMappedFlows[userData->mMappedFlowCount - 1].mStartFrame !=
+              mMappedFlows[userData->mMappedFlowCount - 1].mStartFrame) {
+            NS_WARNING("REASSIGNING MULTIFLOW TEXT RUN (not append)!");
+          }
+        }
+      }
+#endif
       f->ClearTextRun();
       f->SetTextRun(aTextRun);
     }
+    nsIContent* content = startFrame->GetContent();
+    if (content != lastContent) {
+      startFrame->AddStateBits(TEXT_IS_RUN_OWNER);
+      lastContent = content;
+    }    
   }
 }
 
 gfxSkipCharsIterator
 nsTextFrame::EnsureTextRun(nsIRenderingContext* aRC, nsBlockFrame* aBlock,
                            const nsLineList::iterator* aLine,
                            PRUint32* aFlowEndInTextRun)
 {
@@ -1601,37 +1612,53 @@ nsTextFrame::EnsureTextRun(nsIRenderingC
     }
     startAt = userData->mLastFlowIndex - 1;
   }
   NS_ERROR("Can't find flow containing this frame???");
   static const gfxSkipChars emptySkipChars;
   return gfxSkipCharsIterator(emptySkipChars, 0);
 }
 
-PRInt32
-nsTextFrame::GetTrimmedContentLength(const nsTextFragment* aFrag,
-                                     const gfxSkipCharsIterator& aIterator)
+static PRUint32
+GetLengthOfTrimmedText(const nsTextFragment* aFrag,
+                       PRUint32 aStart, PRUint32 aEnd,
+                       gfxSkipCharsIterator* aIterator)
+{
+  aIterator->SetSkippedOffset(aEnd);
+  while (aIterator->GetSkippedOffset() > aStart) {
+    aIterator->AdvanceSkipped(-1);
+    if (!IsSpace(aFrag, aIterator->GetOriginalOffset()))
+      return aIterator->GetSkippedOffset() + 1 - aStart;
+  }
+  return 0;
+}
+
+nsTextFrame::TrimmedOffsets
+nsTextFrame::GetTrimmedOffsets(const nsTextFragment* aFrag,
+                               PRBool aTrimAfter)
 {
   NS_ASSERTION(mTextRun, "Need textrun here");
 
-  if (!(GetStateBits() & TEXT_END_OF_LINE) ||
-      GetStyleText()->WhiteSpaceIsSignificant())
-    return mContentLength;
-
-  gfxSkipCharsIterator iter(aIterator);
-
-  // Look backwards for first non-skipped, non-whitespace character
-  PRInt32 length;
-  for (length = mContentLength; length > 0; --length) {
-    iter.SetOriginalOffset(mContentOffset + length - 1);
-    if (!iter.IsOriginalCharSkipped() &&
-        !IsSpace(aFrag, iter.GetOriginalOffset()))
-      break;
-  }
-  return length;
+  TrimmedOffsets offsets = { mContentOffset, mContentLength };
+  if (GetStyleText()->WhiteSpaceIsSignificant())
+    return offsets;
+
+  if (GetStateBits() & TEXT_START_OF_LINE) {
+    PRInt32 whitespaceCount =
+      GetWhitespaceCount(aFrag, offsets.mStart, offsets.mLength, 1);
+    offsets.mStart += whitespaceCount;
+    offsets.mLength -= whitespaceCount;
+  }
+
+  if (aTrimAfter && (GetStateBits() & TEXT_END_OF_LINE)) {
+    PRInt32 whitespaceCount =
+      GetWhitespaceCount(aFrag, offsets.mStart + offsets.mLength - 1, offsets.mLength, -1);
+    offsets.mLength -= whitespaceCount;
+  }
+  return offsets;
 }
 
 /*
  * Currently only Unicode characters below 0x10000 have their spacing modified
  * by justification. If characters above 0x10000 turn out to need
  * justification spacing, that will require extra work. Currently,
  * this function must not include 0xd800 to 0xdbff because these characters
  * are surrogates.
@@ -1778,16 +1805,17 @@ public:
   PRUint32 ComputeJustifiableCharacters(PRInt32 aOffset, PRInt32 aLength);
   void FindEndOfJustificationRange(gfxSkipCharsIterator* aIter);
 
   /**
    * Count the number of spaces inserted for tabs in the given transformed substring
    */
   PRUint32 GetTabExpansionCount(PRUint32 aOffset, PRUint32 aLength);
 
+  const nsStyleText* GetStyleText() { return mTextStyle; }
   nsTextFrame* GetFrame() { return mFrame; }
   // This may not be equal to the frame offset/length in because we may have
   // adjusted for whitespace trimming according to the state bits set in the frame
   // (for the static provider)
   const gfxSkipCharsIterator& GetStart() { return mStart; }
   PRUint32 GetOriginalLength() { return mLength; }
   const nsTextFragment* GetFragment() { return mFrag; }
 
@@ -1833,139 +1861,79 @@ PropertyProvider::ComputeJustifiableChar
     for (i = 0; i < run.GetRunLength(); ++i) {
       justifiableChars +=
         IsJustifiableCharacter(mFrag, run.GetOriginalOffset() + i, isCJK);
     }
   }
   return justifiableChars;
 }
 
-/**
- * Helper class to fetch the char flags for a run of characters
- */
-class RunCharFlags {
-public:
-  RunCharFlags(const nsSkipCharsRunIterator& aRun, gfxTextRun* aTextRun) {
-    mBufPtr = mBuffer.AppendElements(aRun.GetRunLength());
-    if (!mBufPtr)
-      return;
-    NS_ASSERTION(!aRun.IsSkipped(), "Can't get flags for skipped chars");
-    aTextRun->GetCharFlags(aRun.GetPos().GetSkippedOffset(),
-                           aRun.GetRunLength(), mBufPtr);
-  }
-  RunCharFlags(PRUint32 aStart, PRUint32 aLength, gfxTextRun* aTextRun) {
-    mBufPtr = mBuffer.AppendElements(aLength);
-    if (!mBufPtr)
-      return;
-    aTextRun->GetCharFlags(aStart, aLength, mBufPtr);
-  }
-
-  // Index relative to start of run
-  PRUint8 GetFlags(PRUint32 aIndex) { return mBufPtr[aIndex]; }
-  PRUint8* get() { return mBufPtr; }
-
-private:
-  nsAutoTArray<PRUint8,BIG_TEXT_NODE_SIZE> mBuffer;
-  PRUint8*                                 mBufPtr;
-};
-
 PRUint8*
 PropertyProvider::ComputeTabSpaceCount(PRUint32 aOffset, PRUint32 aLength)
 {
   PRUint32 tabsEnd = mStart.GetSkippedOffset() + mTabSpaceCounts.Length();
   // We incrementally compute the tab space counts because it could be
   // inefficient to run ahead and compute space counts for tabs we don't need.
   // mTabSpaceCounts is an array of space counts whose first element is
   // the space count for the character at startOffset
   if (aOffset + aLength > tabsEnd) {
     PRUint32 column = mTabSpaceCounts.Length() ? mCurrentColumn : mFrame->GetColumn();
     PRInt32 count = aOffset + aLength - tabsEnd;
     nsSkipCharsRunIterator
       run(mStart, nsSkipCharsRunIterator::LENGTH_UNSKIPPED_ONLY, count);
     run.SetSkippedOffset(tabsEnd);
     while (run.NextRun()) {
-      RunCharFlags charFlags(run, mTextRun);
       PRInt32 i;
-
-      if (!charFlags.get())
-        break;
       for (i = 0; i < run.GetRunLength(); ++i) {
         if (mFrag->CharAt(i + run.GetOriginalOffset()) == '\t') {
           PRInt32 spaces = 8 - column%8;
           column += spaces;
           // The tab itself counts as a space
           mTabSpaceCounts.AppendElement(spaces - 1);
         } else {
-          if (charFlags.GetFlags(i) & gfxTextRun::CLUSTER_START) {
+          if (mTextRun->IsClusterStart(i + run.GetSkippedOffset())) {
             ++column;
           }
           mTabSpaceCounts.AppendElement(0);
         }
       }
     }
     mCurrentColumn = column;
   }
 
   return mTabSpaceCounts.Elements() + aOffset - mStart.GetSkippedOffset();
 }
 
 /**
  * Finds the offset of the first character of the cluster containing aPos
  */
-static PRUint32 FindClusterStart(PRUint8* aFlags, PRUint32 aPos)
-{
-  while (aPos > 0) {
-    if (aFlags[aPos] & gfxTextRun::CLUSTER_START)
-      break;
-    --aPos;
-  }
-  return aPos;
-}
-
-/**
- * Finds the offset of the first character of the cluster containing aPos
- */
 static void FindClusterStart(gfxTextRun* aTextRun,
                              gfxSkipCharsIterator* aPos)
 {
   while (aPos->GetOriginalOffset() > 0) {
     if (aPos->IsOriginalCharSkipped() ||
-        (aTextRun->GetCharFlags(aPos->GetSkippedOffset()) & gfxTextRun::CLUSTER_START)) {
+        aTextRun->IsClusterStart(aPos->GetSkippedOffset())) {
       break;
     }
     aPos->AdvanceOriginal(-1);
   }
 }
 
 /**
  * Finds the offset of the last character of the cluster containing aPos
  */
-static PRUint32 FindClusterEnd(PRUint8* aFlags, PRUint32 aLength, PRUint32 aPos)
-{
-  NS_PRECONDITION(aPos < aLength, "character outside string");
-  while (aPos + 1 < aLength) {
-    if (aFlags[aPos + 1] & gfxTextRun::CLUSTER_START)
-      break;
-    ++aPos;
-  }
-  return aPos;
-}
-
-/**
- * Finds the offset of the last character of the cluster containing aPos
- */
 static void FindClusterEnd(gfxTextRun* aTextRun, PRInt32 aOriginalEnd,
                            gfxSkipCharsIterator* aPos)
 {
   NS_PRECONDITION(aPos->GetOriginalOffset() < aOriginalEnd,
                   "character outside string");
   aPos->AdvanceOriginal(1);
   while (aPos->GetOriginalOffset() < aOriginalEnd) {
     if (aPos->IsOriginalCharSkipped() ||
-        (aTextRun->GetCharFlags(aPos->GetSkippedOffset()) & gfxTextRun::CLUSTER_START)) {
+        aTextRun->IsClusterStart(aPos->GetSkippedOffset())) {
       break;
     }
     aPos->AdvanceOriginal(1);
   }
   aPos->AdvanceOriginal(-1);
 }
 
 // aStart, aLength in transformed string offsets
@@ -1986,34 +1954,32 @@ PropertyProvider::GetSpacing(PRUint32 aS
   start.SetSkippedOffset(aStart);
 
   // First, compute the word and letter spacing
   if (mWordSpacing || mLetterSpacing) {
     // Iterate over non-skipped characters
     nsSkipCharsRunIterator
       run(start, nsSkipCharsRunIterator::LENGTH_UNSKIPPED_ONLY, aLength);
     while (run.NextRun()) {
-      RunCharFlags charFlags(run, mTextRun);
       PRUint32 runOffsetInSubstring = run.GetSkippedOffset() - aStart;
-      if (!charFlags.get())
-        break;
-
       PRInt32 i;
+      gfxSkipCharsIterator iter = run.GetPos();
       for (i = 0; i < run.GetRunLength(); ++i) {
         if (i + 1 >= run.GetRunLength() ||
-            (charFlags.GetFlags(i + 1) & gfxTextRun::CLUSTER_START)) {
+            mTextRun->IsClusterStart(i + 1 + run.GetSkippedOffset())) {
           // End of a cluster, put letter-spacing after it
           aSpacing[runOffsetInSubstring + i].mAfter += mLetterSpacing;
         }
         if (IsCSSWordSpacingSpace(mFrag, i + run.GetOriginalOffset())) {
           // It kinda sucks, but space characters can be part of clusters,
           // and even still be whitespace (I think!)
-          PRUint32 clusterLastChar =
-            FindClusterEnd(charFlags.get(), run.GetRunLength(), i);
-          aSpacing[runOffsetInSubstring + clusterLastChar].mAfter += mWordSpacing;
+          iter.SetSkippedOffset(run.GetSkippedOffset() + i);
+          FindClusterEnd(mTextRun, run.GetOriginalOffset() + run.GetRunLength(),
+                         &iter);
+          aSpacing[iter.GetSkippedOffset() - aStart].mAfter += mWordSpacing;
         }
       }
     }
   }
 
   // Now add tab spacing, if there is any
   if (mTextRun->GetFlags() & nsTextFrameUtils::TEXT_HAS_TAB) {
     // ComputeTabSpaceCount() will tell us where spaces need to be inserted
@@ -2039,33 +2005,30 @@ PropertyProvider::GetSpacing(PRUint32 aS
     // justification space on either side of the cluster
     PRBool isCJK = IsChineseJapaneseLangGroup(mFrame);
     gfxSkipCharsIterator justificationEnd(mStart);
     FindEndOfJustificationRange(&justificationEnd);
 
     nsSkipCharsRunIterator
       run(start, nsSkipCharsRunIterator::LENGTH_UNSKIPPED_ONLY, aLength);
     while (run.NextRun()) {
-      RunCharFlags charFlags(run, mTextRun);
-      if (!charFlags.get())
-        break;
-
       PRInt32 i;
-      PRInt32 runOffsetInSubstring = run.GetSkippedOffset() - aStart;
+      gfxSkipCharsIterator iter = run.GetPos();
       for (i = 0; i < run.GetRunLength(); ++i) {
-        if (IsJustifiableCharacter(mFrag, run.GetOriginalOffset() + i, isCJK)) {
-          PRInt32 clusterFirstChar =
-            FindClusterStart(charFlags.get(), i);
-          PRInt32 clusterLastChar =
-            FindClusterEnd(charFlags.get(), run.GetRunLength(), i);
+        PRInt32 originalOffset = run.GetOriginalOffset() + i;
+        if (IsJustifiableCharacter(mFrag, originalOffset, isCJK)) {
+          iter.SetOriginalOffset(originalOffset);
+          FindClusterStart(mTextRun, &iter);
+          PRUint32 clusterFirstChar = iter.GetSkippedOffset();
+          FindClusterEnd(mTextRun, run.GetOriginalOffset() + run.GetRunLength(), &iter);
+          PRUint32 clusterLastChar = iter.GetSkippedOffset();
           // Only apply justification to characters before justificationEnd
-          if (run.GetOriginalOffset() + clusterLastChar <
-              justificationEnd.GetOriginalOffset()) {
-            aSpacing[clusterFirstChar + runOffsetInSubstring].mBefore += halfJustificationSpace;
-            aSpacing[clusterLastChar + runOffsetInSubstring].mAfter += halfJustificationSpace;
+          if (clusterLastChar < justificationEnd.GetSkippedOffset()) {
+            aSpacing[clusterFirstChar - aStart].mBefore += halfJustificationSpace;
+            aSpacing[clusterLastChar - aStart].mAfter += halfJustificationSpace;
           }
         }
       }
     }
   }
 }
 
 PRUint32
@@ -2134,19 +2097,20 @@ PropertyProvider::GetHyphenationBreaks(P
       allowHyphenBreakBeforeNextChar = PR_FALSE;
     }
   }
 }
 
 void
 PropertyProvider::InitializeForDisplay(PRBool aTrimAfter)
 {
-  if (aTrimAfter) {
-    mLength = mFrame->GetTrimmedContentLength(mFrag, mStart);
-  }
+  nsTextFrame::TrimmedOffsets trimmed =
+    mFrame->GetTrimmedOffsets(mFrag, aTrimAfter);
+  mStart.SetOriginalOffset(trimmed.mStart);
+  mLength = trimmed.mLength;
   SetupJustificationSpacing();
 }
 
 static PRUint32 GetSkippedDistance(const gfxSkipCharsIterator& aStart,
                                    const gfxSkipCharsIterator& aEnd)
 {
   return aEnd.GetSkippedOffset() - aStart.GetSkippedOffset();
 }
@@ -2157,17 +2121,17 @@ PropertyProvider::FindEndOfJustification
   if (!(mFrame->GetStateBits() & TEXT_END_OF_LINE))
     return;
 
   // Ignore trailing cluster at end of line for justification purposes
   aIter->SetOriginalOffset(mStart.GetOriginalOffset() + mLength);
   while (aIter->GetOriginalOffset() > mStart.GetOriginalOffset()) {
     aIter->AdvanceOriginal(-1);
     if (!aIter->IsOriginalCharSkipped() &&
-        (mTextRun->GetCharFlags(aIter->GetSkippedOffset()) & gfxTextRun::CLUSTER_START))
+        mTextRun->IsClusterStart(aIter->GetSkippedOffset()))
       break;
   }
 }
 
 void
 PropertyProvider::SetupJustificationSpacing()
 {
   if (NS_STYLE_TEXT_ALIGN_JUSTIFY != mTextStyle->mTextAlign ||
@@ -2762,16 +2726,18 @@ NS_IMETHODIMP nsTextFrame::GetAccessible
 
 //-----------------------------------------------------------------------------
 NS_IMETHODIMP
 nsTextFrame::Init(nsIContent*      aContent,
                   nsIFrame*        aParent,
                   nsIFrame*        aPrevInFlow)
 {
   NS_ASSERTION(!aPrevInFlow, "Can't be a continuation!");
+  NS_PRECONDITION(aContent->IsNodeOfType(nsINode::eTEXT),
+                  "Bogus content!");
   nsresult rv = nsFrame::Init(aContent, aParent, aPrevInFlow);
   // Note that if we're created due to bidi splitting the bidi code
   // will override what we compute here, so it's ok.
   // We're not a continuing frame.
   // mContentOffset = 0; not necessary since we get zeroed out at init
   mContentLength = GetInFlowContentLength();
   return rv;
 }
@@ -3179,57 +3145,61 @@ nsTextFrame::BuildDisplayList(nsDisplayL
   DO_GLOBAL_REFLOW_COUNT_DSP("nsTextFrame");
 
   if ((0 != (mState & TEXT_BLINK_ON)) && nsBlinkTimer::GetBlinkIsOff())
     return NS_OK;
     
   return aLists.Content()->AppendNewToTop(new (aBuilder) nsDisplayText(this));
 }
 
-static nsIContent*
-GetContentAndOffsetsForSelection(nsTextFrame* aFrame, PRInt32* aOffset, PRInt32* aLength)
+static nsIFrame*
+GetGeneratedContentOwner(nsIFrame* aFrame, PRBool* aIsBefore)
 {
-  *aOffset = aFrame->GetContentOffset();
-  *aLength = aFrame->GetContentLength();
-  nsIContent* content = aFrame->GetContent();
-
-  nsIFrame* parent = aFrame->GetParent();
-  if (parent && (aFrame->GetStateBits() & NS_FRAME_GENERATED_CONTENT)) {
-    content = parent->GetContent();
-    if (!content)
-      return nsnull;
-
-    if (aOffset && aLength) {
-      // XXX I don't understand this logic at all
-      // ARE WE A BEFORE FRAME? if not then we assume we are an after frame. this may be bad later
-      nsIFrame *grandParent = parent->GetParent();
-      if (grandParent) {
-        nsIFrame *firstParent = grandParent->GetFirstChild(nsnull);
-        if (firstParent) {
-          *aLength = 0;
-          if (firstParent == parent) {
-            // our parent is the first child of granddad. use BEFORE
-            *aOffset = 0;
-          } else {
-            *aOffset = content->GetChildCount();
-          }
-        }
-      }
+  *aIsBefore = PR_FALSE;
+  while (aFrame && (aFrame->GetStateBits() & NS_FRAME_GENERATED_CONTENT)) {
+    if (aFrame->GetStyleContext()->GetPseudoType() == nsCSSPseudoElements::before) {
+      *aIsBefore = PR_TRUE;
     }
-  }
-
-  return content;
+    aFrame = aFrame->GetParent();
+  }
+  return aFrame;
 }
 
 SelectionDetails*
 nsTextFrame::GetSelectionDetails()
 {
-  PRInt32 offset, length;
-  nsIContent* content = GetContentAndOffsetsForSelection(this, &offset, &length);
-  return GetFrameSelection()->LookUpSelection(content, offset, length, PR_FALSE);
+  if (!(GetStateBits() & NS_FRAME_GENERATED_CONTENT)) {
+    SelectionDetails* details =
+      GetFrameSelection()->LookUpSelection(mContent, mContentOffset, 
+                                           mContentLength, PR_FALSE);
+    SelectionDetails* sd;
+    for (sd = details; sd; sd = sd->mNext) {
+      sd->mStart += mContentOffset;
+      sd->mEnd += mContentOffset;
+    }
+    return details;
+  }
+
+  // Check if the beginning or end of the element is selected, depending on
+  // whether we're :before content or :after content.
+  PRBool isBefore;
+  nsIFrame* owner = GetGeneratedContentOwner(this, &isBefore);
+  if (!owner || !owner->GetContent())
+    return nsnull;
+
+  SelectionDetails* details =
+    GetFrameSelection()->LookUpSelection(owner->GetContent(),
+        isBefore ? 0 : owner->GetContent()->GetChildCount(), 0, PR_FALSE);
+  SelectionDetails* sd;
+  for (sd = details; sd; sd = sd->mNext) {
+    // The entire text is selected!
+    sd->mStart = mContentOffset;
+    sd->mEnd = mContentOffset + mContentLength;
+  }
+  return details;
 }
 
 static void
 FillClippedRect(gfxContext* aCtx, nsPresContext* aPresContext,
                 nscolor aColor, const gfxRect& aDirtyRect, const gfxRect& aRect)
 {
   gfxRect r = aRect.Intersect(aDirtyRect);
   // For now, we need to put this in pixel coordinates
@@ -3513,17 +3483,17 @@ PRBool SelectionIterator::GetNextSegment
       if (mSelectionBuffer[index] != type)
         break;
     } while (mOriginalStart + index < mOriginalEnd);
     mIterator.SetOriginalOffset(index + mOriginalStart);
   
     // Advance to the next cluster boundary
     while (mIterator.GetOriginalOffset() < mOriginalEnd &&
            !mIterator.IsOriginalCharSkipped() &&
-           !(mTextRun->GetCharFlags(mIterator.GetSkippedOffset()) & gfxTextRun::CLUSTER_START)) {
+           !mTextRun->IsClusterStart(mIterator.GetSkippedOffset())) {
       mIterator.AdvanceOriginal(1);
     }
   
     // Check whether we actually got some non-skipped text in this segment
     if (runOffset < mIterator.GetSkippedOffset()) {    
       *aOffset = runOffset;
       *aLength = mIterator.GetSkippedOffset() - runOffset;
       *aXOffset = mXOffset;
@@ -3830,67 +3800,71 @@ nsTextFrame::IsVisibleInSelection(nsISel
   }
   DestroySelectionDetails(details);
 
   return found;
 }
 
 static PRUint32
 CountCharsFit(gfxTextRun* aTextRun, PRUint32 aStart, PRUint32 aLength,
-              gfxFloat aWidth, PropertyProvider* aProvider)
+              gfxFloat aWidth, PropertyProvider* aProvider,
+              gfxFloat* aFitWidth)
 {
   PRUint32 last = 0;
   gfxFloat totalWidth = 0;
   PRUint32 i;
   for (i = 1; i <= aLength; ++i) {
-    if (i == aLength ||
-        (aTextRun->GetCharFlags(aStart + i) & gfxTextRun::CLUSTER_START)) {
+    if (i == aLength || aTextRun->IsClusterStart(aStart + i)) {
+      gfxFloat lastWidth = totalWidth;
       totalWidth += aTextRun->GetAdvanceWidth(aStart + last, i - last, aProvider);
-      if (totalWidth > aWidth)
+      if (totalWidth > aWidth) {
+        *aFitWidth = lastWidth;
         return last;
+      }
       last = i;
     }
   }
+  *aFitWidth = 0;
   return 0;
 }
 
 nsIFrame::ContentOffsets
 nsTextFrame::CalcContentOffsetsFromFramePoint(nsPoint aPoint) {
   ContentOffsets offsets;
   
   gfxSkipCharsIterator iter = EnsureTextRun();
   if (!mTextRun)
     return offsets;
   
   PropertyProvider provider(this, iter);
   // Trim leading but not trailing whitespace if possible
   provider.InitializeForDisplay(PR_FALSE);
   gfxFloat width = mTextRun->IsRightToLeft() ? mRect.width - aPoint.x : aPoint.x;
-  gfxTextRun::Metrics metrics;
+  gfxFloat fitWidth;
   PRUint32 skippedLength = ComputeTransformedLength(provider);
 
   PRUint32 charsFit = CountCharsFit(mTextRun,
-      provider.GetStart().GetSkippedOffset(), skippedLength, width, &provider);
+      provider.GetStart().GetSkippedOffset(), skippedLength, width, &provider, &fitWidth);
 
   PRInt32 selectedOffset;
   if (charsFit < skippedLength) {
     // charsFit characters fitted, but no more could fit. See if we're
     // more than halfway through the cluster.. If we are, choose the next
     // cluster.
     gfxSkipCharsIterator extraCluster(provider.GetStart());
     extraCluster.AdvanceSkipped(charsFit);
     gfxSkipCharsIterator extraClusterLastChar(extraCluster);
     FindClusterEnd(mTextRun,
                    provider.GetStart().GetOriginalOffset() + provider.GetOriginalLength(),
                    &extraClusterLastChar);
     gfxFloat charWidth =
         mTextRun->GetAdvanceWidth(extraCluster.GetSkippedOffset(),
                                   GetSkippedDistance(extraCluster, extraClusterLastChar) + 1,
                                   &provider);
-    selectedOffset = width <= metrics.mAdvanceWidth + charWidth/2
+    selectedOffset = width <= fitWidth + charWidth/2
         ? extraCluster.GetOriginalOffset()
         : extraClusterLastChar.GetOriginalOffset() + 1;
   } else {
     // All characters fitted, we're at (or beyond) the end of the text.
     // XXX This could be some pathological situation where negative spacing
     // caused characters to move backwards. We can't really handle that
     // in the current frame system because frames can't have negative
     // intrinsic widths.
@@ -4044,17 +4018,17 @@ nsTextFrame::GetPointFromOffset(nsPresCo
   PRInt32 trimmedEnd = trimmedOffset + properties.GetOriginalLength();
   inOffset = PR_MAX(inOffset, trimmedOffset);
   inOffset = PR_MIN(inOffset, trimmedEnd);
 
   iter.SetOriginalOffset(inOffset);
 
   if (inOffset < trimmedEnd &&
       !iter.IsOriginalCharSkipped() &&
-      !(mTextRun->GetCharFlags(iter.GetSkippedOffset()) & gfxTextRun::CLUSTER_START)) {
+      !mTextRun->IsClusterStart(iter.GetSkippedOffset())) {
     NS_WARNING("GetPointFromOffset called for non-cluster boundary");
     FindClusterStart(mTextRun, &iter);
   }
 
   gfxFloat advanceWidth =
     mTextRun->GetAdvanceWidth(properties.GetStart().GetSkippedOffset(),
                               GetSkippedDistance(properties.GetStart(), iter),
                               &properties);
@@ -4122,20 +4096,20 @@ PRBool
 nsTextFrame::PeekOffsetNoAmount(PRBool aForward, PRInt32* aOffset)
 {
   NS_ASSERTION(aOffset && *aOffset <= mContentLength, "aOffset out of range");
 
   gfxSkipCharsIterator iter = EnsureTextRun();
   if (!mTextRun)
     return PR_FALSE;
 
-  PRInt32 length = GetTrimmedContentLength(mContent->GetText(), iter);
+  TrimmedOffsets trimmed = GetTrimmedOffsets(mContent->GetText(), PR_TRUE);
   // Check whether there are nonskipped characters in the trimmmed range
-  return iter.ConvertOriginalToSkipped(mContentOffset + length) >
-         iter.ConvertOriginalToSkipped(mContentOffset);
+  return iter.ConvertOriginalToSkipped(trimmed.mStart + trimmed.mLength) >
+         iter.ConvertOriginalToSkipped(trimmed.mStart);
 }
 
 PRBool
 nsTextFrame::PeekOffsetCharacter(PRBool aForward, PRInt32* aOffset)
 {
   NS_ASSERTION(aOffset && *aOffset <= mContentLength, "aOffset out of range");
 
   PRBool selectable;
@@ -4143,42 +4117,43 @@ nsTextFrame::PeekOffsetCharacter(PRBool 
   IsSelectable(&selectable, &selectStyle);
   if (selectStyle == NS_STYLE_USER_SELECT_ALL)
     return PR_FALSE;
 
   gfxSkipCharsIterator iter = EnsureTextRun();
   if (!mTextRun)
     return PR_FALSE;
 
-  PRInt32 length = GetTrimmedContentLength(mContent->GetText(), iter);
+  TrimmedOffsets trimmed = GetTrimmedOffsets(mContent->GetText(), PR_TRUE);
 
   // A negative offset means "end of frame".
   PRInt32 startOffset = mContentOffset + (*aOffset < 0 ? mContentLength : *aOffset);
 
   if (!aForward) {
     *aOffset = 0;
     PRInt32 i;
-    for (i = PR_MIN(mContentOffset + length, startOffset) - 1; i >= mContentOffset; --i) {
+    for (i = PR_MIN(trimmed.mStart + trimmed.mLength, startOffset) - 1;
+         i >= trimmed.mStart; --i) {
       iter.SetOriginalOffset(i);
       if (!iter.IsOriginalCharSkipped() &&
-          (mTextRun->GetCharFlags(iter.GetSkippedOffset()) & gfxTextRun::CLUSTER_START)) {
+          mTextRun->IsClusterStart(iter.GetSkippedOffset())) {
         *aOffset = i - mContentOffset;
         return PR_TRUE;
       }
     }
   } else {
     *aOffset = mContentLength;
     PRInt32 i;
     // XXX there's something weird here about end-of-line. Need to test
     // caret movement through line endings. The old code could return mContentLength,
     // but we can't anymore...
-    for (i = startOffset; i < mContentOffset + length; ++i) {
+    for (i = startOffset; i < trimmed.mStart + trimmed.mLength; ++i) {
       iter.SetOriginalOffset(i);
       if (!iter.IsOriginalCharSkipped() &&
-          (mTextRun->GetCharFlags(iter.GetSkippedOffset()) & gfxTextRun::CLUSTER_START)) {
+          mTextRun->IsClusterStart(iter.GetSkippedOffset())) {
         *aOffset = i - mContentOffset;
         return PR_TRUE;
       }
     }
   }
   
   return PR_FALSE;
 }
@@ -4195,56 +4170,65 @@ nsTextFrame::PeekOffsetWord(PRBool aForw
   if (selectStyle == NS_STYLE_USER_SELECT_ALL)
     return PR_FALSE;
 
   const nsTextFragment* frag = mContent->GetText();
   gfxSkipCharsIterator iter = EnsureTextRun();
   if (!mTextRun)
     return PR_FALSE;
 
-  PRInt32 length = GetTrimmedContentLength(frag, iter);
+  TrimmedOffsets trimmed = GetTrimmedOffsets(frag, PR_TRUE);
 
   // A negative offset means "end of frame".
   PRInt32 startOffset = mContentOffset + (*aOffset < 0 ? mContentLength : *aOffset);
-  startOffset = PR_MIN(startOffset, mContentOffset + length);
+  startOffset = PR_MIN(startOffset, trimmed.mStart + trimmed.mLength);
 
   // Find DOM offset of first nonskipped character
-  PRInt32 offset = mContentOffset;
+  PRInt32 offset = trimmed.mStart;
+  PRInt32 length = trimmed.mLength;
   PRInt32 runLength;
   if (iter.IsOriginalCharSkipped(&runLength)) {
     offset += runLength;
+    length -= runLength;
   }
   startOffset = PR_MAX(startOffset, offset);
 
   PRBool isWhitespace;
   PRBool stopAfterPunctuation = nsTextTransformer::GetWordSelectStopAtPunctuation();
   PRBool stopBeforePunctuation = stopAfterPunctuation && aIsKeyboardSelect;
   PRInt32 direction = aForward ? 1 : -1;
   *aOffset = aForward ? mContentLength : 0;
-
-  PRInt32 nextWordStart = nsTextFrameUtils::FindWordBoundary(frag,
-      mTextRun, &iter, offset, length, startOffset,
+  if (startOffset + direction < offset ||
+      startOffset + direction >= offset + length)
+    return PR_FALSE;
+
+  PRInt32 wordLen = nsTextFrameUtils::FindWordBoundary(frag,
+      mTextRun, &iter, offset, length, startOffset + direction,
       direction, stopBeforePunctuation, stopAfterPunctuation, &isWhitespace);
-  if (nextWordStart < 0)
+  if (wordLen < 0)
     return PR_FALSE;
 
   if (aWordSelectEatSpace == isWhitespace || !*aSawBeforeType) {
-    *aOffset = nextWordStart - mContentOffset;
+    PRInt32 nextWordFirstChar = startOffset + direction*wordLen;
+    *aOffset = (aForward ? nextWordFirstChar : nextWordFirstChar + 1) - mContentOffset;
     if (aWordSelectEatSpace == isWhitespace) {
       *aSawBeforeType = PR_TRUE;
     }
 
     for (;;) {
-      nextWordStart = nsTextFrameUtils::FindWordBoundary(frag,
-        mTextRun, &iter, offset, length, nextWordStart,
+      if (nextWordFirstChar < offset || nextWordFirstChar >= offset + length)
+        return PR_FALSE;
+      wordLen = nsTextFrameUtils::FindWordBoundary(frag,
+        mTextRun, &iter, offset, length, nextWordFirstChar,
         direction, stopBeforePunctuation, stopAfterPunctuation, &isWhitespace);
-      if (nextWordStart < 0 ||
+      if (wordLen < 0 ||
           (aWordSelectEatSpace ? !isWhitespace : *aSawBeforeType))
         break;
-      *aOffset = nextWordStart - mContentOffset;
+      nextWordFirstChar = nextWordFirstChar + direction*wordLen;
+      *aOffset = (aForward ? nextWordFirstChar : nextWordFirstChar + 1) - mContentOffset;
       if (aWordSelectEatSpace == isWhitespace) {
         *aSawBeforeType = PR_TRUE;
       }
     }
   } else {
     *aOffset = aForward ? 0 : mContentLength;
   }
   return PR_TRUE;
@@ -4302,18 +4286,17 @@ FindFirstLetterRange(const nsTextFragmen
       break;
   }
 
   if (i == length)
     return PR_FALSE;
 
   // Advance to the end of the cluster (when i+1 starts a new cluster)
   while (i + 1 < length) {
-    PRUint8 flags = aTextRun->GetCharFlags(aOffset + i + 1);
-    if (flags & gfxTextRun::CLUSTER_START)
+    if (aTextRun->IsClusterStart(aOffset + i + 1))
       break;
   }
   *aLength = i + 1;
   return PR_TRUE;
 }
 
 static void
 AddCharToMetrics(gfxFloat aWidth, PropertyProvider* aProvider,
@@ -4354,98 +4337,93 @@ static nsRect ConvertGfxRectOutward(cons
   r.x = NSToCoordFloor(aRect.X());
   r.y = NSToCoordFloor(aRect.Y());
   r.width = NSToCoordCeil(aRect.XMost()) - r.x;
   r.height = NSToCoordCeil(aRect.YMost()) - r.y;
   return r;
 }
 
 static PRUint32
-GetLengthOfTrimmedText(const nsTextFragment* aFrag,
-                       PRUint32 aStart, PRUint32 aEnd,
-                       gfxSkipCharsIterator* aIterator)
+FindStartAfterSkippingWhitespace(PropertyProvider* aProvider,
+                                 nsIFrame::InlineIntrinsicWidthData* aData,
+                                 PRBool aCollapseWhitespace,
+                                 gfxSkipCharsIterator* aIterator,
+                                 PRUint32 aFlowEndInTextRun)
 {
-  aIterator->SetSkippedOffset(aEnd);
-  while (aIterator->GetSkippedOffset() > aStart) {
-    aIterator->AdvanceSkipped(-1);
-    if (!IsSpace(aFrag, aIterator->GetOriginalOffset()))
-      return aIterator->GetSkippedOffset() + 1 - aStart;
-  }
-  return 0;
+  if (aData->skipWhitespace && aCollapseWhitespace) {
+    while (aIterator->GetSkippedOffset() < aFlowEndInTextRun &&
+           IsSpace(aProvider->GetFragment(), aIterator->GetOriginalOffset())) {
+      aIterator->AdvanceSkipped(1);
+    }
+  }
+  return aIterator->GetSkippedOffset();  
 }
 
 // XXX this doesn't handle characters shaped by line endings. We need to
 // temporarily override the "current line ending" settings.
 void
 nsTextFrame::AddInlineMinWidthForFlow(nsIRenderingContext *aRenderingContext,
                                       nsIFrame::InlineMinWidthData *aData)
 {
   PRUint32 flowEndInTextRun;
   gfxSkipCharsIterator iter =
     EnsureTextRun(aRenderingContext, nsnull, nsnull, &flowEndInTextRun);
   if (!mTextRun)
     return;
 
-  PRUint32 start = iter.GetSkippedOffset();
+  PropertyProvider provider(mTextRun, GetStyleText(), mContent->GetText(), this,
+                            iter, GetInFlowContentLength());
+
+  PRBool collapseWhitespace = provider.GetStyleText()->WhiteSpaceIsSignificant();
+  PRUint32 start =
+    FindStartAfterSkippingWhitespace(&provider, aData, collapseWhitespace,
+                                     &iter, flowEndInTextRun);
   if (start >= flowEndInTextRun)
     return;
-  PRUint32 length = flowEndInTextRun - start;
-
-  PropertyProvider provider(this, iter);
-  RunCharFlags flags(start, length, mTextRun);  
-  nscoord maxAdvance = NSToCoordCeil(
-    GetFontMetrics(provider.GetFontGroup()).maxAdvance*mTextRun->GetAppUnitsPerDevUnit());
-
-  PRBool leadingWord = PR_TRUE;
-  if (flags.GetFlags(0) & gfxTextRun::LINE_BREAK_BEFORE) {
+
+  if (mTextRun->CanBreakLineBefore(start)) {
     aData->Break(aRenderingContext);
-    leadingWord = PR_FALSE;
   }
 
   PRUint32 i;
   PRUint32 wordStart = start;
   // XXX Should we consider hyphenation here?
-  for (i = 1; i < length; ++i) {
-    if (flags.GetFlags(i) & gfxTextRun::LINE_BREAK_BEFORE) {
-      // Don't measure words that are entirely inside this frame and
-      // can't be longer than the current min-width
-      PRUint32 wordLength = i - wordStart;
-      if (!leadingWord || nscoord(wordLength*maxAdvance) > aData->prevLines) {
-        if (!GetStyleText()->WhiteSpaceIsSignificant()) {
-          // Drop whitespace which would be trimmed if this were the end of the line
-          wordLength =
-            GetLengthOfTrimmedText(provider.GetFragment(), wordStart, i, &iter);
-        }
-        if (wordLength > 0) {
-          // We might have had trailingWhitespace coming into this frame, so
-          // clear that now because we have non-whitespace following it.
-          aData->trailingWhitespace = 0;
-          aData->currentLine +=
-            NSToCoordCeil(mTextRun->GetAdvanceWidth(wordStart, wordLength, &provider));
-          aData->Break(aRenderingContext);
-        } else if (leadingWord) {
-          aData->Break(aRenderingContext);
-        }
-        leadingWord = PR_FALSE;
+  for (i = start + 1; i <= flowEndInTextRun; ++i) {
+    if (i < flowEndInTextRun && !mTextRun->CanBreakLineBefore(i))
+      continue;
+
+    nscoord width =
+      NSToCoordCeil(mTextRun->GetAdvanceWidth(wordStart, i - wordStart, &provider));
+    aData->currentLine += width;
+
+    if (collapseWhitespace) {
+      nscoord trailingWhitespaceWidth;
+      PRUint32 lengthAfterTrim =
+        GetLengthOfTrimmedText(provider.GetFragment(), wordStart, i, &iter);
+      if (lengthAfterTrim == 0) {
+        trailingWhitespaceWidth = width;
+      } else {
+        PRUint32 trimStart = wordStart + lengthAfterTrim;
+        trailingWhitespaceWidth =
+          NSToCoordCeil(mTextRun->GetAdvanceWidth(trimStart, i - trimStart, &provider));
       }
+      aData->trailingWhitespace += trailingWhitespaceWidth;
+    } else {
+      aData->trailingWhitespace = 0;
+    }
+    if (i < flowEndInTextRun) {
+      aData->Break(aRenderingContext);
       wordStart = i;
     }
   }
 
-  aData->currentLine +=
-    NSToCoordCeil(mTextRun->GetAdvanceWidth(wordStart, length - wordStart, &provider));
-
-  if (!GetStyleText()->WhiteSpaceIsSignificant()) {
-    // Measure whitespace which would be trimmed if this were the end of the line
-    PRUint32 wordEnd = wordStart +
-      GetLengthOfTrimmedText(provider.GetFragment(), wordStart, flowEndInTextRun, &iter);
-    gfxFloat width = mTextRun->GetAdvanceWidth(wordEnd,
-                                               flowEndInTextRun - wordEnd, &provider);
-    aData->trailingWhitespace += NSToCoordCeil(width);
-  }
+  // Check if we have whitespace at the end
+  aData->skipWhitespace =
+    IsSpace(provider.GetFragment(),
+            iter.ConvertSkippedToOriginal(flowEndInTextRun - 1));
 }
 
 // XXX Need to do something here to avoid incremental reflow bugs due to
 // first-line and first-letter changing min-width
 /* virtual */ void
 nsTextFrame::AddInlineMinWidth(nsIRenderingContext *aRenderingContext,
                                nsIFrame::InlineMinWidthData *aData)
 {
@@ -4472,53 +4450,68 @@ nsTextFrame::AddInlinePrefWidthForFlow(n
                                        nsIFrame::InlinePrefWidthData *aData)
 {
   PRUint32 flowEndInTextRun;
   gfxSkipCharsIterator iter =
     EnsureTextRun(aRenderingContext, nsnull, nsnull, &flowEndInTextRun);
   if (!mTextRun)
     return;
 
-  PropertyProvider provider(this, iter);
-  if (!GetStyleText()->WhiteSpaceIsSignificant()) {
-    // Everything would go onto one line, so just measure it
-    PRUint32 start = iter.GetSkippedOffset();
-    if (start >= flowEndInTextRun)
-      return;
-    PRUint32 trimmedLength =
+  PropertyProvider provider(mTextRun, GetStyleText(), mContent->GetText(), this,
+                            iter, GetInFlowContentLength());
+
+  PRBool collapseWhitespace = provider.GetStyleText()->WhiteSpaceIsSignificant();
+  PRUint32 start =
+    FindStartAfterSkippingWhitespace(&provider, aData, collapseWhitespace,
+                                     &iter, flowEndInTextRun);
+  if (start >= flowEndInTextRun)
+    return;
+
+  if (collapseWhitespace) {
+    // \n line breaks are not honoured, so everything would like to go
+    // onto one line, so just measure it
+    PRUint32 lengthAfterTrim =
       GetLengthOfTrimmedText(provider.GetFragment(), start, flowEndInTextRun, &iter);
     aData->currentLine +=
-      NSToCoordCeil(mTextRun->GetAdvanceWidth(start, trimmedLength, &provider));
-
-    PRUint32 trimmedEnd = start + trimmedLength;
-    nscoord trimWidth = NSToCoordCeil(mTextRun->GetAdvanceWidth(trimmedEnd, flowEndInTextRun - trimmedEnd, &provider));
-    if (trimmedLength == 0) {
+      NSToCoordCeil(mTextRun->GetAdvanceWidth(start, lengthAfterTrim, &provider));
+
+    PRUint32 trimStart = start + lengthAfterTrim;
+    nscoord trimWidth =
+      NSToCoordCeil(mTextRun->GetAdvanceWidth(trimStart, flowEndInTextRun - trimStart, &provider));
+    if (lengthAfterTrim == 0) {
+      // This is *all* trimmable whitespace, so whatever trailingWhitespace
+      // we saw previously is still trailing...
       aData->trailingWhitespace += trimWidth;
     } else {
+      // Some non-whitespace so the old trailingWhitespace is no longer trailing
       aData->trailingWhitespace = trimWidth;
     }
   } else {
     // We respect line breaks, so measure off each line (or part of line).
     PRInt32 end = mContentOffset + GetInFlowContentLength();
-    PRUint32 startRun = iter.GetSkippedOffset();
-    const nsTextFragment* frag = GetContent()->GetText();
+    PRUint32 startRun = start;
+    aData->trailingWhitespace = 0;
     while (iter.GetOriginalOffset() < end) {
-      if (frag->CharAt(iter.GetOriginalOffset()) == '\n') {
+      if (provider.GetFragment()->CharAt(iter.GetOriginalOffset()) == '\n') {
         PRUint32 endRun = iter.GetSkippedOffset();
         aData->currentLine +=
           NSToCoordCeil(mTextRun->GetAdvanceWidth(startRun, endRun - startRun, &provider));
         aData->Break(aRenderingContext);
         startRun = endRun;
       }
       iter.AdvanceOriginal(1);
     }
     aData->currentLine +=
       NSToCoordCeil(mTextRun->GetAdvanceWidth(startRun, iter.GetSkippedOffset() - startRun, &provider));
-    aData->trailingWhitespace = 0;
-  }
+  }
+
+  // Check if we have whitespace at the end
+  aData->skipWhitespace =
+    IsSpace(provider.GetFragment(),
+            iter.ConvertSkippedToOriginal(flowEndInTextRun - 1));
 }
 
 // XXX Need to do something here to avoid incremental reflow bugs due to
 // first-line and first-letter changing pref-width
 /* virtual */ void
 nsTextFrame::AddInlinePrefWidth(nsIRenderingContext *aRenderingContext,
                                 nsIFrame::InlinePrefWidthData *aData)
 {
@@ -4661,16 +4654,23 @@ nsTextFrame::Reflow(nsPresContext*      
 
   // Restrict preformatted text to the nearest newline
   PRInt32 newLineOffset = -1;
   if (textStyle->WhiteSpaceIsSignificant()) {
     newLineOffset = FindChar(frag, offset, length, '\n');
     if (newLineOffset >= 0) {
       length = newLineOffset + 1 - offset;
     }
+  } else {
+    if (atStartOfLine) {
+      // Skip leading whitespace
+      PRInt32 whitespaceCount = GetWhitespaceCount(frag, offset, length, 1);
+      offset += whitespaceCount;
+      length -= whitespaceCount;
+    }
   }
 
   // Restrict to just the first-letter if necessary
   PRBool completedFirstLetter = PR_FALSE;
   if (lineLayout.GetFirstLetterStyleOK()) {
     AddStateBits(TEXT_FIRST_LETTER);
     completedFirstLetter = FindFirstLetterRange(frag, mTextRun, offset, &length);
   }
@@ -4703,121 +4703,105 @@ nsTextFrame::Reflow(nsPresContext*      
   } else {
     PRBool trailingTextFrameCanWrap;
     nsIFrame* lastTextFrame = lineLayout.GetTrailingTextFrame(&trailingTextFrameCanWrap);
     if (!lastTextFrame) {
       suppressInitialBreak = PR_TRUE;
     }
   }
 
-  PRInt32 lastBreak;
-  PRInt32 charsFit;
-  PRBool usedHyphenation;
-  if (lineLayout.HaveForcedBreakPosition()) {
-    // line layout is telling us where to break (because in some previous
-    // pass we told it we could break there).
-    PRInt32 forceBreakAt = lineLayout.GetForcedBreakPosition(mContent);
-    if (forceBreakAt >= offset && forceBreakAt <= offset + length) {
-      charsFit = forceBreakAt - offset;
-      end.SetOriginalOffset(forceBreakAt);
-      if (charsFit == length) {
-        // Break after trailing character, not hyphenated.
-        usedHyphenation = PR_FALSE;
-      } else {
-        // See if there's a hyphenation break at the break point
-        if (end.IsOriginalCharSkipped()) {
-          usedHyphenation = PR_FALSE;
-        } else {
-          PRPackedBool breakData;
-          provider.GetHyphenationBreaks(end.GetSkippedOffset(), 1, &breakData);
-          usedHyphenation = breakData;
-        }
-      }
-    } else {
-      charsFit = length;
-      end.SetOriginalOffset(offset + charsFit);
-      usedHyphenation = PR_FALSE;
-    }
-    PRUint32 transformedLen = GetSkippedDistance(provider.GetStart(), end);
-    mTextRun->SetLineBreaks(transformedOffset, transformedLen,
-                            (GetStateBits() & TEXT_START_OF_LINE) != 0,
-                            charsFit < length, &provider, nsnull);
-    textMetrics =
-      mTextRun->MeasureText(transformedOffset, transformedLen,
-                            needTightBoundingBox, &provider);
-    lastBreak = -1;
+  PRInt32 limitLength = length;
+  PRInt32 forceBreak = lineLayout.GetForcedBreakPosition(mContent);
+  if (forceBreak >= 0) {
+    limitLength = forceBreak - offset;
+    NS_ASSERTION(limitLength >= 0, "Weird break found!");
+  }
+  // This is the heart of text reflow right here! We don't know where
+  // to break, so we need to see how much text fits in the available width.
+  PRUint32 transformedLength;
+  if (offset + limitLength >= PRInt32(frag->GetLength())) {
+    NS_ASSERTION(offset + limitLength == PRInt32(frag->GetLength()),
+                 "Content offset/length out of bounds");
+    NS_ASSERTION(flowEndInTextRun >= transformedOffset,
+                 "Negative flow length?");
+    transformedLength = flowEndInTextRun - transformedOffset;
   } else {
-    // This is the heart of text reflow right here! We don't know where
-    // to break, so we need to see how much text fits in the available width.
-    PRUint32 transformedLength;
-    if (offset + length >= PRInt32(frag->GetLength())) {
-      NS_ASSERTION(offset + length == PRInt32(frag->GetLength()),
-                   "Content offset/length out of bounds");
-      NS_ASSERTION(flowEndInTextRun >= transformedOffset,
-                   "Negative flow length?");
-      transformedLength = flowEndInTextRun - transformedOffset;
-    } else {
-      // we're not looking at all the content, so we need to compute the
-      // length of the transformed substring we're looking at
-      gfxSkipCharsIterator iter(provider.GetStart());
-      iter.SetOriginalOffset(offset + length);
-      transformedLength = iter.GetSkippedOffset() - transformedOffset;
-    }
-    PRUint32 transformedLastBreak = 0;
-    PRUint32 transformedCharsFit =
-      mTextRun->BreakAndMeasureText(transformedOffset, transformedLength,
-                                    (GetStateBits() & TEXT_START_OF_LINE) != 0,
-                                    aReflowState.availableWidth,
-                                    &provider, suppressInitialBreak,
-                                    &textMetrics, needTightBoundingBox,
-                                    &usedHyphenation, &transformedLastBreak);
-    end.SetSkippedOffset(transformedOffset + transformedCharsFit);
-    charsFit = end.GetOriginalOffset() - offset;
-    // That might have taken us beyond our assigned content range, so get back
-    // in.
-    lastBreak = -1;
-    if (charsFit >= length) {
-      charsFit = length;
-      if (transformedLastBreak != PR_UINT32_MAX) {
-        // lastBreak is needed. Use the "end" iterator for this because
-        // it's likely to be close to the desired point
-        end.SetSkippedOffset(transformedOffset + transformedLastBreak);
-        // This may set lastBreak greater than 'length', but that's OK
-        lastBreak = end.GetOriginalOffset();
-        // Reset 'end' to the correct offset.
-        end.SetOriginalOffset(offset + charsFit);
-      }
+    // we're not looking at all the content, so we need to compute the
+    // length of the transformed substring we're looking at
+    gfxSkipCharsIterator iter(provider.GetStart());
+    iter.SetOriginalOffset(offset + limitLength);
+    transformedLength = iter.GetSkippedOffset() - transformedOffset;
+  }
+  PRUint32 transformedLastBreak = 0;
+  PRBool usedHyphenation;
+  PRUint32 transformedCharsFit =
+    mTextRun->BreakAndMeasureText(transformedOffset, transformedLength,
+                                  (GetStateBits() & TEXT_START_OF_LINE) != 0,
+                                  aReflowState.availableWidth,
+                                  &provider, suppressInitialBreak,
+                                  &textMetrics, needTightBoundingBox,
+                                  &usedHyphenation, &transformedLastBreak);
+  end.SetSkippedOffset(transformedOffset + transformedCharsFit);
+  PRInt32 charsFit = end.GetOriginalOffset() - offset;
+  // That might have taken us beyond our assigned content range, so get back
+  // in.
+  PRInt32 lastBreak = -1;
+  if (charsFit >= limitLength) {
+    charsFit = limitLength;
+    if (transformedLastBreak != PR_UINT32_MAX) {
+      // lastBreak is needed. Use the "end" iterator for this because
+      // it's likely to be close to the desired point
+      end.SetSkippedOffset(transformedOffset + transformedLastBreak);
+      // This may set lastBreak greater than 'length', but that's OK
+      lastBreak = end.GetOriginalOffset();
+      // Reset 'end' to the correct offset.
+      end.SetOriginalOffset(offset + charsFit);
     }
   }
   if (usedHyphenation) {
     // Fix up metrics to include hyphen
     AddCharToMetrics(provider.GetHyphenWidth(), &provider, gfxFontGroup::STRING_HYPHEN,
                      mTextRun, &textMetrics, needTightBoundingBox);
     AddStateBits(TEXT_HYPHEN_BREAK);
   }
 
-  // if it's only whitespace that didn't fit, then we will expand to include
-  // the whitespace in this frame.
-  PRBool suckedUpWhitespace = PR_FALSE;
+  // If it's only whitespace that didn't fit, then we will include
+  // the whitespace in this frame, but we will eagerly trim all trailing
+  // whitespace from our width calculations. Basically we do
+  // TrimTrailingWhitespace early so that line layout sees that we fit on
+  // the line.
   if (charsFit < length) {
-    PRInt32 whitespaceCount = GetWhitespaceCount(frag, offset + charsFit, length - charsFit, 1);
+    PRInt32 whitespaceCount =
+      GetWhitespaceCount(frag, offset + charsFit, length - charsFit, 1);
     if (whitespaceCount > 0) {
       charsFit += whitespaceCount;
+
+      // Now trim all trailing whitespace from our width, including possibly
+      // even whitespace that fitted (which could only happen with
+      // white-space:pre-wrap, because when whitespace collapsing is in effect
+      // there can only be one whitespace character rendered at the end of
+      // the frame)
+      AddStateBits(TEXT_TRIMMED_TRAILING_WHITESPACE);
       PRUint32 currentEnd = end.GetSkippedOffset();
+      PRUint32 trimmedEnd = transformedOffset +
+        GetLengthOfTrimmedText(frag, transformedOffset, currentEnd, &end);
+      textMetrics.mAdvanceWidth -=
+        mTextRun->GetAdvanceWidth(trimmedEnd,
+                                  currentEnd - trimmedEnd, &provider);
+      // XXX We don't adjust the bounding box in textMetrics. But we should! Do
+      // we need to remeasure the text? Maybe the metrics returned by the textrun
+      // should have a way of saying "no glyphs outside their font-boxes" so
+      // we know we don't need to adjust the bounding box here and elsewhere...
       end.SetOriginalOffset(offset + charsFit);
-      gfxFloat width = mTextRun->GetAdvanceWidth(currentEnd,
-          end.GetSkippedOffset() - currentEnd, &provider);
-      AddCharToMetrics(width, &provider, gfxFontGroup::STRING_SPACE, mTextRun, &textMetrics,
-                       needTightBoundingBox);
-      suckedUpWhitespace = PR_TRUE;
     }
   } else {
     // All text fit. Record the last potential break, if there is one.
     if (lastBreak >= 0) {
-      lineLayout.NotifyOptionalBreakPosition(mContent, lastBreak);
+      lineLayout.NotifyOptionalBreakPosition(mContent, lastBreak,
+          textMetrics.mAdvanceWidth < aReflowState.availableWidth);
     }
   }
   mContentLength = offset + charsFit - mContentOffset;
 
   /////////////////////////////////////////////////////////////////////
   // Compute output metrics
   /////////////////////////////////////////////////////////////////////
 
@@ -4874,17 +4858,17 @@ nsTextFrame::Reflow(nsPresContext*      
   if (charsFit > 0) {
     endsInWhitespace = IsSpace(frag, offset + charsFit - 1);
     lineLayout.SetInWord(!endsInWhitespace);
     lineLayout.SetEndsInWhiteSpace(endsInWhitespace);
     PRBool wrapping = textStyle->WhiteSpaceCanWrap();
     lineLayout.SetTrailingTextFrame(this, wrapping);
     if (charsFit == length && endsInWhitespace && wrapping) {
       // Record a potential break after final breakable whitespace
-      lineLayout.NotifyOptionalBreakPosition(mContent, offset + length);
+      lineLayout.NotifyOptionalBreakPosition(mContent, offset + length, PR_TRUE);
     }
   } else {
     // Don't allow subsequent text frame to break-before. All our text is       
     // being skipped (usually whitespace, could be discarded Unicode control    
     // characters).
     lineLayout.SetEndsInWhiteSpace(PR_FALSE);
     lineLayout.SetTrailingTextFrame(nsnull, PR_FALSE);
   }
@@ -4898,22 +4882,16 @@ nsTextFrame::Reflow(nsPresContext*      
 
   if (charsFit == 0 && length > 0) {
     // Couldn't place any text
     aStatus = NS_INLINE_LINE_BREAK_BEFORE();
   } else if (mContentLength > 0 && mContentLength - 1 == newLineOffset) {
     // Ends in \n
     aStatus = NS_INLINE_LINE_BREAK_AFTER(aStatus);
     lineLayout.SetLineEndsInBR(PR_TRUE);
-  } else if (suckedUpWhitespace && aStatus == NS_FRAME_COMPLETE) {
-    // Flag a soft-break that we can check (below) if we come back here
-    lineLayout.SetLineEndsInSoftBR(PR_TRUE);
-  } else if (lineLayout.GetLineEndsInSoftBR() && !lineLayout.GetEndsInWhiteSpace()) {
-    // Break-before a word that follows the soft-break flagged earlier
-    aStatus = NS_INLINE_LINE_BREAK_BEFORE();
   }
 
   // Compute space and letter counts for justification, if required
   if (NS_STYLE_TEXT_ALIGN_JUSTIFY == textStyle->mTextAlign &&
       !textStyle->WhiteSpaceIsSignificant()) {
     // This will include a space for trailing whitespace, if any is present.
     // This is corrected for in nsLineLayout::TrimWhiteSpaceIn.
     PRInt32 numJustifiableCharacters =
@@ -4963,55 +4941,55 @@ nsTextFrame::TrimTrailingWhiteSpace(nsPr
     return NS_OK;
 
   gfxSkipCharsIterator iter = EnsureTextRun(&aRC);
   if (!mTextRun)
     return NS_ERROR_FAILURE;
   PRUint32 trimmedStart = iter.GetSkippedOffset();
 
   const nsTextFragment* frag = mContent->GetText();
-  PRInt32 length = GetTrimmedContentLength(frag, iter);
-  PRUint32 trimmedEnd = iter.ConvertOriginalToSkipped(mContentOffset + length);
-
+  TrimmedOffsets trimmed = GetTrimmedOffsets(frag, PR_TRUE);
+  PRUint32 trimmedEnd = iter.ConvertOriginalToSkipped(trimmed.mStart + trimmed.mLength);
   const nsStyleText* textStyle = GetStyleText();
   gfxFloat delta = 0;
-  if (length < mContentLength) {
-    PropertyProvider provider(mTextRun, textStyle, frag, this, iter, mContentLength);
-    PRUint32 end = iter.ConvertOriginalToSkipped(mContentOffset + mContentLength);
-    if (trimmedEnd < end) {
-      delta = mTextRun->GetAdvanceWidth(trimmedEnd, end - trimmedEnd, &provider);
+  PropertyProvider provider(mTextRun, textStyle, frag, this, iter, mContentLength);
+
+  if (GetStateBits() & TEXT_TRIMMED_TRAILING_WHITESPACE) {
+    aLastCharIsJustifiable = PR_TRUE;
+  } else if (trimmed.mStart + trimmed.mLength < mContentOffset + mContentLength) {
+    gfxSkipCharsIterator end = iter;
+    PRUint32 endOffset = end.ConvertOriginalToSkipped(mContentOffset + mContentLength);
+    if (trimmedEnd < endOffset) {
+      delta = mTextRun->GetAdvanceWidth(trimmedEnd, endOffset - trimmedEnd, &provider);
       // non-compressed whitespace being skipped at end of line -> justifiable
       // XXX should we actually *count* justifiable characters that should be
-      // removed from the overall count?
+      // removed from the overall count? I think so...
       aLastCharIsJustifiable = PR_TRUE;
     }
   }
 
   if (!aLastCharIsJustifiable &&
       NS_STYLE_TEXT_ALIGN_JUSTIFY == textStyle->mTextAlign) {
     // Check if any character in the last cluster is justifiable
-    PropertyProvider provider(mTextRun, textStyle, frag, this, iter, mContentLength);
     PRBool isCJK = IsChineseJapaneseLangGroup(this);
     gfxSkipCharsIterator justificationEnd(iter);
     provider.FindEndOfJustificationRange(&justificationEnd);
 
     PRInt32 i;
-    for (i = justificationEnd.GetOriginalOffset(); i < mContentOffset + length; ++i) {
+    for (i = justificationEnd.GetOriginalOffset(); i < trimmed.mStart + trimmed.mLength; ++i) {
       if (IsJustifiableCharacter(frag, i, isCJK)) {
         aLastCharIsJustifiable = PR_TRUE;
       }
     }
   }
 
-  StaticTextProvider textProvider(mTextRun);
-
   gfxFloat advanceDelta;
   mTextRun->SetLineBreaks(trimmedStart, trimmedEnd - trimmedStart,
                           (GetStateBits() & TEXT_START_OF_LINE) != 0, PR_TRUE,
-                          &textProvider, &advanceDelta);
+                          &provider, &advanceDelta);
 
   // aDeltaWidth is *subtracted* from our width.
   // If advanceDelta is positive then setting the line break made us longer,
   // so aDeltaWidth could go negative.
   aDeltaWidth = NSToCoordFloor(delta - advanceDelta);
   // XXX if aDeltaWidth goes negative, that means this frame might not actually fit
   // anymore!!! We need higher level line layout to recover somehow. This can
   // really only happen when we have glyphs with special shapes at the end of
@@ -5156,16 +5134,17 @@ nsTextFrame::List(FILE* out, PRInt32 aIn
   fprintf(out, " {%d,%d,%d,%d}", mRect.x, mRect.y, mRect.width, mRect.height);
   if (0 != mState) {
     if (mState & NS_FRAME_SELECTED_CONTENT) {
       fprintf(out, " [state=%08x] SELECTED", mState);
     } else {
       fprintf(out, " [state=%08x]", mState);
     }
   }
+  fprintf(out, " [content=%p]", NS_STATIC_CAST(void*, mContent));
   fprintf(out, " sc=%p", NS_STATIC_CAST(void*, mStyleContext));
   nsIAtom* pseudoTag = mStyleContext->GetPseudoType();
   if (pseudoTag) {
     nsAutoString atomString;
     pseudoTag->ToString(atomString);
     fprintf(out, " pst=%s",
             NS_LossyConvertUTF16toASCII(atomString).get());
   }
--- a/layout/generic/nsTextFrameUtils.cpp
+++ b/layout/generic/nsTextFrameUtils.cpp
@@ -57,16 +57,18 @@
 // characters of the following Unicode character classes : Ps, Pi, Po, Pf, Pe.
 // (ref.: http://www.w3.org/TR/2004/CR-CSS21-20040225/selector.html#first-letter)
 // Note that the file does NOT yet include non-BMP characters because 
 // there's no point including them without fixing the way we identify 
 // 'first-letter' currently working only with BMP characters.
 #include "punct_marks.ccmap"
 DEFINE_CCMAP(gPuncCharsCCMap, const);
   
+#define UNICODE_ZWSP 0x200B
+  
 PRBool
 nsTextFrameUtils::IsPunctuationMark(PRUnichar aChar)
 {
   return CCMAP_HAS_CHAR(gPuncCharsCCMap, aChar);
 }
 
 static PRBool IsDiscardable(PRUnichar ch, PRUint32* aFlags)
 {
@@ -101,17 +103,17 @@ nsTextFrameUtils::TransformText(const PR
                                 gfxSkipCharsBuilder* aSkipChars,
                                 PRUint32* aAnalysisFlags)
 {
   // We're just going to assume this!
   PRUint32 flags = TEXT_HAS_NON_ASCII;
   PRUnichar* outputStart = aOutput;
 
   if (!aCompressWhitespace) {
-    // Convert tabs to spaces and skip discardables.
+    // Convert tabs and formfeeds to spaces and skip discardables.
     PRUint32 i;
     for (i = 0; i < aLength; ++i) {
       PRUnichar ch = *aText++;
       if (ch == '\t') {
         flags |= TEXT_HAS_TAB|TEXT_WAS_TRANSFORMED;
         aSkipChars->KeepChar();
         *aOutput++ = ' ';
       } else if (IsDiscardable(ch, &flags)) {
@@ -130,29 +132,29 @@ nsTextFrameUtils::TransformText(const PR
   } else {
     PRBool inWhitespace = *aIncomingWhitespace;
     PRUint32 i;
     for (i = 0; i < aLength; ++i) {
       PRUnichar ch = *aText++;
       PRBool nowInWhitespace;
       if (ch == ' ' &&
           (i + 1 >= aLength ||
-           !IsSpaceCombiningSequenceTail(&aText[1], aLength - (i + 1)))) {
+           !IsSpaceCombiningSequenceTail(aText, aLength - (i + 1)))) {
         nowInWhitespace = PR_TRUE;
       } else if (ch == '\n') {
         if (i > 0 && IS_CJ_CHAR(aText[-1]) &&
             i + 1 < aLength && IS_CJ_CHAR(aText[1])) {
           // Discard newlines between CJK chars.
           // XXX this really requires more context to get right!
           aSkipChars->SkipChar();
           continue;
         }
         nowInWhitespace = PR_TRUE;
       } else {
-        nowInWhitespace = ch == '\t';
+        nowInWhitespace = ch == '\t' || ch == '\f' || ch == UNICODE_ZWSP;
       }
 
       if (!nowInWhitespace) {
         if (IsDiscardable(ch, &flags)) {
           aSkipChars->SkipChar();
           nowInWhitespace = inWhitespace;
         } else {
           if (ch == CH_NBSP) {
@@ -223,17 +225,17 @@ nsTextFrameUtils::TransformText(const PR
       }
     }
   } else {
     PRBool inWhitespace = *aIncomingWhitespace;
     PRUint32 i;
     for (i = 0; i < aLength; ++i) {
       PRUint8 ch = *aText++;
       allBits |= ch;
-      PRBool nowInWhitespace = ch == ' ' || ch == '\t' || ch == '\n';
+      PRBool nowInWhitespace = ch == ' ' || ch == '\t' || ch == '\n' || ch == '\f';
       if (!nowInWhitespace) {
         if (IsDiscardable(ch, &flags)) {
           aSkipChars->SkipChar();
           nowInWhitespace = inWhitespace;
         } else {
           if (ch == CH_NBSP) {
             ch = ' ';
             flags |= TEXT_WAS_TRANSFORMED;
@@ -275,16 +277,17 @@ enum SimpleCharClass {
   CLASS_ALNUM,
   CLASS_PUNCT,
   CLASS_SPACE
 };
 
 // This is what nsSampleWordBreaker::GetClass considers whitespace
 static PRBool IsWordBreakerWhitespace(const PRUnichar* aChars, PRInt32 aLength)
 {
+  NS_ASSERTION(aLength > 0, "Can't handle empty string");
   PRUnichar ch = aChars[0];
   if (ch == '\t' || ch == '\n' || ch == '\r')
     return PR_TRUE;
   if (ch == ' ' &&
       !nsTextFrameUtils::IsSpaceCombiningSequenceTail(aChars + 1, aLength - 1))
     return PR_TRUE;
   return PR_FALSE;
 }
@@ -307,85 +310,94 @@ nsTextFrameUtils::FindWordBoundary(const
                                    PRInt32 aOffset, PRInt32 aLength,
                                    PRInt32 aPosition, PRInt32 aDirection,
                                    PRBool aBreakBeforePunctuation,
                                    PRBool aBreakAfterPunctuation,
                                    PRBool* aWordIsWhitespace)
 {
   // A space followed by combining diacritical marks is not whitespace!!
   PRInt32 textLength = aText->GetLength();
+  NS_ASSERTION(aOffset + aLength <= textLength,
+               "Substring out of bounds");
+  NS_ASSERTION(aPosition >= aOffset && aPosition < aOffset + aLength,
+               "Position out of bounds");
   *aWordIsWhitespace = aText->Is2b()
     ? IsWordBreakerWhitespace(aText->Get2b() + aPosition, textLength - aPosition)
     : Classify8BitChar(aText->Get1b()[aPosition]) == CLASS_SPACE;
 
-  PRInt32 nextWordPos; // first character of next/prev "word"
+  PRInt32 len = 0; // length of current "word", excluding first character at aPosition
   if (aText->Is2b()) {
     nsIWordBreaker* wordBreaker = nsContentUtils::WordBreaker();
     const PRUnichar* text = aText->Get2b();
-    PRInt32 pos = aPosition;
     // XXX the wordbreaker currently isn't cluster-aware. We need to make
     // it cluster-aware. In the meantime, just reject any word breaks
     // inside clusters.
     for (;;) {
-      nextWordPos = aDirection > 0
-        ? wordBreaker->NextWord(text, textLength, pos)
-        : wordBreaker->PrevWord(text, textLength, pos);
-      if (nextWordPos < 0)
+      if (aDirection > 0) {
+        // Returns the index of the first character of the next word
+        PRInt32 nextWordPos = wordBreaker->NextWord(text, textLength, aPosition + len);
+        if (nextWordPos < 0)
+          break;
+        len = nextWordPos - aPosition - 1;
+      } else {
+        // Returns the index of the first character of the current word
+        PRInt32 nextWordPos = wordBreaker->PrevWord(text, textLength, aPosition + len);
+        if (nextWordPos < 0)
+          break;
+        len = aPosition - nextWordPos;
+      }
+      if (aTextRun->IsClusterStart(aIterator->ConvertOriginalToSkipped(aPosition + len*aDirection)))
         break;
-      if (aTextRun->GetCharFlags(aIterator->ConvertOriginalToSkipped(nextWordPos)) & gfxTextRun::CLUSTER_START)
-        break;
-      pos = nextWordPos;
     }
   } else {
     const char* text = aText->Get1b();
     SimpleCharClass cl = Classify8BitChar(text[aPosition]);
-    nextWordPos = aPosition;
     // There shouldn't be any clusters in 8bit text but we'll cover that
     // possibility anyway
+    PRInt32 nextWordPos;
     do {
-      nextWordPos += aDirection;
-      if (nextWordPos < aOffset || nextWordPos >= aOffset + aLength) {
-        nextWordPos = NS_WORDBREAKER_NEED_MORE_TEXT;
+      ++len;
+      nextWordPos = aPosition + aDirection*len;
+      if (nextWordPos < aOffset || nextWordPos >= aOffset + aLength)
         break;
-      }
     } while (Classify8BitChar(text[nextWordPos]) == cl ||
-             !(aTextRun->GetCharFlags(aIterator->ConvertOriginalToSkipped(nextWordPos)) & gfxTextRun::CLUSTER_START));
+             !aTextRun->IsClusterStart(aIterator->ConvertOriginalToSkipped(nextWordPos)));
   }
 
   // Handle punctuation breaks
   PRInt32 i;
   PRBool punctPrev = IsPunctuationMark(aText->CharAt(aPosition));
-  for (i = aPosition + aDirection;
-       i != nextWordPos && i >= aOffset && i < aOffset + aLength;
-       i += aDirection) {
+  for (i = 1; i < len; ++i) {
+    PRInt32 pos = aPosition + i*aDirection;
     // See if there's a punctuation break between i-aDirection and i
-    PRBool punct = IsPunctuationMark(aText->CharAt(i));
+    PRBool punct = IsPunctuationMark(aText->CharAt(pos));
     if (punct != punctPrev &&
-        (aTextRun->GetCharFlags(aIterator->ConvertOriginalToSkipped(i)) & gfxTextRun::CLUSTER_START)) {
+        aTextRun->IsClusterStart(aIterator->ConvertOriginalToSkipped(pos))) {
       PRBool punctIsBefore = aDirection < 0 ? punct : punctPrev;
       if (punctIsBefore ? aBreakAfterPunctuation : aBreakBeforePunctuation)
         break;
     }
     punctPrev = punct;
   }
-  if (i < aOffset || i >= aOffset + aLength)
+  PRInt32 pos = aPosition + i*aDirection;
+  if (pos < aOffset || pos >= aOffset + aLength)
     return -1;
   return i;
 }
 
 PRBool nsSkipCharsRunIterator::NextRun() {
   do {
-    if (!mRemainingLength)
-      return PR_FALSE;
     if (mRunLength) {
       mIterator.AdvanceOriginal(mRunLength);
       NS_ASSERTION(mRunLength > 0, "No characters in run (initial length too large?)");
       if (!mSkipped || mLengthIncludesSkipped) {
         mRemainingLength -= mRunLength;
       }
     }
+    if (!mRemainingLength)
+      return PR_FALSE;
     PRInt32 length;
     mSkipped = mIterator.IsOriginalCharSkipped(&length);
     mRunLength = PR_MIN(length, mRemainingLength);
   } while (!mVisitSkipped && mSkipped);
 
   return PR_TRUE;
 }
--- a/layout/generic/nsTextFrameUtils.h
+++ b/layout/generic/nsTextFrameUtils.h
@@ -99,31 +99,28 @@ public:
                                 PRBool aCompressWhitespace,
                                 PRPackedBool* aIncomingWhitespace,
                                 gfxSkipCharsBuilder* aSkipChars,
                                 PRUint32* aAnalysisFlags);
 
   /**
    * Find a word boundary starting from a given position and proceeding either
    * forwards (aDirection == 1) or backwards (aDirection == -1). The search
-   * is limited to a substring of an nsTextFragment. We return the index
-   * of the character that is the first character of the next/prev word; the
-   * result can be aOffset <= result <= aLength (result == aLength means
-   * that there's definitely a word boundary at the end of the text), or -1 to
-   * indicate that no boundary was found.
+   * is limited to a substring of an nsTextFragment, and starts with the
+   * first character of the word (the character at aOffset). We return the length
+   * of the word.
    * 
    * @param aTextRun a text run which we will use to ensure that we don't
    * return a boundary inside a cluster
    * @param aPosition a character in the substring aOffset/aLength
    * @param aBreakBeforePunctuation if true, then we allow a word break
    * when transitioning from regular word text to punctuation (in content order)
    * @param aBreakAfterPunctuation if true, then we allow a word break
    * when transitioning from punctuation to regular word text (in content order)
-   * @param aWordIsWhitespace we set this to true if the word-part we skipped
-   * over is whitespace
+   * @param aWordIsWhitespace we set this to true if this word is whitespace
    * 
    * For the above properties, "punctuation" is defined as any ASCII character
    * which is not a letter or a digit. Regular word text is any non-whitespace
    * (here "whitespace" includes non-breaking whitespace).
    * Word break points are the punctuation breaks defined above, plus
    * for Unicode text, whatever intl's wordbreaker identifies, and for
    * ASCII text, boundaries between whitespace and non-whitespace.
    */
--- a/layout/generic/nsTextRunTransformations.cpp
+++ b/layout/generic/nsTextRunTransformations.cpp
@@ -43,1115 +43,476 @@
 
 #include "nsICaseConversion.h"
 #include "nsStyleConsts.h"
 #include "nsStyleContext.h"
 #include "gfxContext.h"
 
 #define SZLIG 0x00DF
 
-// ==============================
-// First define some useful textrun wrapper implementations
-// ==============================
-
-class TextProviderWrapper : public gfxTextRun::TextProvider {
-public:
-  virtual void ForceRememberText() {
-    NS_ERROR("We should never be asked to recover text, we already forced the textrun to remember it");
-  }
-};
-
-class ProviderWrapper : public gfxTextRun::PropertyProvider {
-public:
-  ProviderWrapper(PropertyProvider* aProvider)
-    : mInner(aProvider) {}
-
-  virtual void ForceRememberText() {
-    NS_ERROR("We should never be asked to recover text, we already forced the textrun to remember it");
-  }
-
-  // Simple forwardings
-  virtual gfxFloat GetHyphenWidth()
-  { return mInner->GetHyphenWidth(); }
-
-protected:
-  PropertyProvider* mInner;
-};
-
-class WrapperTextRun : public gfxTextRun {
-public:
-  WrapperTextRun(gfxTextRun* aInner, gfxTextRunFactory::Parameters* aParams)
-  // WrapperTextRuns always take Unicode
-    : gfxTextRun(aParams), mInner(aInner) {}
-
-protected:
-  nsAutoPtr<gfxTextRun> mInner;
-};
-
 /**
- * SZLIG is a problem. When uppercased it turns into "SS", yes, two S's.
- * 
- * This adds a special wrinkle because normally DOM text only shrinks when
- * being converted for display. Instead of burdening general DOM<->displaytext
- * string offset conversion with the possibility of SZLIG-driven growth, we put
- * all the pain right here in a special text run that knows where extra characters
- * have been inserted in a string and makes the client believe that they're not
- * there by adjusting character offsets.
- * 
- * In general this class could be used to handle text runs that are derived from
- * some base string by inserting characters.
+ * So that we can reshape as necessary, we store enough information
+ * to fully rebuild the textrun contents.
  */
-class nsGermanTextRun : public WrapperTextRun {
-public:
-  /**
-   * The gfxSkipCharsBuilder used here represents the characters that should
-   * be removed from the tranformed text in aInnerTextRun to obtain the
-   * original string.
-   */
-  nsGermanTextRun(gfxTextRun* aInnerTextRun, gfxSkipChars* aSkipChars,
-                  gfxTextRunFactory::Parameters* aParams)
-    : WrapperTextRun(aInnerTextRun, aParams)
-  {
-    mTransform.TakeFrom(aSkipChars);
-  }
-  
-  class GermanProviderWrapper : public ProviderWrapper {
-  public:
-    GermanProviderWrapper(PropertyProvider* aProvider, const nsGermanTextRun& aRun)
-      : ProviderWrapper(aProvider), mRun(aRun) {}
-
-    virtual void GetHyphenationBreaks(PRUint32 aStart, PRUint32 aLength,
-                                      PRPackedBool* aBreakBefore);
-    virtual void GetSpacing(PRUint32 aStart, PRUint32 aLength,
-                            Spacing* aSpacing);
-
-  protected:
-    const nsGermanTextRun& mRun;
-  };
-
-  // Fairly simple forwarding when just offsets or a provider have to be translated
-  virtual PRUint8 GetCharFlags(PRUint32 aOffset)
-  {
-    TranslateOffset(&aOffset);
-    return mInner->GetCharFlags(aOffset);
-  }
-  virtual PRUint32 GetLength()
-  {
-    // mTransform's 'original' characters are actually the *expanded* characters
-    return mTransform.GetOriginalCharCount();
-  }
-  virtual void Draw(gfxContext* aContext, gfxPoint aPt,
-                    PRUint32 aStart, PRUint32 aLength,
-                    const gfxRect* aDirtyRect,
-                    PropertyProvider* aBreakProvider,
-                    gfxFloat* aAdvanceWidth)
-  {
-    GermanProviderWrapper wrapper(aBreakProvider, *this);
-    TranslateSubstring(&aStart, &aLength);
-    mInner->Draw(aContext, aPt, aStart, aLength, aDirtyRect, &wrapper, aAdvanceWidth);
-  }
-  virtual void DrawToPath(gfxContext* aContext, gfxPoint aPt,
-                          PRUint32 aStart, PRUint32 aLength,
-                          PropertyProvider* aBreakProvider,
-                          gfxFloat* aAdvanceWidth)
-  {
-    GermanProviderWrapper wrapper(aBreakProvider, *this);
-    TranslateSubstring(&aStart, &aLength);
-    mInner->DrawToPath(aContext, aPt, aStart, aLength, &wrapper, aAdvanceWidth);
-  }
-  virtual Metrics MeasureText(PRUint32 aStart, PRUint32 aLength,
-                              PRBool aTightBoundingBox,
-                              PropertyProvider* aBreakProvider)
-  {
-    GermanProviderWrapper wrapper(aBreakProvider, *this);
-    TranslateSubstring(&aStart, &aLength);
-    return mInner->MeasureText(aStart, aLength, aTightBoundingBox, &wrapper);
-  }
-  virtual void SetLineBreaks(PRUint32 aStart, PRUint32 aLength,
-                             PRBool aLineBreakBefore, PRBool aLineBreakAfter,
-                             TextProvider* aProvider,
-                             gfxFloat* aAdvanceWidthDelta)
-  {
-    TextProviderWrapper dummyTextProvider;
-    TranslateSubstring(&aStart, &aLength);
-    return mInner->SetLineBreaks(aStart, aLength, aLineBreakBefore, aLineBreakAfter,
-                                 &dummyTextProvider, aAdvanceWidthDelta);
-  }
-  virtual gfxFloat GetAdvanceWidth(PRUint32 aStart, PRUint32 aLength,
-                                   PropertyProvider* aBreakProvider)
-  {
-    GermanProviderWrapper wrapper(aBreakProvider, *this);
-    TranslateSubstring(&aStart, &aLength);
-    return mInner->GetAdvanceWidth(aStart, aLength, &wrapper);
-  }
-
-  // Tricky forwarding where output conversion is required
-  virtual void GetCharFlags(PRUint32 aStart, PRUint32 aLength, PRUint8* aFlags);
-  virtual PRBool SetPotentialLineBreaks(PRUint32 aStart, PRUint32 aLength,
-                                        PRPackedBool* aBreakBefore);
-  virtual PRUint32 BreakAndMeasureText(PRUint32 aStart, PRUint32 aMaxLength,
-                                       PRBool aBreakBefore, gfxFloat aWidth,
-                                       PropertyProvider* aBreakProvider,
-                                       PRBool aSuppressInitialBreak,
-                                       Metrics* aMetrics, PRBool aTightBoundingBox,
-                                       PRBool* aUsedHyphenation,
-                                       PRUint32* aLastBreak);
-
-  friend class GermanProviderWrapper;
-
-private:
-  // Translates an original string offset to an offset in the converted string
-  void TranslateOffset(PRUint32* aOffset);
-  // Translates original string substring offsets to substring offsets in the
-  // converted string
-  void TranslateSubstring(PRUint32* aStart, PRUint32* aLength);
-  // Translates converted string substring offsets to substring offsets in the
-  // original string
-  void UntranslateSubstring(PRUint32* aStart, PRUint32* aLength) const;
-
-  gfxSkipChars mTransform;
-};
-
-void
-nsGermanTextRun::TranslateOffset(PRUint32* aOffset)
-{
-  gfxSkipCharsIterator iter(mTransform);
-  iter.SetSkippedOffset(*aOffset);
-  *aOffset = iter.GetOriginalOffset();
-}
-
-void
-nsGermanTextRun::TranslateSubstring(PRUint32* aStart, PRUint32* aLength)
-{
-  gfxSkipCharsIterator iter(mTransform);
-  PRUint32 end = *aStart + *aLength;
-  iter.SetSkippedOffset(*aStart);
-  *aStart = iter.GetOriginalOffset();
-  iter.SetSkippedOffset(end);
-  *aLength = iter.GetOriginalOffset() - *aStart;
-}
-
-void
-nsGermanTextRun::UntranslateSubstring(PRUint32* aStart, PRUint32* aLength) const
-{
-  gfxSkipCharsIterator iter(mTransform);
-  PRUint32 end = *aStart + *aLength;
-  iter.SetOriginalOffset(*aStart);
-  *aStart = iter.GetSkippedOffset();
-  iter.SetOriginalOffset(end);
-  *aLength = iter.GetSkippedOffset() - *aStart;
-}
-
-void
-nsGermanTextRun::GetCharFlags(PRUint32 aStart, PRUint32 aLength,
-                              PRUint8* aFlags)
-{
-  PRUint32 convertedStart = aStart;
-  PRUint32 convertedLength = aLength;
-  TranslateSubstring(&convertedStart, &convertedLength);
-
-  nsAutoTArray<PRUint8,BIG_TEXT_NODE_SIZE> buffer;
-  if (!buffer.AppendElements(convertedLength)) {
-    memset(aFlags, 0, aLength);
-    return;
-  }
-  mInner->GetCharFlags(convertedStart, convertedLength, buffer.Elements());
-
-  gfxSkipCharsIterator iter(mTransform);
-  nsSkipCharsRunIterator
-    run(iter, nsSkipCharsRunIterator::LENGTH_UNSKIPPED_ONLY, aLength);
-  // Iterate over the original-string runs. The client simply never sees
-  // flags for the extra Ss, since they don't exist as far as the client knows.
-  while (run.NextRun()) {
-    memcpy(aFlags + run.GetSkippedOffset() - aStart,
-           buffer.Elements() + run.GetOriginalOffset() - convertedStart,
-           run.GetRunLength());
-  }
-}
-
-PRBool
-nsGermanTextRun::SetPotentialLineBreaks(PRUint32 aStart, PRUint32 aLength,
-                                        PRPackedBool* aBreakBefore)
-{
-  PRUint32 convertedStart = aStart;
-  PRUint32 convertedLength = aLength;
-  TranslateSubstring(&convertedStart, &convertedLength);
-
-  nsAutoTArray<PRPackedBool,BIG_TEXT_NODE_SIZE> buffer;
-  if (!buffer.AppendElements(convertedLength))
-    return PR_TRUE;
-
-  gfxSkipCharsIterator iter(mTransform);
-  nsSkipCharsRunIterator
-    run(iter, nsSkipCharsRunIterator::LENGTH_UNSKIPPED_ONLY, aLength);
-  while (run.NextRun()) {
-    memcpy(buffer.Elements() + run.GetOriginalOffset() - convertedStart,
-           aBreakBefore + run.GetSkippedOffset() - aStart,
-           run.GetRunLength());
-  }
-
-  return mInner->SetPotentialLineBreaks(convertedStart, convertedLength, buffer.Elements());
-}
-
-PRUint32
-nsGermanTextRun::BreakAndMeasureText(PRUint32 aStart, PRUint32 aMaxLength,
-                                     PRBool aLineBreakBefore, gfxFloat aWidth,
-                                     PropertyProvider* aBreakProvider,
-                                     PRBool aSuppressInitialBreak,
-                                     Metrics* aMetrics, PRBool aTightBoundingBox,
-                                     PRBool* aUsedHyphenation,
-                                     PRUint32* aLastBreak)
-{
-  GermanProviderWrapper wrapper(aBreakProvider, *this);
-  PRUint32 convertedStart = aStart;
-  PRUint32 convertedLength = aMaxLength;
-  TranslateSubstring(&convertedStart, &convertedLength);
-  PRUint32 lastBreak;
-  PRUint32 result =
-    mInner->BreakAndMeasureText(convertedStart, convertedLength, aLineBreakBefore,
-        aWidth, &wrapper, aSuppressInitialBreak, aMetrics, aTightBoundingBox,
-        aUsedHyphenation, &lastBreak);
-  gfxSkipCharsIterator iter(mTransform);
-  iter.SetOriginalOffset(convertedStart + lastBreak);
-  *aLastBreak = iter.GetSkippedOffset() - aStart;
-  iter.SetOriginalOffset(convertedStart + result);
-  return iter.GetSkippedOffset() - aStart;
-}
-
-void
-nsGermanTextRun::GermanProviderWrapper::GetHyphenationBreaks(PRUint32 aStart, PRUint32 aLength,
-                                                             PRPackedBool* aBreakBefore)
-{
-  PRUint32 unconvertedStart = aStart;
-  PRUint32 unconvertedLength = aLength;
-  mRun.UntranslateSubstring(&unconvertedStart, &unconvertedLength);
-
-  // I'm being a bit clever here and reusing aBreakBefore since it's at least
-  // as big as the temporary buffer we need. We put our temporary buffer
-  // at the end of aBreakBefore so that when we start filling aBreakBefore
-  // below, we don't overwrite data we need --- until the very last memmove,
-  // which will be in-place.
-  NS_ASSERTION(aLength >= unconvertedLength,
-               "We only handle *growing* strings here");
-  PRPackedBool* buffer = aBreakBefore + aLength - unconvertedLength;
-  mInner->GetHyphenationBreaks(unconvertedStart, unconvertedLength, buffer);
-
-  gfxSkipCharsIterator iter(mRun.mTransform, aStart);
-  nsSkipCharsRunIterator
-    run(iter, nsSkipCharsRunIterator::LENGTH_INCLUDES_SKIPPED, aLength);
-  run.SetVisitSkipped();
-  while (run.NextRun()) {
-    if (run.IsSkipped()) {
-      // Disallow hyphenation inside the SS
-      memset(aBreakBefore + run.GetOriginalOffset() - aStart, PR_FALSE,
-             run.GetRunLength());
-    } else {
-      memmove(aBreakBefore + run.GetOriginalOffset() - aStart,
-              buffer + run.GetSkippedOffset() - unconvertedStart,
-              run.GetRunLength());
-    }
-  }
-}
-
-void
-nsGermanTextRun::GermanProviderWrapper::GetSpacing(PRUint32 aStart, PRUint32 aLength,
-                                                   Spacing* aSpacing)
-{
-  PRUint32 unconvertedStart = aStart;
-  PRUint32 unconvertedLength = aLength;
-  mRun.UntranslateSubstring(&unconvertedStart, &unconvertedLength);
-
-  // I'm being a bit clever here and reusing aSpacing since it's at least
-  // as big as the temporary buffer we need. We put our temporary buffer
-  // at the end of aSpacing so that when we start filling aSpacing
-  // below, we don't overwrite data we need --- until the very last memmove,
-  // which will be in-place.
-  NS_ASSERTION(aLength >= unconvertedLength,
-               "We only handle *growing* strings here");
-  Spacing* buffer = aSpacing + aLength - unconvertedLength;
-  mInner->GetSpacing(unconvertedStart, unconvertedLength, buffer);
-
-  gfxSkipCharsIterator iter(mRun.mTransform, aStart);
-  nsSkipCharsRunIterator
-    run(iter, nsSkipCharsRunIterator::LENGTH_INCLUDES_SKIPPED, aLength);
-  run.SetVisitSkipped();
-  // The following code relies on the fact that the only spacing that can be
-  // added after a SZLIG is letter-spacing, and the same space should be added
-  // after both S's as was addeded after the SZLIG.
-  gfxFloat lastSpaceAfter = 0;
-  while (run.NextRun()) {
-    if (run.IsSkipped()) {
-      PRUint32 offset = run.GetOriginalOffset() - aStart;
-      PRInt32 i;
-      for (i = 0; i < run.GetRunLength(); ++i) {
-        aSpacing[offset + i].mBefore = 0;
-        aSpacing[offset + i].mAfter = lastSpaceAfter;
-      }
-    } else {
-      memcpy(aSpacing + run.GetOriginalOffset() - aStart,
-             buffer + run.GetSkippedOffset() - unconvertedStart,
-             run.GetRunLength()*sizeof(Spacing));
-      lastSpaceAfter =
-        aSpacing[run.GetOriginalOffset() - aStart + run.GetRunLength() - 1].mAfter;
-    }
-  }
-}
-
-/**
- * This textrun class encapsulates the concatenation of a list of text runs. This
- * allows text with runs of different styles (e.g., fonts) to be presented
- * as a single text run.
- */
-class nsMultiTextRun : public WrapperTextRun {
+class nsTransformedTextRun : public gfxTextRun {
 public:
-  nsMultiTextRun(gfxTextRun* aBaseTextRun, gfxTextRunFactory::Parameters* aParams)
-    : WrapperTextRun(aBaseTextRun, aParams) {}
-
-  struct ChildRun {
-    void Set(gfxTextRun* aChild, PRUint32 aOffset, PRUint32 aLength) {
-      mChild = aChild;
-      mOffset = aOffset;
-      mLength = aLength;
-    }
-
-    nsAutoPtr<gfxTextRun> mChild;
-    PRUint32              mOffset;
-    PRUint32              mLength;
-  };
-
-  // Creates a textrun and adds it to our list.
-  nsresult AddChildRun(gfxTextRunFactory* aFactory, const PRUnichar* aText, PRUint32 aLength,
-                       gfxTextRunFactory::Parameters* aParams);
-  // Clears the list of child runs so that it can be regenerated.
-  void ClearChildren() { mRuns.Clear(); }
-  nsresult AddChildRun(gfxTextRun* aChild, PRUint32 aOffset, PRUint32 aLength) {
-    ChildRun* run = mRuns.AppendElement();
-    if (!run)
-      return NS_ERROR_OUT_OF_MEMORY;
-    run->Set(aChild, aOffset, aLength);
-    return NS_OK;
-  }
-
-  class MultiProviderWrapper : public ProviderWrapper {
-  public:
-    MultiProviderWrapper(PropertyProvider* aProvider, const nsMultiTextRun& aRun)
-      : ProviderWrapper(aProvider), mRun(aRun) {}
-
-    void SetChildRun(ChildRun* aRun) { mChild = aRun; }
-
-    virtual void GetHyphenationBreaks(PRUint32 aStart, PRUint32 aLength,
-                                      PRPackedBool* aBreakBefore)
-    {
-      mInner->GetHyphenationBreaks(mChild->mOffset + aStart, aLength, aBreakBefore);
-    }
-
-    virtual void GetSpacing(PRUint32 aStart, PRUint32 aLength,
-                            Spacing* aSpacing)
-    {
-      mInner->GetSpacing(mChild->mOffset + aStart, aLength, aSpacing);
-    }
-
-  protected:
-    const nsMultiTextRun& mRun;
-    ChildRun*             mChild;
-  };
-
-  virtual PRUint8 GetCharFlags(PRUint32 aOffset);
-  virtual PRUint32 GetLength();
-
-  virtual PRBool SetPotentialLineBreaks(PRUint32 aStart, PRUint32 aLength,
-                                        PRPackedBool* aBreakBefore);
-  virtual void Draw(gfxContext* aContext, gfxPoint aPt,
-                    PRUint32 aStart, PRUint32 aLength,
-                    const gfxRect* aDirtyRect,
-                    PropertyProvider* aBreakProvider,
-                    gfxFloat* aAdvanceWidth);
-  virtual void DrawToPath(gfxContext* aContext, gfxPoint aPt,
-                          PRUint32 aStart, PRUint32 aLength,
-                          PropertyProvider* aBreakProvider,
-                          gfxFloat* aAdvanceWidth);
-  virtual Metrics MeasureText(PRUint32 aStart, PRUint32 aLength,
-                              PRBool aTightBoundingBox,
-                              PropertyProvider* aBreakProvider);
-  virtual void SetLineBreaks(PRUint32 aStart, PRUint32 aLength,
-                             PRBool aLineBreakBefore, PRBool aLineBreakAfter,
-                             TextProvider* aProvider,
-                             gfxFloat* aAdvanceWidthDelta);
-  virtual gfxFloat GetAdvanceWidth(PRUint32 aStart, PRUint32 aLength,
-                                   PropertyProvider* aBreakProvider);
-  virtual void GetCharFlags(PRUint32 aStart, PRUint32 aLength, PRUint8* aFlags);
-  virtual PRUint32 BreakAndMeasureText(PRUint32 aStart, PRUint32 aMaxLength,
-                                       PRBool aLineBreakBefore, gfxFloat aWidth,
-                                       PropertyProvider* aBreakProvider,
-                                       PRBool aSuppressInitialBreak,
-                                       Metrics* aMetrics, PRBool aTightBoundingBox,
-                                       PRBool* aUsedHyphenation,
-                                       PRUint32* aLastBreak);
-
-  friend class MultiProviderWrapper;
-
-private:
-  PRUint32 FindChildIndexContaining(PRUint32 aOffset);
-  ChildRun* FindChildContaining(PRUint32 aOffset) {
-    return &mRuns[FindChildIndexContaining(aOffset)];
-  }
-
-  class ChildIterator {
-  public:
-    ChildIterator(nsMultiTextRun& aMulti, PRUint32 aOffset, PRUint32 aLength,
-                  MultiProviderWrapper* aWrapper = nsnull);
-    PRBool NextChild();
-    ChildRun* GetChild() { return mChild; }
-    gfxTextRun* GetTextRun() { return mChild->mChild; }
-    PRUint32 GetChildOffset() { return mChildOffset; }
-    PRUint32 GetChildLength() { return mChildLength; }
-    PRUint32 GetOffset() { return mOffset; }
-  private:
-    nsMultiTextRun&       mMulti;
-    MultiProviderWrapper* mWrapper;
-    ChildRun*             mChild;
-    PRUint32              mIndex;
-    PRUint32              mOffset;
-    PRUint32              mRemaining;
-    PRUint32              mChildOffset;
-    PRUint32              mChildLength;
-  };
-  friend class ChildIterator;
-  
-  void CombineMetrics(gfxTextRun::Metrics* aRunning, const gfxTextRun::Metrics& aMetrics);
-
-  nsTArray<ChildRun> mRuns;
-};
-
-nsMultiTextRun::ChildIterator::ChildIterator(nsMultiTextRun& aMulti,
-                                             PRUint32 aOffset, PRUint32 aLength,
-                                             MultiProviderWrapper* aWrapper)
-  : mMulti(aMulti), mWrapper(aWrapper), mRemaining(aLength)
-{
-  mIndex = mMulti.FindChildIndexContaining(aOffset);
-}
-
-PRBool
-nsMultiTextRun::ChildIterator::NextChild()
-{
-  if (mRemaining <= 0)
-    return PR_FALSE;
-  NS_ASSERTION(mIndex < mMulti.mRuns.Length(), "substring overrun");
-  mChild = &mMulti.mRuns[mIndex];
-  ++mIndex;
-
-  mChildOffset = PR_MAX(mChild->mOffset, mOffset) - mChild->mOffset;
-  mChildLength = PR_MIN(mChild->mLength, mChildOffset + mRemaining);
-
-  mOffset += mChildLength;
-  mRemaining -= mChildLength;
-  if (mWrapper) {
-    mWrapper->SetChildRun(mChild);
-  }
-  return PR_TRUE;
-}
-
-PRUint32
-nsMultiTextRun::GetLength()
-{
-  if (mRuns.Length() == 0)
-    return 0;
-  ChildRun* last = &mRuns[mRuns.Length() - 1];
-  return last->mOffset + last->mLength;
-}
-
-nsresult
-nsMultiTextRun::AddChildRun(gfxTextRunFactory* aFactory, const PRUnichar* aText,
-                            PRUint32 aLength,
-                            gfxTextRunFactory::Parameters* aParams)
-{
-  PRUint32 offset = GetLength();
-
-  // Update line break list in aParams for the creation of the child. We maintain
-  // the invariant that aParams->mInitialBreaks points to the breaks for the
-  // next child to be added and subsequent children.
-  PRUint32 breakCount = 0;
-  PRUint32 currentBreakCount = aParams->mInitialBreakCount;
-  while (breakCount < currentBreakCount &&
-         aParams->mInitialBreaks[breakCount] <= offset + aLength) {
-    // Get the break position into child-textrun coordinates
-    aParams->mInitialBreaks[breakCount] -= offset;
-    ++breakCount;
-  }
-  aParams->mInitialBreakCount = breakCount;
-
-  gfxTextRun* run = aFactory->MakeTextRun(aText, aLength, aParams);
-  if (!run)
-    return PR_FALSE;
-  // force child to remember its text so we don't have to support recovering it
-  run->RememberText(aText, aLength);
-
-  // Now update line break list in aParams for the next iteration
-  // If there's a break at the end of this child, leave it in the list so
-  // that the next child will see it at its start.
-  if (breakCount > 0 && aParams->mInitialBreaks[breakCount - 1] == aLength) {
-    // restore to global-textrun coordinates. The rest of the breaks are
-    // skipped, never to be used again, so we don't need to restore them.
-    aParams->mInitialBreaks[breakCount - 1] = offset + aLength;
-    --breakCount;
-  }
-  aParams->mInitialBreaks += breakCount;
-  aParams->mInitialBreakCount -= breakCount;
-
-  return AddChildRun(run, offset, aLength);
-}
-
-PRUint32
-nsMultiTextRun::FindChildIndexContaining(PRUint32 aOffset)
-{
-  PRUint32 start = 0;
-  PRUint32 end = mRuns.Length();
-  while (end - start > 1) {
-    PRUint32 mid = (start + end)/2;
-    if (mRuns[mid].mOffset <= aOffset) {
-      start = mid;
-    } else {
-      end = mid;
-    }
-  }
-  NS_ASSERTION(mRuns[start].mOffset + mRuns[start].mLength > aOffset,
-               "Substring offset out of range!");
-  return start;
-}
-
-void
-nsMultiTextRun::CombineMetrics(gfxTextRun::Metrics* aRunning,
-                               const gfxTextRun::Metrics& aMetrics)
-{
-  if (IsRightToLeft()) {
-    // 'aRunning' is to the right of 'aMetrics'
-    gfxTextRun::Metrics tmp = aMetrics;
-    tmp.CombineWith(*aRunning);
-    *aRunning = tmp;
-  } else {
-    // 'aRunning' is to the left of 'aMetrics'
-    aRunning->CombineWith(aMetrics);
-  }
-}
-
-PRUint8
-nsMultiTextRun::GetCharFlags(PRUint32 aOffset)
-{
-  ChildRun* child = FindChildContaining(aOffset);
-  return mInner->GetCharFlags(aOffset - child->mOffset);
-}
-
-void
-nsMultiTextRun::GetCharFlags(PRUint32 aStart, PRUint32 aLength, PRUint8* aFlags)
-{
-  ChildIterator iter(*this, aStart, aLength);
-  while (iter.NextChild()) {
-    iter.GetTextRun()->GetCharFlags(iter.GetChildOffset(), iter.GetChildLength(),
-                                    aFlags);
-    aFlags += iter.GetChildLength();
-  }
-}
-
-PRBool
-nsMultiTextRun::SetPotentialLineBreaks(PRUint32 aStart, PRUint32 aLength,
-                                       PRPackedBool* aBreakBefore)
-{
-  ChildIterator iter(*this, aStart, aLength);
-  PRBool changed = PR_FALSE;
-  while (iter.NextChild()) {
-    changed |= iter.GetTextRun()->SetPotentialLineBreaks(iter.GetChildOffset(), iter.GetChildLength(),
-                                                         aBreakBefore);
-    aBreakBefore += iter.GetChildLength();
-  }
-  return changed;
-}
-
-void
-nsMultiTextRun::Draw(gfxContext* aContext, gfxPoint aPt,
-                     PRUint32 aStart, PRUint32 aLength,
-                     const gfxRect* aDirtyRect, PropertyProvider* aBreakProvider,
-                     gfxFloat* aAdvanceWidth)
-{
-  MultiProviderWrapper wrapper(aBreakProvider, *this);
-  ChildIterator iter(*this, aStart, aLength, &wrapper);
-  gfxFloat totalAdvance = 0;
-  while (iter.NextChild()) {
-    gfxFloat advance;
-    gfxPoint pt = gfxPoint(aPt.x + iter.GetTextRun()->GetDirection()*totalAdvance, aPt.y);
-    iter.GetTextRun()->Draw(aContext, pt,
-                            iter.GetChildOffset(), iter.GetChildLength(),
-                            aDirtyRect, &wrapper, &advance);
-    totalAdvance += advance;
-  }
-  if (aAdvanceWidth) {
-    *aAdvanceWidth = totalAdvance;
-  }
-}
-
-void
-nsMultiTextRun::DrawToPath(gfxContext* aContext, gfxPoint aPt,
-                           PRUint32 aStart, PRUint32 aLength,
-                           PropertyProvider* aBreakProvider,
-                           gfxFloat* aAdvanceWidth)
-{
-  MultiProviderWrapper wrapper(aBreakProvider, *this);
-  ChildIterator iter(*this, aStart, aLength, &wrapper);
-  gfxFloat totalAdvance = 0;
-  while (iter.NextChild()) {
-    gfxFloat advance;
-    gfxPoint pt = gfxPoint(aPt.x + iter.GetTextRun()->GetDirection()*totalAdvance, aPt.y);
-    iter.GetTextRun()->DrawToPath(aContext, pt,
-                                  iter.GetChildOffset(), iter.GetChildLength(),
-                                  &wrapper, &advance);
-    totalAdvance += advance;
-  }
-  if (aAdvanceWidth) {
-    *aAdvanceWidth = totalAdvance;
-  }
-}
-
-gfxTextRun::Metrics
-nsMultiTextRun::MeasureText(PRUint32 aStart, PRUint32 aLength,
-                            PRBool aTightBoundingBox,
-                            PropertyProvider* aBreakProvider)
-{
-  MultiProviderWrapper wrapper(aBreakProvider, *this);
-  ChildIterator iter(*this, aStart, aLength, &wrapper);
-  gfxTextRun::Metrics result;
-  while (iter.NextChild()) {
-    gfxTextRun::Metrics metrics = iter.GetTextRun()->MeasureText(
-        iter.GetChildOffset(), iter.GetChildLength(), aTightBoundingBox, &wrapper);
-    CombineMetrics(&result, metrics);
-  }
-  return result;
-}
-
-PRUint32
-nsMultiTextRun::BreakAndMeasureText(PRUint32 aStart, PRUint32 aMaxLength,
-                                    PRBool aBreakBefore, gfxFloat aWidth,
-                                    PropertyProvider* aBreakProvider,
-                                    PRBool aSuppressInitialBreak,
-                                    Metrics* aMetrics, PRBool aTightBoundingBox,
-                                    PRBool* aUsedHyphenation,
-                                    PRUint32* aLastBreak)
-{
-  MultiProviderWrapper wrapper(aBreakProvider, *this);
-  ChildIterator iter(*this, aStart, aMaxLength, &wrapper);
-  PRUint32 fitSoFar = 0;
-  gfxTextRun::Metrics totalMetrics;
-  PRUint32 lastBreak = aMaxLength;
-
-  while (iter.NextChild()) {
-    gfxTextRun::Metrics metrics;
-    PRUint32 childLastBreak;
-    PRUint32 fits = iter.GetTextRun()->BreakAndMeasureText(
-        iter.GetChildOffset(), iter.GetChildLength(), aBreakBefore,
-        aWidth - aMetrics->mAdvanceWidth, &wrapper, aSuppressInitialBreak,
-        &metrics, aTightBoundingBox, aUsedHyphenation, &childLastBreak);
-
-    CombineMetrics(&totalMetrics, metrics);
-    if (childLastBreak < iter.GetChildLength()) {
-      lastBreak = fitSoFar + childLastBreak;
-    }
-    fitSoFar += fits;
-
-    if (fits < iter.GetChildLength())
-      break;
-    aBreakBefore = PR_FALSE;
-    aSuppressInitialBreak = PR_FALSE;
-  }
-
-  if (aMetrics) {
-    *aMetrics = totalMetrics;
-  }
-  if (aLastBreak) {
-    *aLastBreak = lastBreak;
-  }
-  return fitSoFar;
-}
-
-void
-nsMultiTextRun::SetLineBreaks(PRUint32 aStart, PRUint32 aLength,
-                              PRBool aLineBreakBefore, PRBool aLineBreakAfter,
-                              TextProvider* aProvider,
-                              gfxFloat* aAdvanceWidthDelta)
-{
-  TextProviderWrapper dummyTextProvider;
-  ChildIterator iter(*this, aStart, aLength);
-  gfxFloat delta = 0;
-  while (iter.NextChild()) {
-    gfxFloat thisDelta;
-    iter.GetTextRun()->SetLineBreaks(
-        iter.GetChildOffset(), iter.GetChildLength(),
-        aLineBreakBefore && iter.GetOffset() == aStart,
-        aLineBreakAfter && iter.GetOffset() + iter.GetChildLength() == aStart + aLength,
-        &dummyTextProvider, &thisDelta);
-    delta += thisDelta;
-  }
-  *aAdvanceWidthDelta = delta;
-}
-
-gfxFloat
-nsMultiTextRun::GetAdvanceWidth(PRUint32 aStart, PRUint32 aLength,
-                                PropertyProvider* aBreakProvider)
-{
-  MultiProviderWrapper wrapper(aBreakProvider, *this);
-  ChildIterator iter(*this, aStart, aLength, &wrapper);
-  gfxFloat result;
-  while (iter.NextChild()) {
-    result += iter.GetTextRun()->GetAdvanceWidth(
-        iter.GetChildOffset(), iter.GetChildLength(), &wrapper);
-  }
-  return result;
-}
-
-// ==============================
-// Now define factories that use the above textruns
-// ==============================
-
-static gfxTextRunFactory::Parameters
-GetParametersForInner(const gfxTextRunFactory::Parameters& aParams,
-                      gfxSkipChars* aDummy)
-{
-  gfxTextRunFactory::Parameters params =
-    { aParams.mContext, nsnull, aParams.mLangGroup, aDummy,
-      aParams.mInitialBreaks, aParams.mInitialBreakCount,
-      aParams.mAppUnitsPerDevUnit, aParams.mFlags };
-  return params;
-}
-
-gfxTextRun*
-nsTransformingTextRunFactory::MakeTextRun(const PRUint8* aString, PRUint32 aLength,
-                                          gfxTextRunFactory::Parameters* aParams)
-{
-  // We'll only have a Unicode code path to minimize the amount of code needed
-  // for this rarely used feature
-  NS_ConvertASCIItoUTF16 unicodeString(NS_REINTERPRET_CAST(const char*, aString), aLength);
-  Parameters params = *aParams;
-  params.mFlags &= ~gfxTextRunFactory::TEXT_IS_PERSISTENT;
-  return MakeTextRun(unicodeString.get(), aLength, &params);
-}
-
-/**
- * For smallcaps we divide the text into runs of lowercase and non-lowercase
- * clusters and build a nsMultiTextRun for the separate runs. For the lowercase
- * runs we use nsCaseTransformTextRunFactory to convert to uppercase.
- */
-gfxTextRun*
-nsFontVariantTextRunFactory::MakeTextRun(const PRUnichar* aString, PRUint32 aLength,
-                                         gfxTextRunFactory::Parameters* aParams)
-{
-  nsICaseConversion* converter = nsTextTransformer::GetCaseConv();
-  if (!converter || !mStyles)
-    return nsnull;
-
-  gfxFontStyle fontStyle = *mFontGroup->GetStyle();
-  fontStyle.size *= 0.8;
-  nsRefPtr<gfxFontGroup> smallFont = mFontGroup->Copy(&fontStyle);
-  if (!smallFont)
-    return nsnull;
-
-  gfxSkipChars dummy;
-  gfxTextRunFactory::Parameters innerParams = GetParametersForInner(*aParams, &dummy);
-  gfxTextRun* inner = mFontGroup->MakeTextRun(aString, aLength, &innerParams);
-  if (!inner)
-    return nsnull;
-  // force the inner to remember the text, so we don't have to worry about
-  // reconstructing it later.
-  inner->RememberText(aString, aLength);
-
-  nsAutoPtr<nsMultiTextRun> textRun;
-  textRun = new nsMultiTextRun(inner, aParams);
-  if (!textRun)
-    return nsnull;
-
-  nsRefPtr<nsCaseTransformTextRunFactory> uppercaseFactory =
-    new nsCaseTransformTextRunFactory(smallFont, nsnull, PR_TRUE);
-  if (!uppercaseFactory)
-    return nsnull;
-
-  PRUint32 i;
-  PRUint32 runStart = 0;
-  PRPackedBool runIsLowercase = PR_FALSE;
-  for (i = 0; i <= aLength; ++i) {
-    PRBool isLowercase = PR_FALSE;
-    if (i < aLength) {
-      // Characters that aren't the start of a cluster are ignored here. They
-      // get added to whatever lowercase/non-lowercase run we're in.
-      if (!(inner->GetCharFlags(i) & gfxTextRun::CLUSTER_START))
-        continue;
-
-      if (mStyles[i]->GetStyleFont()->mFont.variant == NS_STYLE_FONT_VARIANT_SMALL_CAPS) {
-        PRUnichar ch = aString[i];
-        PRUnichar ch2;
-        converter->ToUpper(ch, &ch2);
-        isLowercase = ch != ch2;
-      } else {
-        // Don't transform the character! I.e., pretend that it's not lowercase
-      }
-    }
-
-    if ((i == aLength || runIsLowercase != isLowercase) && runStart < i) {
-      gfxTextRunFactory* factory;
-      if (runIsLowercase) {
-        factory = uppercaseFactory;
-        if (mInnerTransformingTextRunFactory) {
-          mInnerTransformingTextRunFactory->SetStyles(mStyles + runStart);
-        }
-      } else {
-        factory = mFontGroup;
-      }
-      nsresult rv = textRun->AddChildRun(factory, aString + runStart, i - runStart, &innerParams);
-      if (NS_FAILED(rv))
-        return nsnull;
-      runStart = i;
-      runIsLowercase = isLowercase;
-    }
-  }
-
-  return textRun.forget();
-}
-
-class nsCaseTransformingTextRun : public nsMultiTextRun {
-public:
-  nsCaseTransformingTextRun(gfxTextRun* aBaseTextRun,
-                            gfxTextRunFactory::Parameters* aParams,
-                            nsCaseTransformTextRunFactory* aFactory,
-                            const PRUnichar* aString, PRUint32 aLength)
-    : nsMultiTextRun(aBaseTextRun, aParams), mString(aString, aLength),
+  nsTransformedTextRun(gfxTextRunFactory::Parameters* aParams,
+                       nsTransformingTextRunFactory* aFactory,
+                       const PRUnichar* aString, PRUint32 aLength,
+                       nsStyleContext** aStyles)
+    : gfxTextRun(aParams, aLength), mString(aString, aLength),
       mFactory(aFactory), mRefContext(aParams->mContext),
-      mLangGroup(aParams->mLangGroup),
-      mAppUnitsPerDevUnit(aParams->mAppUnitsPerDevUnit),
-      mFlags(aParams->mFlags)
+      mLangGroup(aParams->mLangGroup)
   {
     PRUint32 i;
     for (i = 0; i < aLength; ++i) {
-      mLineBreakOpportunities.AppendElement(PR_FALSE);
-      if (!aFactory->IsAllUppercase()) {
-        mStyles.AppendElement(aFactory->GetStyles()[i]);
-      }
+      mStyles.AppendElement(aStyles[i]);
     }
     for (i = 0; i < aParams->mInitialBreakCount; ++i) {
       mLineBreaks.AppendElement(aParams->mInitialBreaks[i]);
     }
   }
 
   virtual PRBool SetPotentialLineBreaks(PRUint32 aStart, PRUint32 aLength,
-                                        PRPackedBool* aBreakBefore);
-  virtual void SetLineBreaks(PRUint32 aStart, PRUint32 aLength,
-                             PRBool aLineBreakBefore, PRBool aLineBreakAfter,
-                             TextProvider* aProvider,
-                             gfxFloat* aAdvanceWidthDelta);
-  nsresult Build();
+                                        PRPackedBool* aBreakBefore)
+  {
+    PRBool changed = gfxTextRun::SetPotentialLineBreaks(aStart, aLength, aBreakBefore);
+    mFactory->RebuildTextRun(this);
+    return changed;
+  }
+  virtual PRBool SetLineBreaks(PRUint32 aStart, PRUint32 aLength,
+                               PRBool aLineBreakBefore, PRBool aLineBreakAfter,
+                               PropertyProvider* aProvider,
+                               gfxFloat* aAdvanceWidthDelta);
 
-private:
   nsString                                mString;
-  nsRefPtr<nsCaseTransformTextRunFactory> mFactory;
+  nsAutoPtr<nsTransformingTextRunFactory> mFactory;
   nsRefPtr<gfxContext>                    mRefContext;
   nsCOMPtr<nsIAtom>                       mLangGroup;
   nsTArray<PRUint32>                      mLineBreaks;
-  nsTArray<PRPackedBool>                  mLineBreakOpportunities;
   nsTArray<nsRefPtr<nsStyleContext> >     mStyles;
-  gfxFloat                                mAppUnitsPerDevUnit;
-  PRUint32                                mFlags;
 };
 
-nsresult
-nsCaseTransformingTextRun::Build()
+PRBool
+nsTransformedTextRun::SetLineBreaks(PRUint32 aStart, PRUint32 aLength,
+                                    PRBool aLineBreakBefore, PRBool aLineBreakAfter,
+                                    PropertyProvider* aProvider,
+                                    gfxFloat* aAdvanceWidthDelta)
+{
+  nsTArray<PRUint32> newBreaks;
+  PRUint32 i;
+  PRBool changed = PR_FALSE;
+  for (i = 0; i < mLineBreaks.Length(); ++i) {
+    PRUint32 pos = mLineBreaks[i];
+    if (pos >= aStart)
+      break;
+    newBreaks.AppendElement(pos);
+  }
+  if (aLineBreakBefore != (i < mLineBreaks.Length() &&
+                           mLineBreaks[i] == aStart)) {
+    changed = PR_TRUE;
+  }
+  if (aLineBreakBefore) {
+    newBreaks.AppendElement(aStart);
+  }
+  if (aLineBreakAfter != (i + 1 < mLineBreaks.Length() &&
+                          mLineBreaks[i + 1] == aStart + aLength)) {
+    changed = PR_TRUE;
+  }
+  if (aLineBreakAfter) {
+    newBreaks.AppendElement(aStart + aLength);
+  }
+  for (; i < mLineBreaks.Length(); ++i) {
+    if (mLineBreaks[i] > aStart + aLength)
+      break;
+    changed = PR_TRUE;
+  }
+  if (!changed) {
+    if (aAdvanceWidthDelta) {
+      *aAdvanceWidthDelta = 0;
+    }
+    return PR_FALSE;
+  }
+
+  newBreaks.AppendElements(mLineBreaks.Elements() + i, mLineBreaks.Length() - i);
+  mLineBreaks.SwapElements(newBreaks);
+
+  gfxFloat currentAdvance = GetAdvanceWidth(aStart, aLength, aProvider);
+  mFactory->RebuildTextRun(this);
+  if (aAdvanceWidthDelta) {
+    *aAdvanceWidthDelta = GetAdvanceWidth(aStart, aLength, aProvider) - currentAdvance;
+  }
+  return PR_TRUE;
+}
+
+gfxTextRun*
+nsTransformingTextRunFactory::MakeTextRun(const PRUnichar* aString, PRUint32 aLength,
+                                          gfxTextRunFactory::Parameters* aParams,
+                                          nsStyleContext** aStyles)
+{
+  nsTransformedTextRun* textRun =
+    new nsTransformedTextRun(aParams, this, aString, aLength, aStyles);
+  if (!textRun)
+    return nsnull;
+  RebuildTextRun(textRun);
+  return textRun;
+}
+
+gfxTextRun*
+nsTransformingTextRunFactory::MakeTextRun(const PRUint8* aString, PRUint32 aLength,
+                                          gfxTextRunFactory::Parameters* aParams,
+                                          nsStyleContext** aStyles)
+{
+  // We'll only have a Unicode code path to minimize the amount of code needed
+  // for these rarely used features
+  NS_ConvertASCIItoUTF16 unicodeString(NS_REINTERPRET_CAST(const char*, aString), aLength);
+  gfxTextRunFactory::Parameters params = *aParams;
+  params.mFlags &= ~gfxTextRunFactory::TEXT_IS_PERSISTENT;
+  return MakeTextRun(unicodeString.get(), aLength, &params, aStyles);
+}
+
+static PRUint32
+CountGlyphs(const gfxTextRun::DetailedGlyph* aDetails) {
+  PRUint32 glyphCount;
+  for (glyphCount = 0; !aDetails[glyphCount].mIsLastGlyph; ++glyphCount) {
+  }
+  return glyphCount + 1;
+}
+
+/**
+ * Concatenate textruns together just by copying their glyphrun data
+ */
+static void
+AppendTextRun(gfxTextRun* aDest, gfxTextRun* aSrc, PRUint32 aOffset)
+{
+  PRUint32 numGlyphRuns;
+  const gfxTextRun::GlyphRun* glyphRuns = aSrc->GetGlyphRuns(&numGlyphRuns);
+  PRUint32 j;
+  PRUint32 offset = aOffset;
+  for (j = 0; j < numGlyphRuns; ++j) {
+    PRUint32 runOffset = glyphRuns[j].mCharacterOffset;
+    PRUint32 len =
+      (j + 1 < numGlyphRuns ? glyphRuns[j + 1].mCharacterOffset : aSrc->GetLength()) -
+      runOffset;
+    nsresult rv = aDest->AddGlyphRun(glyphRuns[j].mFont, offset);
+    if (NS_FAILED(rv))
+      return;
+
+    PRUint32 k;
+    for (k = 0; k < len; ++k) {
+      gfxTextRun::CompressedGlyph g = aSrc->GetCharacterGlyphs()[runOffset + k];
+      if (g.IsComplexCluster()) {
+        const gfxTextRun::DetailedGlyph* details = aSrc->GetDetailedGlyphs(runOffset + k);
+        aDest->SetDetailedGlyphs(offset, details, CountGlyphs(details));
+      } else {
+        aDest->SetCharacterGlyph(offset, g);
+      }
+      ++offset;
+    }
+  }
+  NS_ASSERTION(offset - aOffset == aSrc->GetLength(),
+               "Something went wrong in our length calculations...");
+}
+
+/**
+ * Copy a given textrun, but merge certain characters into a single logical
+ * character. Glyphs for a character are added to the glyph list for the previous
+ * character and then the merged character is eliminated. Visually the results
+ * are identical.
+ * 
+ * This is used for text-transform:uppercase when we encounter a SZLIG,
+ * whose uppercase form is "SS".
+ * 
+ * This function is unable to merge characters when they occur in different
+ * glyph runs. It's hard to see how this could happen, but if it does, we just
+ * discard the characters-to-merge.
+ * 
+ * @param aCharsToMerge when aCharsToMerge[i] is true, this character is
+ * merged into the previous character
+ */
+static void
+MergeCharactersInTextRun(gfxTextRun* aDest, gfxTextRun* aSrc,
+                         PRPackedBool* aCharsToMerge)
+{
+  aDest->ResetGlyphRuns();
+
+  PRUint32 numGlyphRuns;
+  const gfxTextRun::GlyphRun* glyphRuns = aSrc->GetGlyphRuns(&numGlyphRuns);
+  PRUint32 offset = 0;
+  PRUint32 j;
+  for (j = 0; j < numGlyphRuns; ++j) {
+    PRUint32 runOffset = glyphRuns[j].mCharacterOffset;
+    PRUint32 len =
+      (j + 1 < numGlyphRuns ? glyphRuns[j + 1].mCharacterOffset : aSrc->GetLength()) -
+      runOffset;
+    nsresult rv = aDest->AddGlyphRun(glyphRuns[j].mFont, offset);
+    if (NS_FAILED(rv))
+      return;
+
+    PRUint32 k;
+    for (k = 0; k < len; ++k) {
+      if (aCharsToMerge[runOffset + k])
+        continue;
+
+      gfxTextRun::CompressedGlyph g = aSrc->GetCharacterGlyphs()[runOffset + k];
+      if (g.IsSimpleGlyph() || g.IsComplexCluster()) {
+        PRUint32 mergedCount = 1;
+        PRBool multipleGlyphs = PR_FALSE;
+        while (k + mergedCount < len) {
+          gfxTextRun::CompressedGlyph h = aSrc->GetCharacterGlyphs()[runOffset + k + mergedCount];
+          if (!aCharsToMerge[runOffset + k + mergedCount] &&
+              !h.IsClusterContinuation() && !h.IsLigatureContinuation())
+            break;
+          if (h.IsComplexCluster() || h.IsSimpleGlyph()) {
+            multipleGlyphs = PR_TRUE;
+          }
+          ++mergedCount;
+        }
+        if (g.IsSimpleGlyph() && !multipleGlyphs) {
+          aDest->SetCharacterGlyph(offset, g);
+        } else {
+          // We have something complex to do.
+          nsAutoTArray<gfxTextRun::DetailedGlyph,2> detailedGlyphs;
+          PRUint32 m;
+          for (m = 0; m < mergedCount; ++m) {
+            gfxTextRun::CompressedGlyph h = aSrc->GetCharacterGlyphs()[runOffset + k + m];
+            if (h.IsSimpleGlyph()) {
+              gfxTextRun::DetailedGlyph* details = detailedGlyphs.AppendElement();
+              if (!details)
+                return;
+              details->mGlyphID = h.GetSimpleGlyph();
+              details->mAdvance = h.GetSimpleAdvance();
+              details->mXOffset = 0;
+              details->mYOffset = 0;
+            } else if (h.IsComplexCluster()) {
+              const gfxTextRun::DetailedGlyph* srcDetails = aSrc->GetDetailedGlyphs(runOffset + k + m);
+              detailedGlyphs.AppendElements(srcDetails, CountGlyphs(srcDetails));
+            }
+            detailedGlyphs[detailedGlyphs.Length() - 1].mIsLastGlyph = PR_FALSE;
+          }
+          detailedGlyphs[detailedGlyphs.Length() - 1].mIsLastGlyph = PR_TRUE;
+          aDest->SetDetailedGlyphs(offset, detailedGlyphs.Elements(), detailedGlyphs.Length());
+        }
+      } else {
+        aDest->SetCharacterGlyph(offset, g);
+      }
+      ++offset;
+    }
+  }
+  NS_ASSERTION(offset == aDest->GetLength(), "Bad offset calculations");
+}
+
+static gfxTextRunFactory::Parameters
+GetParametersForInner(nsTransformedTextRun* aTextRun,
+                      gfxSkipChars* aDummy)
+{
+  gfxTextRunFactory::Parameters params =
+    { aTextRun->mRefContext, nsnull, aTextRun->mLangGroup, aDummy,
+      nsnull, nsnull, PRUint32(aTextRun->GetAppUnitsPerDevUnit()),
+      aTextRun->GetFlags() };
+  return params;
+}
+
+void
+nsFontVariantTextRunFactory::RebuildTextRun(nsTransformedTextRun* aTextRun)
 {
   nsICaseConversion* converter = nsTextTransformer::GetCaseConv();
   if (!converter)
-    return NS_ERROR_FAILURE;
+    return;
+
+  gfxFontStyle fontStyle = *mFontGroup->GetStyle();
+  fontStyle.size *= 0.8;
+  nsRefPtr<gfxFontGroup> smallFont = mFontGroup->Copy(&fontStyle);
+  if (!smallFont)
+    return;
+
+  gfxSkipChars dummy;
+  gfxTextRunFactory::Parameters innerParams = GetParametersForInner(aTextRun, &dummy);
+
+  PRUint32 length = aTextRun->GetLength();
+  const PRUnichar* str = aTextRun->mString.BeginReading();
+  nsRefPtr<nsStyleContext>* styles = aTextRun->mStyles.Elements();
+  // Create a textrun so we can check cluster-start properties
+  nsAutoPtr<gfxTextRun> inner;
+  inner = mFontGroup->MakeTextRun(str, length, &innerParams);
+  if (!inner)
+    return;
+
+  nsCaseTransformTextRunFactory uppercaseFactory(smallFont, nsnull, PR_TRUE);
+
+  aTextRun->ResetGlyphRuns();
+
+  PRUint32 runStart = 0;
+  PRPackedBool runIsLowercase = PR_FALSE;
+  nsAutoTArray<nsStyleContext*,50> styleArray;
+  nsAutoTArray<PRPackedBool,50> canBreakBeforeArray;
+  nsAutoTArray<PRUint32,10> lineBreakBeforeArray;
+
+  PRUint32 nextLineBreak = 0;
+  PRUint32 i;
+  for (i = 0; i <= length; ++i) {
+    if (nextLineBreak < aTextRun->mLineBreaks.Length() &&
+        aTextRun->mLineBreaks[nextLineBreak] == i) {
+      lineBreakBeforeArray.AppendElement(i - runStart);
+      ++nextLineBreak;
+    }
+
+    PRBool isLowercase = PR_FALSE;
+    if (i < length) {
+      // Characters that aren't the start of a cluster are ignored here. They
+      // get added to whatever lowercase/non-lowercase run we're in.
+      if (!inner->IsClusterStart(i))
+        continue;
+
+      if (styles[i]->GetStyleFont()->mFont.variant == NS_STYLE_FONT_VARIANT_SMALL_CAPS) {
+        PRUnichar ch = str[i];
+        PRUnichar ch2;
+        converter->ToUpper(ch, &ch2);
+        isLowercase = ch != ch2 || ch == SZLIG;
+      } else {
+        // Don't transform the character! I.e., pretend that it's not lowercase
+      }
+    }
+
+    if ((i == length || runIsLowercase != isLowercase) && runStart < i) {
+      nsAutoPtr<gfxTextRun> child;
+      // Setup actual line break data for child (which may affect shaping)
+      innerParams.mInitialBreaks = lineBreakBeforeArray.Elements();
+      innerParams.mInitialBreakCount = lineBreakBeforeArray.Length();
+      if (runIsLowercase) {
+        child = uppercaseFactory.MakeTextRun(str + runStart, i - runStart,
+                                             &innerParams, styleArray.Elements());
+      } else {
+        child = mFontGroup->MakeTextRun(str + runStart, i - runStart, &innerParams);
+      }
+      if (!child)
+        return;
+      // Copy potential linebreaks into child so they're preserved
+      // (and also child will be shaped appropriately)
+      NS_ASSERTION(canBreakBeforeArray.Length() == i - runStart,
+                   "lost some break-before values?");
+      child->SetPotentialLineBreaks(0, canBreakBeforeArray.Length(), canBreakBeforeArray.Elements());
+      AppendTextRun(aTextRun, child, runStart);
+
+      runStart = i;
+      runIsLowercase = isLowercase;
+      styleArray.Clear();
+      canBreakBeforeArray.Clear();
+      lineBreakBeforeArray.Clear();
+      if (nextLineBreak > 0 && aTextRun->mLineBreaks[nextLineBreak - 1] == i) {
+        lineBreakBeforeArray.AppendElement(0);
+      }
+    }
+
+    if (i < length) {
+      styleArray.AppendElement(styles[i]);
+      canBreakBeforeArray.AppendElement(aTextRun->CanBreakLineBefore(i));
+    }
+  }
+  NS_ASSERTION(nextLineBreak == aTextRun->mLineBreaks.Length(),
+               "lost track of line breaks somehow");
+}
+
+void
+nsCaseTransformTextRunFactory::RebuildTextRun(nsTransformedTextRun* aTextRun)
+{
+  nsICaseConversion* converter = nsTextTransformer::GetCaseConv();
+  if (!converter)
+    return;
+
+  PRUint32 length = aTextRun->GetLength();
+  const PRUnichar* str = aTextRun->mString.BeginReading();
+  nsRefPtr<nsStyleContext>* styles = aTextRun->mStyles.Elements();
 
   nsAutoString convertedString;
+  nsAutoTArray<PRPackedBool,50> charsToMergeArray;
+  nsAutoTArray<nsStyleContext*,50> styleArray;
+  nsAutoTArray<PRPackedBool,50> canBreakBeforeArray;
+  nsAutoTArray<PRUint32,10> lineBreakBeforeArray;
+  PRUint32 nextLineBreak = 0;
+  PRUint32 extraCharsCount = 0;
+
   PRUint32 i;
-  // skipBuilder represents a translation from the *transformed* string to the
-  // *original* string, because in this case, that's the way deletions go
-  gfxSkipCharsBuilder skipBuilder;
-  nsAutoTArray<nsStyleContext*,1000> styles;
+  for (i = 0; i < length; ++i) {
+    PRUnichar ch = str[i];
 
-  for (i = 0; i < mString.Length(); ++i) {
-    PRUnichar ch = mString.CharAt(i);
+    charsToMergeArray.AppendElement(PR_FALSE);
+    styleArray.AppendElement(styles[i]);
+    canBreakBeforeArray.AppendElement(aTextRun->CanBreakLineBefore(i));
+    if (nextLineBreak < aTextRun->mLineBreaks.Length() &&
+        aTextRun->mLineBreaks[nextLineBreak] == i) {
+      lineBreakBeforeArray.AppendElement(i + extraCharsCount);
+      ++nextLineBreak;
+    }
 
-    // We're definitely going to keep at least one char in the hypothetical
-    // "transformation" from original to transformed string
-    skipBuilder.KeepChar();
-    
-    PRUint8 style = mFactory->IsAllUppercase() ? NS_STYLE_TEXT_TRANSFORM_UPPERCASE
-      : mStyles[i]->GetStyleText()->mTextTransform;
+    PRUint8 style = mAllUppercase ? NS_STYLE_TEXT_TRANSFORM_UPPERCASE
+      : styles[i]->GetStyleText()->mTextTransform;
+    PRBool extraChar = PR_FALSE;
 
     switch (style) {
     case NS_STYLE_TEXT_TRANSFORM_LOWERCASE:
       converter->ToLower(ch, &ch);
       break;
     case NS_STYLE_TEXT_TRANSFORM_UPPERCASE:
       if (ch == SZLIG) {
         convertedString.Append('S');
-        skipBuilder.SkipChar();
+        extraChar = PR_TRUE;
         ch = 'S';
       } else {
         converter->ToUpper(ch, &ch);
       }
       break;
     case NS_STYLE_TEXT_TRANSFORM_CAPITALIZE:
-      if (mLineBreakOpportunities[i]) {
+      if (aTextRun->CanBreakLineBefore(i)) {
         if (ch == SZLIG) {
-          styles.AppendElement(mStyles[i]);
           convertedString.Append('S');
-          skipBuilder.SkipChar();
+          extraChar = PR_TRUE;
           ch = 'S';
         } else {
           converter->ToTitle(ch, &ch);
         }
       }
       break;
     default:
       break;
     }
 
-    styles.AppendElement(mStyles[i]);
     convertedString.Append(ch);
-  }
-
-  PRBool allCharsKept = skipBuilder.GetAllCharsKept();
-  gfxSkipChars skipChars;
-  skipChars.TakeFrom(&skipBuilder);
-
-  // Convert line break offsets
-  nsAutoTArray<PRUint32,50> lineBreaks;
-  if (!lineBreaks.AppendElements(mLineBreaks.Length()))
-    return NS_ERROR_OUT_OF_MEMORY;
-  gfxSkipCharsIterator iter(skipChars);
-  for (i = 0; i < mLineBreaks.Length(); ++i) {
-    lineBreaks[i] = iter.ConvertSkippedToOriginal(mLineBreaks[i]);
-  }
-
-  // Create a textrun for the transformed string
-  gfxSkipChars dummy;
-  gfxTextRunFactory::Parameters innerParams = 
-    { mRefContext, nsnull, mLangGroup, &dummy, mLineBreaks.Elements(),
-      mLineBreaks.Length(), PRUint32(mAppUnitsPerDevUnit),
-      mFlags | gfxTextRunFactory::TEXT_IS_PERSISTENT };
-
-  if (mFactory->GetInnerTransformingTextRunFactory()) {
-    mFactory->GetInnerTransformingTextRunFactory()->SetStyles(styles.Elements());
-  }
-
-  nsAutoPtr<gfxTextRun> innerTextRun;
-  innerTextRun =
-    mFactory->GetInnerTextRunFactory()->MakeTextRun(convertedString.get(), convertedString.Length(),
-                                                    &innerParams);
-  if (!innerTextRun)
-    return NS_ERROR_FAILURE;
-  // Make sure the inner textrun never calls ForceRememberText, because we don't
-  // implement it
-  innerTextRun->RememberText(convertedString.get(), convertedString.Length());
-
-  if (!allCharsKept) {
-    // We need to adjust offsets because characters were inserted.
-    gfxTextRun* wrapper = new nsGermanTextRun(innerTextRun, &skipChars, &innerParams);
-    if (!wrapper)
-      return NS_ERROR_FAILURE;
-    innerTextRun.forget();
-    innerTextRun = wrapper;
-  }
-
-  ClearChildren();
-  nsresult rv = AddChildRun(innerTextRun, 0, convertedString.Length());
-  if (NS_SUCCEEDED(rv)) {
-    innerTextRun.forget();
-  }
-  return rv;
-}
-
-PRBool
-nsCaseTransformingTextRun::SetPotentialLineBreaks(PRUint32 aStart, PRUint32 aLength,
-                                                  PRPackedBool* aBreakBefore)
-{
-  PRUint32 i;
-  PRBool changed = PR_FALSE;
-  PRBool changedStyle = PR_FALSE;
-  for (i = 0; i < aLength; ++i) {
-    if (mLineBreakOpportunities[aStart + i] != aBreakBefore[i]) {
-      changed = PR_TRUE;
-      if (mStyles[i]->GetStyleText()->mTextTransform == NS_STYLE_TEXT_TRANSFORM_CAPITALIZE) {
-        changedStyle = PR_TRUE;
-      }
-      mLineBreakOpportunities[aStart + i] = aBreakBefore[i];
+    if (extraChar) {
+      ++extraCharsCount;
+      charsToMergeArray.AppendElement(PR_TRUE);
+      styleArray.AppendElement(styles[i]);
+      canBreakBeforeArray.AppendElement(PR_FALSE);
     }
   }
-  if (!changedStyle)
-    return nsMultiTextRun::SetPotentialLineBreaks(aStart, aLength, aBreakBefore);
-
-  Build();
-  return changed;
-}
+  if (nextLineBreak < aTextRun->mLineBreaks.Length() &&
+      aTextRun->mLineBreaks[nextLineBreak] == length) {
+    lineBreakBeforeArray.AppendElement(length + extraCharsCount);
+    ++nextLineBreak;
+  }
+  NS_ASSERTION(nextLineBreak == aTextRun->mLineBreaks.Length(),
+               "lost track of line breaks somehow");
 
-void
-nsCaseTransformingTextRun::SetLineBreaks(PRUint32 aStart, PRUint32 aLength,
-                                         PRBool aLineBreakBefore, PRBool aLineBreakAfter,
-                                         TextProvider* aProvider,
-                                         gfxFloat* aAdvanceWidthDelta)
-{
-  nsTArray<PRUint32> newBreaks;
-  for (PRUint32 i = 0; i < mLineBreaks.Length(); ++i) {
-    PRUint32 b = mLineBreaks[i];
-    if (b == aStart && !aLineBreakBefore)
-      continue;
-    if (b > aStart && aLineBreakBefore) {
-      newBreaks.AppendElement(aStart);
-      aLineBreakBefore = PR_FALSE;
-    }
-    if (b == aStart + aLength && !aLineBreakAfter)
-      continue;
-    if (b > aStart + aLength && aLineBreakAfter) {
-      newBreaks.AppendElement(aStart + aLength);
-      aLineBreakAfter = PR_FALSE;
-    }
-    newBreaks.AppendElement(b);
-  }
-  mLineBreaks.SwapElements(newBreaks);
-  nsMultiTextRun::SetLineBreaks(aStart, aLength, aLineBreakBefore, aLineBreakAfter,
-                                aProvider, aAdvanceWidthDelta);
-}
+  gfxSkipChars dummy;
+  gfxTextRunFactory::Parameters innerParams = GetParametersForInner(aTextRun, &dummy);
 
-/**
- * When doing case conversion we first transform the string. If the string
- * ends up with no extra characters inserted, we can just create a textrun
- * from the transformed string and hand it back to the caller, because string
- * offsets are the same. If extra characters are inserted due to
- * uppercased SZLIG, we have to create a special text run, nsGermanTextRun,
- * which adjusts character offsets for us.
- */
-gfxTextRun*
-nsCaseTransformTextRunFactory::MakeTextRun(const PRUnichar* aString, PRUint32 aLength,
-                                           gfxTextRunFactory::Parameters* aParams)
-{
-  // Create a textrun for the transformed string
-  gfxSkipChars dummy;
-  gfxTextRunFactory::Parameters innerParams = GetParametersForInner(*aParams, &dummy);
-  innerParams.mFlags &= ~gfxTextRunFactory::TEXT_IS_PERSISTENT;
-
+  nsAutoPtr<gfxTextRun> child;
+  // Setup actual line break data for child (which may affect shaping)
+  innerParams.mInitialBreaks = lineBreakBeforeArray.Elements();
+  innerParams.mInitialBreakCount = lineBreakBeforeArray.Length();
   if (mInnerTransformingTextRunFactory) {
-    mInnerTransformingTextRunFactory->SetStyles(mStyles);
+    child = mInnerTransformingTextRunFactory->MakeTextRun(
+        convertedString.BeginReading(), convertedString.Length(),
+        &innerParams, styleArray.Elements());
+  } else {
+    child = mFontGroup->MakeTextRun(
+        convertedString.BeginReading(), convertedString.Length(), &innerParams);
   }
-
-  // This is the dummy that nsMultiTextRun needs
-  nsAutoPtr<gfxTextRun> innerTextRun;
-  innerTextRun =
-    mInnerTextRunFactory->MakeTextRun(aString, aLength, &innerParams);
-  if (!innerTextRun)
-    return nsnull;
-  // Make sure the inner textrun never calls ForceRememberText, because we don't
-  // implement it
-  innerTextRun->RememberText(aString, aLength);
-
-  nsAutoPtr<nsCaseTransformingTextRun> textRun;
-  textRun =
-    new nsCaseTransformingTextRun(innerTextRun, aParams, this, aString, aLength);
-  if (!textRun)
-    return nsnull;
-  innerTextRun.forget();
-  nsresult rv = textRun->Build();
-  if (NS_FAILED(rv))
-    return nsnull;
-  return textRun.forget();
+  if (!child)
+    return;
+  // Copy potential linebreaks into child so they're preserved
+  // (and also child will be shaped appropriately)
+  NS_ASSERTION(convertedString.Length() == canBreakBeforeArray.Length(),
+               "Dropped characters or break-before values somewhere!");
+  child->SetPotentialLineBreaks(0, canBreakBeforeArray.Length(), canBreakBeforeArray.Elements());
+  // Now merge multiple characters into one multi-glyph character as required
+  MergeCharactersInTextRun(aTextRun, child, charsToMergeArray.Elements());
 }
--- a/layout/generic/nsTextRunTransformations.h
+++ b/layout/generic/nsTextRunTransformations.h
@@ -35,90 +35,67 @@
  *
  * ***** END LICENSE BLOCK ***** */
 
 #ifndef NSTEXTRUNTRANSFORMATIONS_H_
 #define NSTEXTRUNTRANSFORMATIONS_H_
 
 #include "gfxFont.h"
 
-class nsCaseTransformingTextRun;
+class nsTransformedTextRun;
 class nsStyleContext;
 
-class nsTransformingTextRunFactory : public gfxTextRunFactory {
+class nsTransformingTextRunFactory {
 public:
-  nsTransformingTextRunFactory(gfxTextRunFactory* aInnerTextRunFactory,
-                               nsTransformingTextRunFactory* aInnerTransformingTextRunFactory)
-    : mInnerTextRunFactory(aInnerTextRunFactory),
-      mInnerTransformingTextRunFactory(aInnerTransformingTextRunFactory),
-      mStyles(nsnull) {}
+  virtual ~nsTransformingTextRunFactory() {}
 
-  // Assign style contexts to each character in the string. Call this before
-  // calling MakeTextRun. The array needs to have a lifetime covering the
-  // call to MakeTextRun.
-  virtual void SetStyles(nsStyleContext** aStyles) { mStyles = aStyles; }
   // Default 8-bit path just transforms to Unicode and takes that path
-  virtual gfxTextRun* MakeTextRun(const PRUint8* aString, PRUint32 aLength,
-                                  Parameters* aParams);
-  // Redeclare this to make C++ compiler shut up
-  virtual gfxTextRun* MakeTextRun(const PRUnichar* aString, PRUint32 aLength,
-                                  Parameters* aParams) = 0;
+  gfxTextRun* MakeTextRun(const PRUint8* aString, PRUint32 aLength,
+                          gfxFontGroup::Parameters* aParams, nsStyleContext** aStyles);
+  gfxTextRun* MakeTextRun(const PRUnichar* aString, PRUint32 aLength,
+                          gfxFontGroup::Parameters* aParams, nsStyleContext** aStyles);
 
-protected:
-  nsRefPtr<gfxTextRunFactory>            mInnerTextRunFactory;
-  nsRefPtr<nsTransformingTextRunFactory> mInnerTransformingTextRunFactory;
-  nsStyleContext**                       mStyles;
+  virtual void RebuildTextRun(nsTransformedTextRun* aTextRun) = 0;
 };
 
 /**
  * Builds textruns that render their text using a font-variant (i.e.,
  * smallcaps).
  */
 class nsFontVariantTextRunFactory : public nsTransformingTextRunFactory {
 public:
   nsFontVariantTextRunFactory(gfxFontGroup* aFontGroup)
-    : nsTransformingTextRunFactory(aFontGroup, nsnull), mFontGroup(aFontGroup) {}
+    : mFontGroup(aFontGroup) {}
     
-  // Redeclare this to make C++ compiler shut up
-  virtual gfxTextRun* MakeTextRun(const PRUint8* aString, PRUint32 aLength,
-                                  Parameters* aParams) {
-    return nsTransformingTextRunFactory::MakeTextRun(aString, aLength, aParams);
-  }
-  virtual gfxTextRun* MakeTextRun(const PRUnichar* aString, PRUint32 aLength,
-                                  Parameters* aParams);
+  virtual void RebuildTextRun(nsTransformedTextRun* aTextRun);
 
-private:
+protected:
   nsRefPtr<gfxFontGroup> mFontGroup;
 };
 
 /**
  * Builds textruns that transform the text in some way (e.g., capitalize)
  * and then render the text using some other textrun implementation.
  */
 class nsCaseTransformTextRunFactory : public nsTransformingTextRunFactory {
 public:
-  nsCaseTransformTextRunFactory(gfxTextRunFactory* aInnerTextRunFactory,
+  // We could add an optimization here so that when there is no inner
+  // factory, no title-case conversion, and no upper-casing of SZLIG, we override
+  // MakeTextRun (after making it virtual in the superclass) and have it
+  // just convert the string to uppercase or lowercase and create the textrun
+  // via the fontgroup.
+  
+  nsCaseTransformTextRunFactory(gfxFontGroup* aFontGroup,
                                 nsTransformingTextRunFactory* aInnerTransformingTextRunFactory,
                                 PRBool aAllUppercase = PR_FALSE)
-    : nsTransformingTextRunFactory(aInnerTextRunFactory, aInnerTransformingTextRunFactory),
+    : mFontGroup(aFontGroup),
+      mInnerTransformingTextRunFactory(aInnerTransformingTextRunFactory),
       mAllUppercase(aAllUppercase) {}
 
-  // Redeclare this to make C++ compiler shut up
-  virtual gfxTextRun* MakeTextRun(const PRUint8* aString, PRUint32 aLength,
-                                  Parameters* aParams) {
-    return nsTransformingTextRunFactory::MakeTextRun(aString, aLength, aParams);
-  }
-  virtual gfxTextRun* MakeTextRun(const PRUnichar* aString, PRUint32 aLength,
-                                  Parameters* aParams);
+  virtual void RebuildTextRun(nsTransformedTextRun* aTextRun);
 
-  // We need these for the implementation of case-transforming text runs
-  gfxTextRunFactory* GetInnerTextRunFactory() { return mInnerTextRunFactory; }
-  nsTransformingTextRunFactory* GetInnerTransformingTextRunFactory() {
-    return mInnerTransformingTextRunFactory;
-  }
-  nsStyleContext** GetStyles() { return mStyles; }
-  PRBool IsAllUppercase() { return mAllUppercase; }
-
-private:
-  PRPackedBool mAllUppercase;
+protected:
+  nsRefPtr<gfxFontGroup>                  mFontGroup;
+  nsAutoPtr<nsTransformingTextRunFactory> mInnerTransformingTextRunFactory;
+  PRPackedBool                            mAllUppercase;
 };
 
 #endif /*NSTEXTRUNTRANSFORMATIONS_H_*/