Bug 389421. Rework word selection, in particular so that layout.word_select.stop_at_punctuation is applied to boundaries between punctuation and non-punctuation, and all Unicode punctuation is treated as punctuation.

This commit is contained in:
roc+@cs.cmu.edu 2007-08-29 20:10:19 -07:00
parent 171e7e67b5
commit bbc848d48f
5 changed files with 97 additions and 37 deletions

View File

@ -68,7 +68,7 @@ public:
virtual PRBool PeekOffsetNoAmount(PRBool aForward, PRInt32* aOffset);
virtual PRBool PeekOffsetCharacter(PRBool aForward, PRInt32* aOffset);
virtual PRBool PeekOffsetWord(PRBool aForward, PRBool aWordSelectEatSpace, PRBool aIsKeyboardSelect,
PRInt32* aOffset, PRBool* aSawBeforeType);
PRInt32* aOffset, PeekWordState* aState);
NS_IMETHOD Reflow(nsPresContext* aPresContext,
nsHTMLReflowMetrics& aDesiredSize,
@ -261,7 +261,7 @@ BRFrame::PeekOffsetCharacter(PRBool aForward, PRInt32* aOffset)
PRBool
BRFrame::PeekOffsetWord(PRBool aForward, PRBool aWordSelectEatSpace, PRBool aIsKeyboardSelect,
PRInt32* aOffset, PRBool* aSawBeforeType)
PRInt32* aOffset, PeekWordState* aState)
{
NS_ASSERTION (aOffset && *aOffset <= 1, "aOffset out of range");
// Keep going. The actual line jumping will stop us.

View File

@ -4647,7 +4647,7 @@ nsIFrame::PeekOffset(nsPeekOffsetStruct* aPos)
nsContentUtils::GetBoolPref("layout.word_select.eat_space_to_next_word");
}
// sawBeforeType means "we already saw characters of the type
// mSawBeforeType means "we already saw characters of the type
// before the boundary we're looking for". Examples:
// 1. If we're moving forward, looking for a word beginning (i.e. a boundary
// between whitespace and non-whitespace), then eatingWS==PR_TRUE means
@ -4655,15 +4655,14 @@ nsIFrame::PeekOffset(nsPeekOffsetStruct* aPos)
// 2. If we're moving backward, looking for a word beginning (i.e. a boundary
// between non-whitespace and whitespace), then eatingWS==PR_TRUE means
// "we already saw some non-whitespace".
PRBool sawBeforeType = PR_FALSE;
PeekWordState state;
PRBool done = PR_FALSE;
while (!done) {
PRBool movingInFrameDirection =
IsMovingInFrameDirection(current, aPos->mDirection, aPos->mVisual);
done = current->PeekOffsetWord(movingInFrameDirection, wordSelectEatSpace, aPos->mIsKeyboardSelect,
&offset, &sawBeforeType);
done = current->PeekOffsetWord(movingInFrameDirection, wordSelectEatSpace,
aPos->mIsKeyboardSelect, &offset, &state);
if (!done) {
nsIFrame* nextFrame;
@ -4676,14 +4675,14 @@ nsIFrame::PeekOffset(nsPeekOffsetStruct* aPos)
// 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 && sawBeforeType) {
jumpedLine && !wordSelectEatSpace && state.mSawBeforeType) {
done = PR_TRUE;
} else {
current = nextFrame;
offset = nextFrameOffset;
// Jumping a line is equivalent to encountering whitespace
if (wordSelectEatSpace && jumpedLine)
sawBeforeType = PR_TRUE;
state.SetSawBeforeType();
}
}
}
@ -4889,7 +4888,7 @@ nsFrame::PeekOffsetCharacter(PRBool aForward, PRInt32* aOffset)
PRBool
nsFrame::PeekOffsetWord(PRBool aForward, PRBool aWordSelectEatSpace, PRBool aIsKeyboardSelect,
PRInt32* aOffset, PRBool* aSawBeforeType)
PRInt32* aOffset, PeekWordState* aState)
{
NS_ASSERTION (aOffset && *aOffset <= 1, "aOffset out of range");
PRInt32 startOffset = *aOffset;
@ -4898,16 +4897,41 @@ nsFrame::PeekOffsetWord(PRBool aForward, PRBool aWordSelectEatSpace, PRBool aIsK
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 (aWordSelectEatSpace && *aSawBeforeType)
return PR_TRUE;
if (!aState->mAtStart) {
if (aState->mLastCharWasPunctuation) {
// We're not punctuation, so this is a punctuation boundary.
if (BreakWordBetweenPunctuation(aForward, aIsKeyboardSelect))
return PR_TRUE;
} else {
// This is not a punctuation boundary.
if (aWordSelectEatSpace && aState->mSawBeforeType)
return PR_TRUE;
}
}
// Otherwise skip to the other side and note that we encountered non-whitespace.
*aOffset = 1 - startOffset;
aState->Update(PR_FALSE);
if (!aWordSelectEatSpace)
*aSawBeforeType = PR_TRUE;
aState->SetSawBeforeType();
}
return PR_FALSE;
}
PRBool
nsFrame::BreakWordBetweenPunctuation(PRBool aAfterPunct, PRBool aIsKeyboardSelect)
{
if (!nsContentUtils::GetBoolPref("layout.word_select.stop_at_punctuation")) {
// When this pref is false, we never stop at a punctuation boundary.
return PR_FALSE;
}
if (!aIsKeyboardSelect) {
// mouse caret movement (e.g. word selection) always stops at every punctuation boundary
return PR_TRUE;
}
// keyboard caret movement stops after punctuation, not before it
return aAfterPunct;
}
NS_IMETHODIMP
nsFrame::CheckVisibility(nsPresContext* , PRInt32 , PRInt32 , PRBool , PRBool *, PRBool *)
{

View File

@ -248,7 +248,13 @@ public:
virtual PRBool PeekOffsetNoAmount(PRBool aForward, PRInt32* aOffset);
virtual PRBool PeekOffsetCharacter(PRBool aForward, PRInt32* aOffset);
virtual PRBool PeekOffsetWord(PRBool aForward, PRBool aWordSelectEatSpace, PRBool aIsKeyboardSelect,
PRInt32* aOffset, PRBool* aSawBeforeType);
PRInt32* aOffset, PeekWordState *aState);
/**
* Check whether we should break at a boundary between punctuation and
* non-punctuation.
* @param aAfterPunct true when this point is logically *after* punctuation.
*/
PRBool BreakWordBetweenPunctuation(PRBool aAfterPunct, PRBool aIsKeyboardSelect);
NS_IMETHOD CheckVisibility(nsPresContext* aContext, PRInt32 aStartIndex, PRInt32 aEndIndex, PRBool aRecurse, PRBool *aFinished, PRBool *_retval);

View File

@ -2110,14 +2110,31 @@ protected:
* as a word on its own.
* @param aOffset [in/out] At what offset into the frame to start looking.
* on output - what offset was reached (whether or not we found a place to stop).
* @param aSawBeforeType [in/out] Did we encounter a character of the pre-boundary type
* (whitespace if aWordSelectEatSpace is true, non-whitespace otherwise).
* @param aState [in/out] the state that is carried from frame to frame
* @return PR_TRUE: An appropriate offset was found within this frame,
* and is given by aOffset.
* PR_FALSE: Not found within this frame, need to try the next frame.
*/
struct PeekWordState {
// 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;
PeekWordState() : mAtStart(PR_TRUE), mSawBeforeType(PR_FALSE),
mLastCharWasPunctuation(PR_FALSE) {}
void SetSawBeforeType() { mSawBeforeType = PR_TRUE; }
void Update(PRBool aAfterPunctuation) {
mLastCharWasPunctuation = aAfterPunctuation;
mAtStart = PR_FALSE;
}
};
virtual PRBool PeekOffsetWord(PRBool aForward, PRBool aWordSelectEatSpace, PRBool aIsKeyboardSelect,
PRInt32* aOffset, PRBool* aSawBeforeType) = 0;
PRInt32* aOffset, PeekWordState* aState) = 0;
/**
* Search for the first paragraph boundary before or after the given position

View File

@ -82,6 +82,8 @@
#include "nsTextFrameTextRunCache.h"
#include "nsExpirationTracker.h"
#include "nsICaseConversion.h"
#include "nsIUGenCategory.h"
#include "nsUnicharUtilCIID.h"
#include "nsTextFragment.h"
#include "nsGkAtoms.h"
@ -392,7 +394,7 @@ public:
virtual PRBool PeekOffsetNoAmount(PRBool aForward, PRInt32* aOffset);
virtual PRBool PeekOffsetCharacter(PRBool aForward, PRInt32* aOffset);
virtual PRBool PeekOffsetWord(PRBool aForward, PRBool aWordSelectEatSpace, PRBool aIsKeyboardSelect,
PRInt32* aOffset, PRBool* aSawBeforeType);
PRInt32* aOffset, PeekWordState* aState);
NS_IMETHOD CheckVisibility(nsPresContext* aContext, PRInt32 aStartIndex, PRInt32 aEndIndex, PRBool aRecurse, PRBool *aFinished, PRBool *_retval);
@ -4669,6 +4671,7 @@ public:
PRInt32 GetBeforeOffset();
private:
nsCOMPtr<nsIUGenCategory> mCategories;
gfxSkipCharsIterator mIterator;
const nsTextFragment* mFrag;
nsTextFrame* mTextFrame;
@ -4742,7 +4745,10 @@ PRBool
ClusterIterator::IsPunctuation()
{
NS_ASSERTION(mCharIndex >= 0, "No cluster selected");
return nsTextFrameUtils::IsPunctuationMark(mFrag->CharAt(mCharIndex));
if (!mCategories)
return PR_FALSE;
nsIUGenCategory::nsUGenCategory c = mCategories->Get(mFrag->CharAt(mCharIndex));
return c == nsIUGenCategory::kPunctuation || c == nsIUGenCategory::kSymbol;
}
PRBool
@ -4810,26 +4816,32 @@ ClusterIterator::ClusterIterator(nsTextFrame* aTextFrame, PRInt32 aPosition,
}
mIterator.SetOriginalOffset(aPosition);
mCategories = do_GetService(NS_UNICHARCATEGORY_CONTRACTID);
mFrag = aTextFrame->GetContent()->GetText();
mTrimmed = aTextFrame->GetTrimmedOffsets(mFrag, PR_TRUE);
PRInt32 textLen = aTextFrame->GetContentLength();
if (!mWordBreaks.AppendElements(textLen)) {
if (!mWordBreaks.AppendElements(textLen + 1)) {
mDirection = 0; // signal failure
return;
}
memset(mWordBreaks.Elements(), PR_FALSE, textLen);
memset(mWordBreaks.Elements(), PR_FALSE, textLen + 1);
nsAutoString text;
mFrag->AppendTo(text, aTextFrame->GetContentOffset(), textLen);
nsIWordBreaker* wordBreaker = nsContentUtils::WordBreaker();
PRInt32 i = 0;
while ((i = wordBreaker->NextWord(text.get(), textLen, 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.
}
PRBool
nsTextFrame::PeekOffsetWord(PRBool aForward, PRBool aWordSelectEatSpace, PRBool aIsKeyboardSelect,
PRInt32* aOffset, PRBool* aSawBeforeType)
PRInt32* aOffset, PeekWordState* aState)
{
PRInt32 contentLength = GetContentLength();
NS_ASSERTION (aOffset && *aOffset <= contentLength, "aOffset out of range");
@ -4846,24 +4858,25 @@ nsTextFrame::PeekOffsetWord(PRBool aForward, PRBool aWordSelectEatSpace, PRBool
if (!cIter.NextCluster())
return PR_FALSE;
PRBool stopAfterPunctuation =
nsContentUtils::GetBoolPref("layout.word_select.stop_at_punctuation");
PRBool stopBeforePunctuation = stopAfterPunctuation && !aIsKeyboardSelect;
do {
if (aWordSelectEatSpace == cIter.IsWhitespace() && !*aSawBeforeType) {
*aSawBeforeType = PR_TRUE;
PRBool isPunctuation = cIter.IsPunctuation();
if (aWordSelectEatSpace == cIter.IsWhitespace() && !aState->mSawBeforeType) {
aState->SetSawBeforeType();
aState->Update(isPunctuation);
continue;
}
if (cIter.GetBeforeOffset() != offset &&
(cIter.IsPunctuation() ? stopBeforePunctuation
: cIter.HaveWordBreakBefore() && *aSawBeforeType)) {
*aOffset = cIter.GetBeforeOffset() - mContentOffset;
return PR_TRUE;
}
if (stopAfterPunctuation && cIter.IsPunctuation()) {
*aOffset = cIter.GetAfterOffset() - mContentOffset;
return PR_TRUE;
// See if we can break before the current cluster
if (!aState->mAtStart) {
PRBool canBreak = isPunctuation != aState->mLastCharWasPunctuation
? BreakWordBetweenPunctuation(aForward ? aState->mLastCharWasPunctuation : isPunctuation,
aIsKeyboardSelect)
: cIter.HaveWordBreakBefore() && aState->mSawBeforeType;
if (canBreak) {
*aOffset = cIter.GetBeforeOffset() - mContentOffset;
return PR_TRUE;
}
}
aState->Update(isPunctuation);
} while (cIter.NextCluster());
*aOffset = cIter.GetAfterOffset() - mContentOffset;