зеркало из https://github.com/mozilla/pjs.git
Landing textframe updates: Use word-based textrun cache; rewritten PeekOffsetWord; adjust textframe content-offsets and content-lengths when CharacterDataChanged fires; search backwards for textrun scanning/creation start point, instead of all the way back to the start of the block; ensure that we pass the correct start iterator to PropertyProvider in TrimTrailingWhiteSpace.
This commit is contained in:
Родитель
e0bedb85f1
Коммит
3a2c98339c
|
@ -94,6 +94,7 @@
|
|||
#include "nsCSSRendering.h"
|
||||
#include "nsContentUtils.h"
|
||||
#include "nsLineBreaker.h"
|
||||
#include "nsIWordBreaker.h"
|
||||
|
||||
#include "nsILineIterator.h"
|
||||
|
||||
|
@ -112,6 +113,7 @@
|
|||
#include "nsIThebesFontMetrics.h"
|
||||
#include "gfxFont.h"
|
||||
#include "gfxContext.h"
|
||||
#include "gfxTextRunWordCache.h"
|
||||
|
||||
#ifdef NS_DEBUG
|
||||
#undef NOISY_BLINK
|
||||
|
@ -604,10 +606,6 @@ static FrameTextRunCache *gTextRuns = nsnull;
|
|||
|
||||
/*
|
||||
* Cache textruns and expire them after 3*10 seconds of no use.
|
||||
* Our textruns are either:
|
||||
* -- not tracked and not in the cache
|
||||
* -- tracked but not in the cache and nsTextFrameUtils::TEXT_IS_UNCACHED is set
|
||||
* -- tracked, in the cache, and nsTextFrameUtils::TEXT_IS_UNCACHED is not set
|
||||
*/
|
||||
class FrameTextRunCache : public nsExpirationTracker<gfxTextRun,3> {
|
||||
public:
|
||||
|
@ -619,7 +617,9 @@ public:
|
|||
}
|
||||
|
||||
void RemoveFromCache(gfxTextRun* aTextRun) {
|
||||
RemoveObject(aTextRun);
|
||||
if (aTextRun->GetExpirationState()->IsTracked()) {
|
||||
RemoveObject(aTextRun);
|
||||
}
|
||||
if (!(aTextRun->GetFlags() & nsTextFrameUtils::TEXT_IS_UNCACHED)) {
|
||||
mCache.RemoveTextRun(aTextRun);
|
||||
}
|
||||
|
@ -632,23 +632,7 @@ public:
|
|||
delete aTextRun;
|
||||
}
|
||||
|
||||
class InnerCache : public gfxTextRunCache {
|
||||
public:
|
||||
// If a textrun is removed from the cache, and has no associated frame(s),
|
||||
// then we may as well destroy it now. We should stop tracking it
|
||||
// in any case; even if it has an associated frame, there is no point
|
||||
// in keeping it around after it's dropped from that frame.
|
||||
virtual void NotifyRemovedFromCache(gfxTextRun* aTextRun) {
|
||||
if (aTextRun->GetExpirationState()->IsTracked()) {
|
||||
gTextRuns->RemoveObject(aTextRun);
|
||||
}
|
||||
if (aTextRun->GetUserData())
|
||||
return;
|
||||
delete aTextRun;
|
||||
}
|
||||
};
|
||||
|
||||
InnerCache mCache;
|
||||
gfxTextRunWordCache mCache;
|
||||
};
|
||||
|
||||
static gfxTextRun *
|
||||
|
@ -662,13 +646,20 @@ MakeTextRun(const PRUnichar *aText, PRUint32 aLength,
|
|||
} else if (aLength == 1 && aText[0] == ' ') {
|
||||
textRun = aFontGroup->MakeSpaceTextRun(aParams, aFlags | nsTextFrameUtils::TEXT_IS_UNCACHED);
|
||||
} else {
|
||||
textRun = gTextRuns->mCache.GetOrMakeTextRun(aText, aLength, aFontGroup, aParams, aFlags);
|
||||
PRBool isInCache;
|
||||
textRun = gTextRuns->mCache.MakeTextRun(aText, aLength, aFontGroup,
|
||||
aParams, aFlags, &isInCache);
|
||||
if (!isInCache && textRun) {
|
||||
textRun->SetFlagBits(nsTextFrameUtils::TEXT_IS_UNCACHED);
|
||||
}
|
||||
}
|
||||
if (!textRun)
|
||||
return nsnull;
|
||||
nsresult rv = gTextRuns->AddObject(textRun);
|
||||
if (NS_FAILED(rv))
|
||||
if (NS_FAILED(rv)) {
|
||||
gTextRuns->RemoveFromCache(textRun);
|
||||
return nsnull;
|
||||
}
|
||||
return textRun.forget();
|
||||
}
|
||||
|
||||
|
@ -683,13 +674,20 @@ MakeTextRun(const PRUint8 *aText, PRUint32 aLength,
|
|||
} else if (aLength == 1 && aText[0] == ' ') {
|
||||
textRun = aFontGroup->MakeSpaceTextRun(aParams, aFlags | nsTextFrameUtils::TEXT_IS_UNCACHED);
|
||||
} else {
|
||||
textRun = gTextRuns->mCache.GetOrMakeTextRun(aText, aLength, aFontGroup, aParams, aFlags);
|
||||
PRBool isInCache;
|
||||
textRun = gTextRuns->mCache.MakeTextRun(aText, aLength, aFontGroup,
|
||||
aParams, aFlags, &isInCache);
|
||||
if (!isInCache && textRun) {
|
||||
textRun->SetFlagBits(nsTextFrameUtils::TEXT_IS_UNCACHED);
|
||||
}
|
||||
}
|
||||
if (!textRun)
|
||||
return nsnull;
|
||||
nsresult rv = gTextRuns->AddObject(textRun);
|
||||
if (NS_FAILED(rv))
|
||||
if (NS_FAILED(rv)) {
|
||||
gTextRuns->RemoveFromCache(textRun);
|
||||
return nsnull;
|
||||
}
|
||||
return textRun.forget();
|
||||
}
|
||||
|
||||
|
@ -770,6 +768,20 @@ static PRBool IsSpace(const nsTextFragment* aFrag, PRUint32 aPos)
|
|||
}
|
||||
}
|
||||
|
||||
static PRBool IsSelectionSpace(const nsTextFragment* aFrag, PRUint32 aPos)
|
||||
{
|
||||
NS_ASSERTION(aPos < aFrag->GetLength(), "No text for IsSpace!");
|
||||
PRUnichar ch = aFrag->CharAt(aPos);
|
||||
if (ch == ' ' || ch == 0x00A0/*NBSP*/) {
|
||||
if (!aFrag->Is2b())
|
||||
return PR_TRUE;
|
||||
return !nsTextFrameUtils::IsSpaceCombiningSequenceTail(
|
||||
aFrag->Get2b() + aPos + 1, aFrag->GetLength() - (aPos + 1));
|
||||
} else {
|
||||
return ch == '\t' || ch == '\n' || ch == '\f';
|
||||
}
|
||||
}
|
||||
|
||||
static PRUint32 GetWhitespaceCount(const nsTextFragment* frag, PRInt32 aStartOffset,
|
||||
PRInt32 aLength, PRInt32 aDirection)
|
||||
{
|
||||
|
@ -805,16 +817,22 @@ static PRUint32 GetWhitespaceCount(const nsTextFragment* frag, PRInt32 aStartOff
|
|||
*/
|
||||
class BuildTextRunsScanner {
|
||||
public:
|
||||
BuildTextRunsScanner(nsPresContext* aPresContext, gfxContext* aContext) :
|
||||
BuildTextRunsScanner(nsPresContext* aPresContext, gfxContext* aContext,
|
||||
nsIFrame* aLineContainer) :
|
||||
mCurrentFramesAllSameTextRun(nsnull),
|
||||
mContext(aContext), mBidiEnabled(aPresContext->BidiEnabled()),
|
||||
mTrimNextRunLeadingWhitespace(PR_FALSE) {
|
||||
mContext(aContext),
|
||||
mLineContainer(aLineContainer),
|
||||
mBidiEnabled(aPresContext->BidiEnabled()),
|
||||
mTrimNextRunLeadingWhitespace(PR_FALSE), mSkipIncompleteTextRuns(PR_FALSE) {
|
||||
ResetRunInfo();
|
||||
}
|
||||
|
||||
void SetAtStartOfLine() {
|
||||
mStartOfLine = PR_TRUE;
|
||||
}
|
||||
void SetSkipIncompleteTextRuns(PRBool aSkip) {
|
||||
mSkipIncompleteTextRuns = aSkip;
|
||||
}
|
||||
void SetCommonAncestorWithLastFrame(nsIFrame* aFrame) {
|
||||
mCommonAncestorWithLastFrame = aFrame;
|
||||
}
|
||||
|
@ -840,7 +858,22 @@ public:
|
|||
void BuildTextRunForFrames(void* aTextBuffer);
|
||||
void AssignTextRun(gfxTextRun* aTextRun);
|
||||
nsTextFrame* GetNextBreakBeforeFrame(PRUint32* aIndex);
|
||||
void SetupBreakSinksForTextRun(gfxTextRun* aTextRun, PRBool aIsExistingTextRun);
|
||||
void SetupBreakSinksForTextRun(gfxTextRun* aTextRun, PRBool aIsExistingTextRun,
|
||||
PRBool aSuppressSink);
|
||||
struct FindBoundaryState {
|
||||
nsIFrame* mStopAtFrame;
|
||||
nsTextFrame* mFirstTextFrame;
|
||||
nsTextFrame* mLastTextFrame;
|
||||
PRPackedBool mSeenTextRunBoundaryOnLaterLine;
|
||||
PRPackedBool mSeenTextRunBoundaryOnThisLine;
|
||||
PRPackedBool mSeenSpaceForLineBreakingOnThisLine;
|
||||
};
|
||||
enum FindBoundaryResult {
|
||||
FB_CONTINUE,
|
||||
FB_STOPPED_AT_STOP_FRAME,
|
||||
FB_FOUND_VALID_TEXTRUN_BOUNDARY
|
||||
};
|
||||
FindBoundaryResult FindBoundaries(nsIFrame* aFrame, FindBoundaryState* aState);
|
||||
|
||||
PRBool StylesMatchForTextRun(nsIFrame* aFrame1, nsIFrame* aFrame2);
|
||||
|
||||
|
@ -890,6 +923,7 @@ private:
|
|||
nsLineBreaker mLineBreaker;
|
||||
gfxTextRun* mCurrentFramesAllSameTextRun;
|
||||
gfxContext* mContext;
|
||||
nsIFrame* mLineContainer;
|
||||
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. Otherwise
|
||||
|
@ -902,6 +936,7 @@ private:
|
|||
PRPackedBool mStartOfLine;
|
||||
PRPackedBool mTrimNextRunLeadingWhitespace;
|
||||
PRPackedBool mCurrentRunTrimLeadingWhitespace;
|
||||
PRPackedBool mSkipIncompleteTextRuns;
|
||||
};
|
||||
|
||||
static nsIFrame*
|
||||
|
@ -913,14 +948,100 @@ FindLineContainer(nsIFrame* aFrame)
|
|||
return aFrame;
|
||||
}
|
||||
|
||||
static PRBool
|
||||
TextContainsLineBreakerWhiteSpace(const void* aText, PRUint32 aLength,
|
||||
PRBool aIsDoubleByte)
|
||||
{
|
||||
PRUint32 i;
|
||||
if (aIsDoubleByte) {
|
||||
const PRUnichar* chars = NS_STATIC_CAST(const PRUnichar*, aText);
|
||||
for (i = 0; i < aLength; ++i) {
|
||||
if (nsLineBreaker::IsSpace(chars[i]))
|
||||
return PR_TRUE;
|
||||
}
|
||||
return PR_FALSE;
|
||||
} else {
|
||||
const PRUint8* chars = NS_STATIC_CAST(const PRUint8*, aText);
|
||||
for (i = 0; i < aLength; ++i) {
|
||||
if (nsLineBreaker::IsSpace(chars[i]))
|
||||
return PR_TRUE;
|
||||
}
|
||||
return PR_FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
BuildTextRunsScanner::FindBoundaryResult
|
||||
BuildTextRunsScanner::FindBoundaries(nsIFrame* aFrame, FindBoundaryState* aState)
|
||||
{
|
||||
nsTextFrame* textFrame = aFrame->GetType() == nsGkAtoms::textFrame
|
||||
? NS_STATIC_CAST(nsTextFrame*, aFrame) : nsnull;
|
||||
if (textFrame) {
|
||||
if (aState->mLastTextFrame &&
|
||||
textFrame != aState->mLastTextFrame->GetNextInFlow() &&
|
||||
!StylesMatchForTextRun(aState->mLastTextFrame, textFrame)) {
|
||||
aState->mSeenTextRunBoundaryOnThisLine = PR_TRUE;
|
||||
if (aState->mSeenSpaceForLineBreakingOnThisLine)
|
||||
return FB_FOUND_VALID_TEXTRUN_BOUNDARY;
|
||||
}
|
||||
if (!aState->mFirstTextFrame) {
|
||||
aState->mFirstTextFrame = textFrame;
|
||||
}
|
||||
aState->mLastTextFrame = textFrame;
|
||||
}
|
||||
|
||||
if (aFrame == aState->mStopAtFrame)
|
||||
return FB_STOPPED_AT_STOP_FRAME;
|
||||
|
||||
if (textFrame) {
|
||||
if (!aState->mSeenSpaceForLineBreakingOnThisLine) {
|
||||
const nsTextFragment* frag = textFrame->GetContent()->GetText();
|
||||
PRUint32 start = textFrame->GetContentOffset();
|
||||
const void* text = frag->Is2b()
|
||||
? NS_STATIC_CAST(const void*, frag->Get2b() + start)
|
||||
: NS_STATIC_CAST(const void*, frag->Get1b() + start);
|
||||
if (TextContainsLineBreakerWhiteSpace(text, textFrame->GetContentLength(),
|
||||
frag->Is2b())) {
|
||||
aState->mSeenSpaceForLineBreakingOnThisLine = PR_TRUE;
|
||||
if (aState->mSeenTextRunBoundaryOnLaterLine)
|
||||
return FB_FOUND_VALID_TEXTRUN_BOUNDARY;
|
||||
}
|
||||
}
|
||||
return FB_CONTINUE;
|
||||
}
|
||||
|
||||
PRBool continueTextRun = aFrame->CanContinueTextRun();
|
||||
PRBool descendInto = PR_TRUE;
|
||||
if (!continueTextRun) {
|
||||
// XXX do we need this? are there frames we need to descend into that aren't
|
||||
// float-containing-blocks?
|
||||
descendInto = !aFrame->IsFloatContainingBlock();
|
||||
aState->mSeenTextRunBoundaryOnThisLine = PR_TRUE;
|
||||
if (aState->mSeenSpaceForLineBreakingOnThisLine)
|
||||
return FB_FOUND_VALID_TEXTRUN_BOUNDARY;
|
||||
}
|
||||
|
||||
if (descendInto) {
|
||||
nsIFrame* child = aFrame->GetFirstChild(nsnull);
|
||||
while (child) {
|
||||
FindBoundaryResult result = FindBoundaries(child, aState);
|
||||
if (result != FB_CONTINUE)
|
||||
return result;
|
||||
child = child->GetNextSibling();
|
||||
}
|
||||
}
|
||||
|
||||
if (!continueTextRun) {
|
||||
aState->mSeenTextRunBoundaryOnThisLine = PR_TRUE;
|
||||
if (aState->mSeenSpaceForLineBreakingOnThisLine)
|
||||
return FB_FOUND_VALID_TEXTRUN_BOUNDARY;
|
||||
}
|
||||
|
||||
return FB_CONTINUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
* if they're still valid. We could make this a lot more complicated if we
|
||||
* need to squeeze out performance.
|
||||
* to build text runs that span content nodes.
|
||||
*
|
||||
* @param aForFrameLine the line containing aForFrame; if null, we'll figure
|
||||
* out the line (slowly)
|
||||
|
@ -934,16 +1055,13 @@ BuildTextRuns(nsIRenderingContext* aRC, nsTextFrame* aForFrame,
|
|||
if (!aLineContainer) {
|
||||
aLineContainer = FindLineContainer(aForFrame);
|
||||
} else {
|
||||
NS_ASSERTION(aLineContainer == FindLineContainer(aForFrame), "Wrong line container hint");
|
||||
NS_ASSERTION(!aForFrame || aLineContainer == FindLineContainer(aForFrame), "Wrong line container hint");
|
||||
}
|
||||
|
||||
nsPresContext* presContext = aForFrame->PresContext();
|
||||
nsPresContext* presContext = aLineContainer->PresContext();
|
||||
gfxContext* ctx = NS_STATIC_CAST(gfxContext*,
|
||||
aRC->GetNativeGraphicData(nsIRenderingContext::NATIVE_THEBES_CONTEXT));
|
||||
BuildTextRunsScanner scanner(presContext, ctx);
|
||||
|
||||
scanner.SetAtStartOfLine();
|
||||
scanner.SetCommonAncestorWithLastFrame(nsnull);
|
||||
BuildTextRunsScanner scanner(presContext, ctx, aLineContainer);
|
||||
|
||||
nsBlockFrame* block = nsnull;
|
||||
aLineContainer->QueryInterface(kBlockFrameCID, (void**)&block);
|
||||
|
@ -951,10 +1069,11 @@ BuildTextRuns(nsIRenderingContext* aRC, nsTextFrame* aForFrame,
|
|||
if (!block) {
|
||||
// Just loop through all the children of the linecontainer ... it's really
|
||||
// just one line
|
||||
scanner.SetAtStartOfLine();
|
||||
scanner.SetCommonAncestorWithLastFrame(nsnull);
|
||||
nsIFrame* child = aLineContainer->GetFirstChild(nsnull);
|
||||
while (child) {
|
||||
scanner.ScanFrame(child);
|
||||
scanner.LiftCommonAncestorWithLastFrameToParent(aLineContainer);
|
||||
child = child->GetNextSibling();
|
||||
}
|
||||
// Set mStartOfLine so FlushFrames knows its textrun ends a line
|
||||
|
@ -963,12 +1082,12 @@ BuildTextRuns(nsIRenderingContext* aRC, nsTextFrame* aForFrame,
|
|||
return;
|
||||
}
|
||||
|
||||
// Find line where we can start building text runs. It's the first line at
|
||||
// or before aForFrameLine that's after a hard break.
|
||||
// Find the line containing aForFrame
|
||||
nsBlockFrame::line_iterator line;
|
||||
if (aForFrameLine) {
|
||||
line = *aForFrameLine;
|
||||
} else {
|
||||
NS_ASSERTION(aForFrame, "One of aForFrame or aForFrameLine must be set!");
|
||||
nsIFrame* immediateChild =
|
||||
nsLayoutUtils::FindChildContainingDescendant(block, aForFrame);
|
||||
// This may be a float e.g. for a floated first-letter
|
||||
|
@ -981,14 +1100,72 @@ BuildTextRuns(nsIRenderingContext* aRC, nsTextFrame* aForFrame,
|
|||
NS_ASSERTION(line != block->end_lines(),
|
||||
"Frame is not in the block!!!");
|
||||
}
|
||||
|
||||
// Find a line where we can start building text runs. We choose the last line
|
||||
// where:
|
||||
// -- there is a textrun boundary between the start of the line and the
|
||||
// start of aForFrame
|
||||
// -- there is a space between the start of the line and the textrun boundary
|
||||
// (this is so we can be sure the line breaks will be set properly
|
||||
// on the textruns we construct).
|
||||
// The possibly-partial text runs up to and including the first space
|
||||
// are not reconstructed. We construct partial text runs for that text ---
|
||||
// for the sake of simplifying the code and feeding the linebreaker ---
|
||||
// but we discard them instead of assigning them to frames.
|
||||
// This is a little awkward because we traverse lines in the reverse direction
|
||||
// but we traverse the frames in each line in the forward direction.
|
||||
nsBlockFrame::line_iterator firstLine = block->begin_lines();
|
||||
while (line != firstLine) {
|
||||
--line;
|
||||
if (line->IsBlock()) {
|
||||
++line;
|
||||
nsTextFrame* stopAtFrame = aForFrame;
|
||||
nsTextFrame* nextLineFirstTextFrame = nsnull;
|
||||
PRBool seenTextRunBoundaryOnLaterLine = PR_FALSE;
|
||||
PRBool mayBeginInTextRun = PR_TRUE;
|
||||
while (PR_TRUE) {
|
||||
if (line == firstLine) {
|
||||
mayBeginInTextRun = PR_FALSE;
|
||||
break;
|
||||
}
|
||||
--line;
|
||||
PRBool prevLineIsBlock = line->IsBlock();
|
||||
++line;
|
||||
if (prevLineIsBlock) {
|
||||
mayBeginInTextRun = PR_FALSE;
|
||||
break;
|
||||
}
|
||||
|
||||
BuildTextRunsScanner::FindBoundaryState state = { stopAtFrame, nsnull, nsnull,
|
||||
seenTextRunBoundaryOnLaterLine, PR_FALSE, PR_FALSE };
|
||||
nsIFrame* child = line->mFirstChild;
|
||||
PRBool foundBoundary = PR_FALSE;
|
||||
PRInt32 i;
|
||||
for (i = line->GetChildCount() - 1; i >= 0; --i) {
|
||||
BuildTextRunsScanner::FindBoundaryResult result =
|
||||
scanner.FindBoundaries(child, &state);
|
||||
if (result == BuildTextRunsScanner::FB_FOUND_VALID_TEXTRUN_BOUNDARY) {
|
||||
foundBoundary = PR_TRUE;
|
||||
break;
|
||||
} else if (result == BuildTextRunsScanner::FB_STOPPED_AT_STOP_FRAME) {
|
||||
break;
|
||||
}
|
||||
child = child->GetNextSibling();
|
||||
}
|
||||
if (foundBoundary)
|
||||
break;
|
||||
if (!stopAtFrame && state.mLastTextFrame && nextLineFirstTextFrame &&
|
||||
!scanner.StylesMatchForTextRun(state.mLastTextFrame, nextLineFirstTextFrame)) {
|
||||
// Found a textrun boundary at the end of the line
|
||||
if (state.mSeenSpaceForLineBreakingOnThisLine)
|
||||
break;
|
||||
seenTextRunBoundaryOnLaterLine = PR_TRUE;
|
||||
} else if (state.mSeenTextRunBoundaryOnThisLine) {
|
||||
seenTextRunBoundaryOnLaterLine = PR_TRUE;
|
||||
}
|
||||
stopAtFrame = nsnull;
|
||||
if (state.mFirstTextFrame) {
|
||||
nextLineFirstTextFrame = state.mFirstTextFrame;
|
||||
}
|
||||
--line;
|
||||
}
|
||||
scanner.SetSkipIncompleteTextRuns(mayBeginInTextRun);
|
||||
|
||||
// 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
|
||||
|
@ -1003,7 +1180,6 @@ BuildTextRuns(nsIRenderingContext* aRC, nsTextFrame* aForFrame,
|
|||
PRInt32 i;
|
||||
for (i = line->GetChildCount() - 1; i >= 0; --i) {
|
||||
scanner.ScanFrame(child);
|
||||
scanner.LiftCommonAncestorWithLastFrameToParent(block);
|
||||
child = child->GetNextSibling();
|
||||
}
|
||||
++line;
|
||||
|
@ -1035,17 +1211,17 @@ void BuildTextRunsScanner::FlushFrames(PRBool aFlushLineBreaks)
|
|||
if (mMappedFlows.Length() == 0)
|
||||
return;
|
||||
|
||||
if (mCurrentFramesAllSameTextRun &&
|
||||
if (!mSkipIncompleteTextRuns && mCurrentFramesAllSameTextRun &&
|
||||
((mCurrentFramesAllSameTextRun->GetFlags() & nsTextFrameUtils::TEXT_INCOMING_WHITESPACE) != 0) ==
|
||||
mCurrentRunTrimLeadingWhitespace) {
|
||||
// We do not need to (re)build the textrun.
|
||||
// Optimization: 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.
|
||||
SetupBreakSinksForTextRun(mCurrentFramesAllSameTextRun, PR_TRUE);
|
||||
SetupBreakSinksForTextRun(mCurrentFramesAllSameTextRun, PR_TRUE, PR_FALSE);
|
||||
mTrimNextRunLeadingWhitespace =
|
||||
(mCurrentFramesAllSameTextRun->GetFlags() & nsTextFrameUtils::TEXT_TRAILING_WHITESPACE) != 0;
|
||||
} else {
|
||||
|
@ -1190,7 +1366,6 @@ void BuildTextRunsScanner::ScanFrame(nsIFrame* aFrame)
|
|||
nsIFrame* f;
|
||||
for (f = aFrame->GetFirstChild(nsnull); f; f = f->GetNextSibling()) {
|
||||
ScanFrame(f);
|
||||
LiftCommonAncestorWithLastFrameToParent(aFrame);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1199,6 +1374,8 @@ void BuildTextRunsScanner::ScanFrame(nsIFrame* aFrame)
|
|||
mCommonAncestorWithLastFrame = nsnull;
|
||||
mTrimNextRunLeadingWhitespace = PR_FALSE;
|
||||
}
|
||||
|
||||
LiftCommonAncestorWithLastFrameToParent(aFrame->GetParent());
|
||||
}
|
||||
|
||||
nsTextFrame*
|
||||
|
@ -1285,7 +1462,8 @@ BuildTextRunsScanner::BuildTextRunForFrames(void* aTextBuffer)
|
|||
PRBool anyTextTransformStyle = PR_FALSE;
|
||||
nsIContent* lastContent = nsnull;
|
||||
PRInt32 endOfLastContent = 0;
|
||||
PRUint32 textFlags = gfxTextRunFactory::TEXT_NEED_BOUNDING_BOX;
|
||||
PRUint32 textFlags = gfxTextRunFactory::TEXT_NEED_BOUNDING_BOX |
|
||||
nsTextFrameUtils::TEXT_NO_BREAKS;
|
||||
|
||||
if (mCurrentRunTrimLeadingWhitespace) {
|
||||
textFlags |= nsTextFrameUtils::TEXT_INCOMING_WHITESPACE;
|
||||
|
@ -1442,19 +1620,22 @@ BuildTextRunsScanner::BuildTextRunForFrames(void* aTextBuffer)
|
|||
|
||||
PRUint32 transformedLength = currentTransformedTextOffset;
|
||||
|
||||
if (!(textFlags & nsTextFrameUtils::TEXT_WAS_TRANSFORMED) &&
|
||||
mMappedFlows.Length() == 1) {
|
||||
// The textrun maps one continuous, unmodified run of DOM text. It can
|
||||
// point to the DOM text directly.
|
||||
const nsTextFragment* frag = lastContent->GetText();
|
||||
if (frag->Is2b()) {
|
||||
textPtr = frag->Get2b() + mMappedFlows[0].mContentOffset;
|
||||
} else {
|
||||
textPtr = frag->Get1b() + mMappedFlows[0].mContentOffset;
|
||||
}
|
||||
textFlags |= gfxTextRunFactory::TEXT_IS_PERSISTENT;
|
||||
}
|
||||
|
||||
// Disable this because it breaks the word cache. Disable at least until
|
||||
// we have a CharacterDataWillChange notification.
|
||||
//
|
||||
// if (!(textFlags & nsTextFrameUtils::TEXT_WAS_TRANSFORMED) &&
|
||||
// mMappedFlows.Length() == 1) {
|
||||
// // The textrun maps one continuous, unmodified run of DOM text. It can
|
||||
// // point to the DOM text directly.
|
||||
// const nsTextFragment* frag = lastContent->GetText();
|
||||
// if (frag->Is2b()) {
|
||||
// textPtr = frag->Get2b() + mMappedFlows[0].mContentOffset;
|
||||
// } else {
|
||||
// textPtr = frag->Get1b() + mMappedFlows[0].mContentOffset;
|
||||
// }
|
||||
// textFlags |= gfxTextRunFactory::TEXT_IS_PERSISTENT;
|
||||
// }
|
||||
//
|
||||
// Now build the textrun
|
||||
nsTextFrame* firstFrame = mMappedFlows[0].mStartFrame;
|
||||
gfxFontGroup* fontGroup = GetFontGroupForFrame(firstFrame);
|
||||
|
@ -1484,6 +1665,7 @@ BuildTextRunsScanner::BuildTextRunForFrames(void* aTextBuffer)
|
|||
styles.AppendElement(sc);
|
||||
}
|
||||
}
|
||||
textFlags |= nsTextFrameUtils::TEXT_IS_UNCACHED;
|
||||
}
|
||||
|
||||
if (textFlags & nsTextFrameUtils::TEXT_HAS_TAB) {
|
||||
|
@ -1501,7 +1683,7 @@ BuildTextRunsScanner::BuildTextRunForFrames(void* aTextBuffer)
|
|||
if (ShouldDisableLigatures(firstFrame->GetStyleText())) {
|
||||
textFlags |= gfxTextRunFactory::TEXT_DISABLE_LIGATURES;
|
||||
}
|
||||
|
||||
|
||||
gfxSkipChars skipChars;
|
||||
skipChars.TakeFrom(&builder);
|
||||
// Convert linebreak coordinates to transformed string offsets
|
||||
|
@ -1517,12 +1699,12 @@ BuildTextRunsScanner::BuildTextRunForFrames(void* aTextBuffer)
|
|||
++nextBreakIndex;
|
||||
}
|
||||
|
||||
gfxTextRun* textRun;
|
||||
gfxTextRunFactory::Parameters params =
|
||||
{ mContext, finalUserData, &skipChars,
|
||||
textBreakPoints.Elements(), nextBreakIndex,
|
||||
firstFrame->PresContext()->AppUnitsPerDevPixel() };
|
||||
|
||||
gfxTextRun* textRun;
|
||||
if (mDoubleByteText) {
|
||||
const PRUnichar* text = NS_STATIC_CAST(const PRUnichar*, textPtr);
|
||||
if (transformingFactory) {
|
||||
|
@ -1535,10 +1717,6 @@ BuildTextRunsScanner::BuildTextRunForFrames(void* aTextBuffer)
|
|||
} else {
|
||||
textRun = MakeTextRun(text, transformedLength, fontGroup, ¶ms, textFlags);
|
||||
}
|
||||
if (!textRun) {
|
||||
DestroyUserData(userData);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
const PRUint8* text = NS_STATIC_CAST(const PRUint8*, textPtr);
|
||||
textFlags |= gfxFontGroup::TEXT_IS_8BIT;
|
||||
|
@ -1552,16 +1730,28 @@ BuildTextRunsScanner::BuildTextRunForFrames(void* aTextBuffer)
|
|||
} else {
|
||||
textRun = MakeTextRun(text, transformedLength, fontGroup, ¶ms, textFlags);
|
||||
}
|
||||
if (!textRun) {
|
||||
DestroyUserData(userData);
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (!textRun) {
|
||||
DestroyUserData(userData);
|
||||
return;
|
||||
}
|
||||
|
||||
// We have to set these up after we've created the textrun, because
|
||||
// the breaks may be stored in the textrun during this very call.
|
||||
// This is a bit annoying because it requires another loop over the frames
|
||||
// making up the textrun, but I don't see a way to avoid this.
|
||||
SetupBreakSinksForTextRun(textRun, PR_FALSE);
|
||||
SetupBreakSinksForTextRun(textRun, PR_FALSE, mSkipIncompleteTextRuns);
|
||||
|
||||
if (mSkipIncompleteTextRuns) {
|
||||
mSkipIncompleteTextRuns = !TextContainsLineBreakerWhiteSpace(textPtr,
|
||||
transformedLength, mDoubleByteText);
|
||||
|
||||
// Nuke the textrun
|
||||
gTextRuns->RemoveFromCache(textRun);
|
||||
delete textRun;
|
||||
DestroyUserData(userData);
|
||||
return;
|
||||
}
|
||||
|
||||
// Actually wipe out the textruns associated with the mapped frames and associate
|
||||
// those frames with this text run.
|
||||
|
@ -1593,7 +1783,8 @@ HasCompressedLeadingWhitespace(nsTextFrame* aFrame, PRInt32 aContentEndOffset,
|
|||
|
||||
void
|
||||
BuildTextRunsScanner::SetupBreakSinksForTextRun(gfxTextRun* aTextRun,
|
||||
PRBool aIsExistingTextRun)
|
||||
PRBool aIsExistingTextRun,
|
||||
PRBool aSuppressSink)
|
||||
{
|
||||
// textruns have uniform language
|
||||
nsIAtom* lang = mMappedFlows[0].mStartFrame->GetStyleVisibility()->mLangGroup;
|
||||
|
@ -1623,8 +1814,11 @@ BuildTextRunsScanner::SetupBreakSinksForTextRun(gfxTextRun* aTextRun,
|
|||
|
||||
if (length > 0) {
|
||||
PRUint32 flags = 0;
|
||||
if (!mappedFlow->mAncestorControllingInitialBreak ||
|
||||
mappedFlow->mAncestorControllingInitialBreak->GetStyleText()->WhiteSpaceCanWrap()) {
|
||||
nsIFrame* initialBreakController = mappedFlow->mAncestorControllingInitialBreak;
|
||||
if (!initialBreakController) {
|
||||
initialBreakController = mLineContainer;
|
||||
}
|
||||
if (initialBreakController->GetStyleText()->WhiteSpaceCanWrap()) {
|
||||
flags |= nsLineBreaker::BREAK_ALLOW_INITIAL;
|
||||
}
|
||||
const nsStyleText* textStyle = startFrame->GetStyleText();
|
||||
|
@ -1634,13 +1828,23 @@ BuildTextRunsScanner::SetupBreakSinksForTextRun(gfxTextRun* aTextRun,
|
|||
// and after each whitespace character
|
||||
flags |= nsLineBreaker::BREAK_ALLOW_INSIDE;
|
||||
}
|
||||
|
||||
|
||||
BreakSink* sink = *breakSink;
|
||||
if (aSuppressSink) {
|
||||
sink = nsnull;
|
||||
} else if (flags) {
|
||||
aTextRun->ClearFlagBits(nsTextFrameUtils::TEXT_NO_BREAKS);
|
||||
} else if (aTextRun->GetFlags() & nsTextFrameUtils::TEXT_NO_BREAKS) {
|
||||
// Don't bother setting breaks on a textrun that can't be broken
|
||||
// and currently has no breaks set...
|
||||
sink = nsnull;
|
||||
}
|
||||
if (aTextRun->GetFlags() & gfxFontGroup::TEXT_IS_8BIT) {
|
||||
mLineBreaker.AppendText(lang, aTextRun->GetText8Bit() + offset,
|
||||
length, flags, *breakSink);
|
||||
length, flags, sink);
|
||||
} else {
|
||||
mLineBreaker.AppendText(lang, aTextRun->GetTextUnicode() + offset,
|
||||
length, flags, *breakSink);
|
||||
length, flags, sink);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3071,6 +3275,7 @@ nsContinuingTextFrame::Destroy()
|
|||
if (mPrevContinuation || mNextContinuation) {
|
||||
nsSplittableFrame::RemoveFromFlow(this);
|
||||
}
|
||||
ClearTextRun();
|
||||
// Let the base class destroy the frame
|
||||
nsFrame::Destroy();
|
||||
}
|
||||
|
@ -3250,19 +3455,21 @@ nsTextFrame::ClearTextRun()
|
|||
return;
|
||||
|
||||
UnhookTextRunFromFrames(textRun);
|
||||
if (textRun->GetFlags() & gfxFontGroup::TEXT_IS_PERSISTENT) {
|
||||
// the textrun's text may be referencing a DOM node that's changing,
|
||||
// or that will change, so we'd better kill this textrun now.
|
||||
if (textRun->GetExpirationState()->IsTracked()) {
|
||||
gTextRuns->RemoveFromCache(textRun);
|
||||
}
|
||||
delete textRun;
|
||||
return;
|
||||
}
|
||||
// see comments in BuildTextRunForFrames...
|
||||
// if (textRun->GetFlags() & gfxFontGroup::TEXT_IS_PERSISTENT) {
|
||||
// NS_ERROR("Shouldn't reach here for now...");
|
||||
// // the textrun's text may be referencing a DOM node that has changed,
|
||||
// // so we'd better kill this textrun now.
|
||||
// if (textRun->GetExpirationState()->IsTracked()) {
|
||||
// gTextRuns->RemoveFromCache(textRun);
|
||||
// }
|
||||
// delete textRun;
|
||||
// return;
|
||||
// }
|
||||
|
||||
// If it's not tracked (i.e. in the cache), then delete it now.
|
||||
// Otherwise it stays alive in case it gets hit in the cache.
|
||||
if (!textRun->GetExpirationState()->IsTracked()) {
|
||||
if (textRun->GetFlags() & nsTextFrameUtils::TEXT_IS_UNCACHED) {
|
||||
// Remove it now because it's not doing anything useful
|
||||
gTextRuns->RemoveFromCache(textRun);
|
||||
delete textRun;
|
||||
}
|
||||
}
|
||||
|
@ -3292,8 +3499,12 @@ nsTextFrame::CharacterDataChanged(nsPresContext* aPresContext,
|
|||
do {
|
||||
textFrame->mState &= ~TEXT_WHITESPACE_FLAGS;
|
||||
// If the text node has shrunk, clip the frame contentlength as necessary
|
||||
textFrame->mContentLength = PR_MIN(mContentLength, newLength);
|
||||
newLength -= textFrame->mContentLength;
|
||||
if (textFrame->mContentOffset > newLength) {
|
||||
textFrame->mContentOffset = newLength;
|
||||
}
|
||||
if (textFrame->mContentOffset + textFrame->mContentLength > newLength) {
|
||||
textFrame->mContentLength = newLength - textFrame->mContentOffset;
|
||||
}
|
||||
lastTextFrame = textFrame;
|
||||
textFrame = NS_STATIC_CAST(nsTextFrame*, textFrame->GetNextContinuation());
|
||||
if (!textFrame) {
|
||||
|
@ -3304,7 +3515,7 @@ nsTextFrame::CharacterDataChanged(nsPresContext* aPresContext,
|
|||
targetTextFrame = this;
|
||||
}
|
||||
// Set the length of the last text frame in the chain (necessary if the node grew)
|
||||
lastTextFrame->mContentLength = PR_MAX(0, nodeLength - lastTextFrame->mContentOffset);
|
||||
lastTextFrame->mContentLength = nodeLength - lastTextFrame->mContentOffset;
|
||||
|
||||
// Ask the parent frame to reflow me.
|
||||
aPresContext->GetPresShell()->FrameNeedsReflow(targetTextFrame,
|
||||
|
@ -4333,6 +4544,35 @@ nsTextFrame::PeekOffsetNoAmount(PRBool aForward, PRInt32* aOffset)
|
|||
iter.ConvertOriginalToSkipped(trimmed.mStart);
|
||||
}
|
||||
|
||||
/**
|
||||
* This class iterates through the clusters before or after the given
|
||||
* 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);
|
||||
|
||||
PRBool NextCluster();
|
||||
PRBool IsWhitespace();
|
||||
PRBool IsPunctuation();
|
||||
PRBool HaveWordBreakBefore();
|
||||
PRInt32 GetAfterOffset();
|
||||
PRInt32 GetBeforeOffset();
|
||||
|
||||
private:
|
||||
gfxSkipCharsIterator mIterator;
|
||||
const nsTextFragment* mFrag;
|
||||
nsTextFrame* mTextFrame;
|
||||
PRInt32 mDirection;
|
||||
PRInt32 mCharIndex;
|
||||
nsTextFrame::TrimmedOffsets mTrimmed;
|
||||
nsTArray<PRPackedBool> mWordBreaks;
|
||||
};
|
||||
|
||||
PRBool
|
||||
nsTextFrame::PeekOffsetCharacter(PRBool aForward, PRInt32* aOffset)
|
||||
{
|
||||
|
@ -4385,6 +4625,109 @@ nsTextFrame::PeekOffsetCharacter(PRBool aForward, PRInt32* aOffset)
|
|||
return PR_FALSE;
|
||||
}
|
||||
|
||||
PRBool
|
||||
ClusterIterator::IsWhitespace()
|
||||
{
|
||||
return IsSelectionSpace(mFrag, mCharIndex);
|
||||
}
|
||||
|
||||
PRBool
|
||||
ClusterIterator::IsPunctuation()
|
||||
{
|
||||
return nsTextFrameUtils::IsPunctuationMark(mFrag->CharAt(mCharIndex));
|
||||
}
|
||||
|
||||
PRBool
|
||||
ClusterIterator::HaveWordBreakBefore()
|
||||
{
|
||||
return mWordBreaks[GetBeforeOffset() - mTextFrame->GetContentOffset()];
|
||||
}
|
||||
|
||||
PRInt32
|
||||
ClusterIterator::GetBeforeOffset()
|
||||
{
|
||||
return mCharIndex + (mDirection > 0 ? 0 : 1);
|
||||
}
|
||||
|
||||
PRInt32
|
||||
ClusterIterator::GetAfterOffset()
|
||||
{
|
||||
return mCharIndex + (mDirection > 0 ? 1 : 0);
|
||||
}
|
||||
|
||||
PRBool
|
||||
ClusterIterator::NextCluster()
|
||||
{
|
||||
if (!mDirection)
|
||||
return PR_FALSE;
|
||||
gfxTextRun* textRun = mTextFrame->GetTextRun();
|
||||
|
||||
while (PR_TRUE) {
|
||||
if (mDirection > 0) {
|
||||
if (mIterator.GetOriginalOffset() >= mTrimmed.mStart + mTrimmed.mLength)
|
||||
return PR_FALSE;
|
||||
if (mIterator.IsOriginalCharSkipped() ||
|
||||
mIterator.GetOriginalOffset() < mTrimmed.mStart ||
|
||||
!textRun->IsClusterStart(mIterator.GetSkippedOffset())) {
|
||||
mIterator.AdvanceOriginal(1);
|
||||
continue;
|
||||
}
|
||||
mCharIndex = mIterator.GetOriginalOffset();
|
||||
mIterator.AdvanceOriginal(1);
|
||||
} else {
|
||||
if (mIterator.GetOriginalOffset() <= mTrimmed.mStart)
|
||||
return PR_FALSE;
|
||||
mIterator.AdvanceOriginal(-1);
|
||||
if (mIterator.IsOriginalCharSkipped() ||
|
||||
mIterator.GetOriginalOffset() >= mTrimmed.mStart + mTrimmed.mLength ||
|
||||
!textRun->IsClusterStart(mIterator.GetSkippedOffset()))
|
||||
continue;
|
||||
mCharIndex = mIterator.GetOriginalOffset();
|
||||
}
|
||||
|
||||
return PR_TRUE;
|
||||
}
|
||||
}
|
||||
|
||||
ClusterIterator::ClusterIterator(nsTextFrame* aTextFrame, PRInt32 aPosition,
|
||||
PRInt32 aDirection)
|
||||
: mTextFrame(aTextFrame), mDirection(aDirection), mCharIndex(-1)
|
||||
{
|
||||
mIterator = aTextFrame->EnsureTextRun();
|
||||
if (!aTextFrame->GetTextRun()) {
|
||||
mDirection = 0; // signal failure
|
||||
return;
|
||||
}
|
||||
mIterator.SetOriginalOffset(aPosition);
|
||||
|
||||
mFrag = aTextFrame->GetContent()->GetText();
|
||||
mTrimmed = aTextFrame->GetTrimmedOffsets(mFrag, PR_TRUE);
|
||||
|
||||
if (!mWordBreaks.AppendElements(aTextFrame->GetContentLength())) {
|
||||
mDirection = 0; // signal failure
|
||||
return;
|
||||
}
|
||||
nsAutoString text;
|
||||
PRInt32 textLen = aTextFrame->GetContentLength();
|
||||
mFrag->AppendTo(text, aTextFrame->GetContentOffset(), textLen);
|
||||
nsIWordBreaker* wordBreaker = nsContentUtils::WordBreaker();
|
||||
PRInt32 i = 0;
|
||||
if (i < textLen) {
|
||||
mWordBreaks[i] = PR_FALSE;
|
||||
}
|
||||
while (i < textLen) {
|
||||
PRInt32 nextWord = wordBreaker->NextWord(text.get(), textLen, i);
|
||||
if (nextWord < 0) {
|
||||
if (i + 1 < textLen) {
|
||||
memset(&mWordBreaks[i + 1], textLen - i - 1, PR_FALSE);
|
||||
}
|
||||
break;
|
||||
}
|
||||
i = nextWord;
|
||||
mWordBreaks[i] = PR_TRUE;
|
||||
}
|
||||
}
|
||||
|
||||
PRBool
|
||||
nsTextFrame::PeekOffsetWord(PRBool aForward, PRBool aWordSelectEatSpace, PRBool aIsKeyboardSelect,
|
||||
PRInt32* aOffset, PRBool* aSawBeforeType)
|
||||
|
@ -4397,69 +4740,28 @@ nsTextFrame::PeekOffsetWord(PRBool aForward, PRBool aWordSelectEatSpace, PRBool
|
|||
if (selectStyle == NS_STYLE_USER_SELECT_ALL)
|
||||
return PR_FALSE;
|
||||
|
||||
const nsTextFragment* frag = mContent->GetText();
|
||||
gfxSkipCharsIterator iter = EnsureTextRun();
|
||||
if (!mTextRun)
|
||||
return PR_FALSE;
|
||||
|
||||
TrimmedOffsets trimmed = GetTrimmedOffsets(frag, PR_TRUE);
|
||||
|
||||
// A negative offset means "end of frame".
|
||||
PRInt32 startOffset = mContentOffset + (*aOffset < 0 ? mContentLength : *aOffset);
|
||||
startOffset = PR_MIN(startOffset, trimmed.mStart + trimmed.mLength);
|
||||
|
||||
// Find DOM offset of first nonskipped character
|
||||
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;
|
||||
PRInt32 dirAdjust = aForward ? 0 : -1;
|
||||
*aOffset = aForward ? mContentLength : 0;
|
||||
if (startOffset + direction < offset ||
|
||||
startOffset + direction >= offset + length)
|
||||
return PR_FALSE;
|
||||
PRInt32 offset = mContentOffset + (*aOffset < 0 ? mContentLength : *aOffset);
|
||||
ClusterIterator cIter(this, offset, aForward ? 1 : -1);
|
||||
|
||||
PRInt32 wordLen = nsTextFrameUtils::FindWordBoundary(frag,
|
||||
mTextRun, &iter, offset, length, startOffset + dirAdjust,
|
||||
direction, stopBeforePunctuation, stopAfterPunctuation, &isWhitespace);
|
||||
if (wordLen < 0)
|
||||
return PR_FALSE;
|
||||
|
||||
if (aWordSelectEatSpace == isWhitespace || !*aSawBeforeType) {
|
||||
PRInt32 nextWordFirstChar = startOffset + dirAdjust + direction*wordLen;
|
||||
*aOffset = (aForward ? nextWordFirstChar : nextWordFirstChar + 1) - mContentOffset;
|
||||
if (aWordSelectEatSpace == isWhitespace) {
|
||||
while (cIter.NextCluster()) {
|
||||
if (aWordSelectEatSpace == cIter.IsWhitespace() && !*aSawBeforeType) {
|
||||
*aSawBeforeType = PR_TRUE;
|
||||
}
|
||||
|
||||
for (;;) {
|
||||
if (nextWordFirstChar < offset || nextWordFirstChar >= offset + length)
|
||||
return PR_FALSE;
|
||||
wordLen = nsTextFrameUtils::FindWordBoundary(frag,
|
||||
mTextRun, &iter, offset, length, nextWordFirstChar,
|
||||
direction, stopBeforePunctuation, stopAfterPunctuation, &isWhitespace) - 1;
|
||||
if (wordLen < 0 ||
|
||||
(aWordSelectEatSpace ? !isWhitespace : *aSawBeforeType))
|
||||
break;
|
||||
nextWordFirstChar = nextWordFirstChar + direction*wordLen;
|
||||
*aOffset = (aForward ? nextWordFirstChar : nextWordFirstChar + 1) - mContentOffset;
|
||||
if (aWordSelectEatSpace == isWhitespace) {
|
||||
*aSawBeforeType = PR_TRUE;
|
||||
} else {
|
||||
if ((*aSawBeforeType && cIter.HaveWordBreakBefore()) ||
|
||||
(stopBeforePunctuation && cIter.IsPunctuation())) {
|
||||
*aOffset = cIter.GetBeforeOffset() - mContentOffset;
|
||||
return PR_TRUE;
|
||||
}
|
||||
if (stopAfterPunctuation && cIter.IsPunctuation()) {
|
||||
*aOffset = cIter.GetAfterOffset() - mContentOffset;
|
||||
return PR_TRUE;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
*aOffset = aForward ? 0 : mContentLength;
|
||||
}
|
||||
return PR_TRUE;
|
||||
*aOffset = cIter.GetAfterOffset() - mContentOffset;
|
||||
return PR_FALSE;
|
||||
}
|
||||
|
||||
// TODO this needs to be deCOMtaminated with the interface fixed in
|
||||
|
@ -5170,13 +5472,14 @@ nsTextFrame::TrimTrailingWhiteSpace(nsPresContext* aPresContext,
|
|||
if (!mContentLength)
|
||||
return NS_OK;
|
||||
|
||||
gfxSkipCharsIterator iter = EnsureTextRun(&aRC);
|
||||
gfxSkipCharsIterator start = EnsureTextRun(&aRC);
|
||||
if (!mTextRun)
|
||||
return NS_ERROR_FAILURE;
|
||||
PRUint32 trimmedStart = iter.GetSkippedOffset();
|
||||
PRUint32 trimmedStart = start.GetSkippedOffset();
|
||||
|
||||
const nsTextFragment* frag = mContent->GetText();
|
||||
TrimmedOffsets trimmed = GetTrimmedOffsets(frag, PR_TRUE);
|
||||
gfxSkipCharsIterator iter = start;
|
||||
PRUint32 trimmedEnd = iter.ConvertOriginalToSkipped(trimmed.mStart + trimmed.mLength);
|
||||
const nsStyleText* textStyle = GetStyleText();
|
||||
gfxFloat delta = 0;
|
||||
|
@ -5189,7 +5492,7 @@ nsTextFrame::TrimTrailingWhiteSpace(nsPresContext* aPresContext,
|
|||
if (trimmedEnd < endOffset) {
|
||||
// We can't be dealing with tabs here ... they wouldn't be trimmed. So it's
|
||||
// OK to pass null for the line container.
|
||||
PropertyProvider provider(mTextRun, textStyle, frag, this, iter, mContentLength,
|
||||
PropertyProvider provider(mTextRun, textStyle, frag, this, start, mContentLength,
|
||||
nsnull, 0);
|
||||
delta = mTextRun->GetAdvanceWidth(trimmedEnd, endOffset - trimmedEnd, &provider);
|
||||
// non-compressed whitespace being skipped at end of line -> justifiable
|
||||
|
@ -5202,7 +5505,7 @@ nsTextFrame::TrimTrailingWhiteSpace(nsPresContext* aPresContext,
|
|||
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,
|
||||
PropertyProvider provider(mTextRun, textStyle, frag, this, start, mContentLength,
|
||||
nsnull, 0);
|
||||
PRBool isCJK = IsChineseJapaneseLangGroup(this);
|
||||
gfxSkipCharsIterator justificationEnd(iter);
|
||||
|
|
|
@ -240,118 +240,6 @@ nsTextFrameUtils::TransformText(const PRUint8* aText, PRUint32 aLength,
|
|||
return aOutput;
|
||||
}
|
||||
|
||||
// TODO The wordbreaker needs to be fixed. It's buggy, for example, it doesn't
|
||||
// handle diacriticals combined with spaces
|
||||
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;
|
||||
}
|
||||
|
||||
// like nsSampleWordBreaker::GetClass
|
||||
static SimpleCharClass Classify8BitChar(PRUint8 aChar)
|
||||
{
|
||||
if (aChar == ' ' || aChar == '\t' || aChar == '\n' || aChar == '\r')
|
||||
return CLASS_SPACE;
|
||||
if ((aChar >= 'a' && aChar <= 'z') || (aChar >= 'A' || aChar <= 'Z') ||
|
||||
(aChar >= '0' && aChar <= '9') || (aChar >= 128))
|
||||
return CLASS_ALNUM;
|
||||
return CLASS_PUNCT;
|
||||
}
|
||||
|
||||
PRInt32
|
||||
nsTextFrameUtils::FindWordBoundary(const nsTextFragment* aText,
|
||||
gfxTextRun* aTextRun,
|
||||
gfxSkipCharsIterator* aIterator,
|
||||
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 len = 0; // length of current "word", excluding first character at aPosition
|
||||
if (aText->Is2b()) {
|
||||
nsIWordBreaker* wordBreaker = nsContentUtils::WordBreaker();
|
||||
const PRUnichar* text = aText->Get2b();
|
||||
// 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 (;;) {
|
||||
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;
|
||||
}
|
||||
} else {
|
||||
const char* text = aText->Get1b();
|
||||
SimpleCharClass cl = Classify8BitChar(text[aPosition]);
|
||||
// There shouldn't be any clusters in 8bit text but we'll cover that
|
||||
// possibility anyway
|
||||
PRInt32 nextWordPos;
|
||||
do {
|
||||
++len;
|
||||
nextWordPos = aPosition + aDirection*len;
|
||||
if (nextWordPos < aOffset || nextWordPos >= aOffset + aLength)
|
||||
break;
|
||||
} while (Classify8BitChar(text[nextWordPos]) == cl ||
|
||||
!aTextRun->IsClusterStart(aIterator->ConvertOriginalToSkipped(nextWordPos)));
|
||||
}
|
||||
|
||||
// Handle punctuation breaks
|
||||
PRInt32 i;
|
||||
PRBool punctPrev = IsPunctuationMark(aText->CharAt(aPosition));
|
||||
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(pos));
|
||||
if (punct != punctPrev &&
|
||||
aTextRun->IsClusterStart(aIterator->ConvertOriginalToSkipped(pos))) {
|
||||
PRBool punctIsBefore = aDirection < 0 ? punct : punctPrev;
|
||||
if (punctIsBefore ? aBreakAfterPunctuation : aBreakBeforePunctuation)
|
||||
break;
|
||||
}
|
||||
punctPrev = punct;
|
||||
}
|
||||
PRInt32 pos = aPosition + i*aDirection;
|
||||
if (pos < aOffset || pos >= aOffset + aLength)
|
||||
return -1;
|
||||
return i;
|
||||
}
|
||||
|
||||
PRBool nsSkipCharsRunIterator::NextRun() {
|
||||
do {
|
||||
if (mRunLength) {
|
||||
|
|
|
@ -63,7 +63,8 @@ public:
|
|||
TEXT_INCOMING_WHITESPACE = 0x200000,
|
||||
TEXT_TRAILING_WHITESPACE = 0x400000,
|
||||
TEXT_COMPRESSED_LEADING_WHITESPACE = 0x800000,
|
||||
TEXT_IS_UNCACHED = 0x1000000
|
||||
TEXT_IS_UNCACHED = 0x1000000,
|
||||
TEXT_NO_BREAKS = 0x2000000
|
||||
};
|
||||
|
||||
static PRBool
|
||||
|
@ -106,39 +107,6 @@ public:
|
|||
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, 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 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.
|
||||
*/
|
||||
static PRInt32
|
||||
FindWordBoundary(const nsTextFragment* aText,
|
||||
gfxTextRun* aTextRun,
|
||||
gfxSkipCharsIterator* aIterator,
|
||||
PRInt32 aOffset, PRInt32 aLength,
|
||||
PRInt32 aPosition, PRInt32 aDirection,
|
||||
PRBool aBreakBeforePunctuation,
|
||||
PRBool aBreakAfterPunctuation,
|
||||
PRBool* aWordIsWhitespace);
|
||||
};
|
||||
|
||||
class nsSkipCharsRunIterator {
|
||||
|
|
Загрузка…
Ссылка в новой задаче