Bug 391584 - Pass around a context string so we can detect word breaks at the beginning or end of frames [p=roc r=smontagu a=blocking1.9+]
authorreed@reedloden.com
Wed, 07 Nov 2007 20:33:28 -0800
changeset 7679 2ae4e6d1e02f694273402175d2df6a57f7b571a2
parent 7678 2b91bbb618415ba0850eda6bba0655fc62bb0bcf
child 7680 a6b8c2b350ef044326650fb27fba83b11518e862
push idunknown
push userunknown
push dateunknown
reviewerssmontagu, blocking1.9
bugs391584
milestone1.9b2pre
Bug 391584 - Pass around a context string so we can detect word breaks at the beginning or end of frames [p=roc r=smontagu a=blocking1.9+]
layout/generic/nsFrame.cpp
layout/generic/nsIFrame.h
layout/generic/nsTextFrameThebes.cpp
--- a/layout/generic/nsFrame.cpp
+++ b/layout/generic/nsFrame.cpp
@@ -4704,16 +4704,19 @@ nsIFrame::PeekOffset(nsPeekOffsetStruct*
                                            aPos->mJumpLines, aPos->mScrollViewStop,
                                            &nextFrame, &nextFrameOffset, &jumpedLine);
           // We can't jump lines if we're looking for whitespace following
           // non-whitespace, and we already encountered non-whitespace.
           if (NS_FAILED(result) ||
               jumpedLine && !wordSelectEatSpace && state.mSawBeforeType) {
             done = PR_TRUE;
           } else {
+            if (jumpedLine) {
+              state.mContext.Truncate();
+            }
             current = nextFrame;
             offset = nextFrameOffset;
             // Jumping a line is equivalent to encountering whitespace
             if (wordSelectEatSpace && jumpedLine)
               state.SetSawBeforeType();
           }
         }
       }
@@ -4918,16 +4921,18 @@ nsFrame::PeekOffsetCharacter(PRBool aFor
 }
 
 PRBool
 nsFrame::PeekOffsetWord(PRBool aForward, PRBool aWordSelectEatSpace, PRBool aIsKeyboardSelect,
                         PRInt32* aOffset, PeekWordState* aState)
 {
   NS_ASSERTION (aOffset && *aOffset <= 1, "aOffset out of range");
   PRInt32 startOffset = *aOffset;
+  // This isn't text, so truncate the context
+  aState->mContext.Truncate();
   if (startOffset < 0)
     startOffset = 1;
   if (aForward == (startOffset == 0)) {
     // We're before the frame and moving forward, or after it and moving backwards.
     // If we're looking for non-whitespace, we found it (without skipping this frame).
     if (!aState->mAtStart) {
       if (aState->mLastCharWasPunctuation) {
         // We're not punctuation, so this is a punctuation boundary.
--- a/layout/generic/nsIFrame.h
+++ b/layout/generic/nsIFrame.h
@@ -2135,16 +2135,19 @@ protected:
     // true when we're still at the start of the search, i.e., we can't return
     // this point as a valid offset!
     PRPackedBool mAtStart;
     // true when we've encountered at least one character of the pre-boundary type
     // (whitespace if aWordSelectEatSpace is true, non-whitespace otherwise)
     PRPackedBool mSawBeforeType;
     // true when the last character encountered was punctuation
     PRPackedBool mLastCharWasPunctuation;
+    // text that's *before* the current frame when aForward is true, *after*
+    // the current frame when aForward is false.
+    nsAutoString mContext;
 
     PeekWordState() : mAtStart(PR_TRUE), mSawBeforeType(PR_FALSE),
         mLastCharWasPunctuation(PR_FALSE) {}
     void SetSawBeforeType() { mSawBeforeType = PR_TRUE; }
     void Update(PRBool aAfterPunctuation) {
       mLastCharWasPunctuation = aAfterPunctuation;
       mAtStart = PR_FALSE;
     }
--- a/layout/generic/nsTextFrameThebes.cpp
+++ b/layout/generic/nsTextFrameThebes.cpp
@@ -4415,17 +4415,18 @@ nsTextFrame::PeekOffsetNoAmount(PRBool a
  * aPosition (which is a content offset). You can test each cluster
  * to see if it's whitespace (as far as selection/caret movement is concerned),
  * or punctuation, or if there is a word break before the cluster. ("Before"
  * is interpreted according to aDirection, so if aDirection is -1, "before"
  * means actually *after* the cluster content.)
  */
 class ClusterIterator {
 public:
-  ClusterIterator(nsTextFrame* aTextFrame, PRInt32 aPosition, PRInt32 aDirection);
+  ClusterIterator(nsTextFrame* aTextFrame, PRInt32 aPosition, PRInt32 aDirection,
+                  nsString& aContext);
 
   PRBool NextCluster();
   PRBool IsWhitespace();
   PRBool IsPunctuation();
   PRBool HaveWordBreakBefore() { return mHaveWordBreak; }
   PRInt32 GetAfterOffset();
   PRInt32 GetBeforeOffset();
 
@@ -4557,17 +4558,17 @@ ClusterIterator::NextCluster()
       mHaveWordBreak = PR_TRUE;
     }
     if (!keepGoing)
       return PR_TRUE;
   }
 }
 
 ClusterIterator::ClusterIterator(nsTextFrame* aTextFrame, PRInt32 aPosition,
-                                 PRInt32 aDirection)
+                                 PRInt32 aDirection, nsString& aContext)
   : mTextFrame(aTextFrame), mDirection(aDirection), mCharIndex(-1)
 {
   mIterator = aTextFrame->EnsureTextRun();
   if (!aTextFrame->GetTextRun()) {
     mDirection = 0; // signal failure
     return;
   }
   mIterator.SetOriginalOffset(aPosition);
@@ -4579,31 +4580,42 @@ ClusterIterator::ClusterIterator(nsTextF
 
   PRInt32 textOffset = aTextFrame->GetContentOffset();
   PRInt32 textLen = aTextFrame->GetContentLength();
   if (!mWordBreaks.AppendElements(textLen + 1)) {
     mDirection = 0; // signal failure
     return;
   }
   memset(mWordBreaks.Elements(), PR_FALSE, textLen + 1);
-  nsAutoString text;
-  mFrag->AppendTo(text, textOffset, textLen);
+  PRInt32 textStart;
+  if (aDirection > 0) {
+    if (aContext.IsEmpty()) {
+      // No previous context, so it must be the start of a line or text run
+      mWordBreaks[0] = PR_TRUE;
+    }
+    textStart = aContext.Length();
+    mFrag->AppendTo(aContext, textOffset, textLen);
+  } else {
+    if (aContext.IsEmpty()) {
+      // No following context, so it must be the end of a line or text run
+      mWordBreaks[textLen] = PR_TRUE;
+    }
+    textStart = 0;
+    nsAutoString str;
+    mFrag->AppendTo(str, textOffset, textLen);
+    aContext.Insert(str, 0);
+  }
   nsIWordBreaker* wordBreaker = nsContentUtils::WordBreaker();
-  PRInt32 i = 0;
-  while ((i = wordBreaker->NextWord(text.get(), textLen, i)) >= 0) {
-    mWordBreaks[i] = PR_TRUE;
-  }
-  // XXX this never allows word breaks at the start or end of the frame, but to fix
-  // this we would need to rewrite word-break detection to use the text from
-  // textruns or something. Not a regression, at least. For now we can make things
-  // a little better by noting a word break opportunity when the frame starts
-  // or ends with white-space.
-  if (textLen > 0) {
-    mWordBreaks[0] = IsSelectionSpace(mFrag, textOffset);
-    mWordBreaks[textLen] = IsSelectionSpace(mFrag, textOffset + textLen - 1);
+  PRInt32 i;
+  for (i = 0; i <= textLen; ++i) {
+    PRInt32 indexInText = i + textStart;
+    mWordBreaks[i] |=
+      wordBreaker->BreakInBetween(aContext.get(), indexInText,
+                                  aContext.get() + indexInText,
+                                  aContext.Length() - indexInText);
   }
 }
 
 PRBool
 nsTextFrame::PeekOffsetWord(PRBool aForward, PRBool aWordSelectEatSpace, PRBool aIsKeyboardSelect,
                             PRInt32* aOffset, PeekWordState* aState)
 {
   PRInt32 contentLength = GetContentLength();
@@ -4611,17 +4623,17 @@ nsTextFrame::PeekOffsetWord(PRBool aForw
 
   PRBool selectable;
   PRUint8 selectStyle;
   IsSelectable(&selectable, &selectStyle);
   if (selectStyle == NS_STYLE_USER_SELECT_ALL)
     return PR_FALSE;
 
   PRInt32 offset = GetContentOffset() + (*aOffset < 0 ? contentLength : *aOffset);
-  ClusterIterator cIter(this, offset, aForward ? 1 : -1);
+  ClusterIterator cIter(this, offset, aForward ? 1 : -1, aState->mContext);
 
   if (!cIter.NextCluster())
     return PR_FALSE;
   
   do {
     PRBool isPunctuation = cIter.IsPunctuation();
     if (aWordSelectEatSpace == cIter.IsWhitespace() && !aState->mSawBeforeType) {
       aState->SetSawBeforeType();