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:
roc+@cs.cmu.edu 2007-06-12 14:01:12 -07:00
Родитель e0bedb85f1
Коммит 3a2c98339c
3 изменённых файлов: 465 добавлений и 306 удалений

Просмотреть файл

@ -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) {
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,15 +1100,73 @@ 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
// text run boundary is required we flush textRunFrames ((re)building their
@ -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) {
@ -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, &params, 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, &params, textFlags);
}
}
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();
@ -1635,12 +1829,22 @@ BuildTextRunsScanner::SetupBreakSinksForTextRun(gfxTextRun* aTextRun,
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 {
*aOffset = aForward ? 0 : mContentLength;
}
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;
}
}
}
*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 {