Changed text measurement code to use the new rendering context function

that measures chunks or words. Because that function is only implemented
on Win32 the code is #ifdef _WIN32
This commit is contained in:
troy%netscape.com 2000-03-31 04:27:43 +00:00
Родитель 32ffaeca59
Коммит bcb180081c
2 изменённых файлов: 1000 добавлений и 486 удалений

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

@ -453,6 +453,7 @@ public:
nscolor mSelectionTextColor;
nscolor mSelectionBGColor;
nscoord mSpaceWidth;
nscoord mAveCharWidth;
PRBool mJustifying;
PRBool mPreformatted;
PRIntn mNumSpaces;
@ -483,6 +484,7 @@ public:
aPresContext->GetMetricsFor(*plainFont, &mNormalFont);
aRenderingContext.SetFont(mNormalFont);
aRenderingContext.GetWidth(' ', mSpaceWidth);
mNormalFont->GetAveCharWidth(mAveCharWidth);
mLastFont = mNormalFont;
// Get the small-caps font if needed
@ -540,51 +542,33 @@ public:
struct TextReflowData {
PRInt32 mX; // OUT
PRInt32 mOffset; // IN/OUT How far along we are in the content
PRInt32 mPrevOffset; // OUT
PRInt32 mColumn; // IN/OUT
PRInt32 mPrevColumn; // IN/OUT
PRInt32 mLastWordLen; // OUT
PRInt32 mLastWordWidth; // OUT
nscoord mMaxWordWidth; // OUT
nscoord mPrevMaxWordWidth; // OUT
PRPackedBool mWrapping; // IN
PRPackedBool mSkipWhitespace; // IN
PRPackedBool mMeasureText; // IN
PRPackedBool mInWord; // IN
PRPackedBool mFirstLetterOK; // IN
PRPackedBool mJustDidFirstLetter; // OUT
PRPackedBool mIsBreakable; // IN/OUT
PRPackedBool mTextStartsWithNBSP; // OUT
PRPackedBool mEndsInWhitespace; // OUT
PRPackedBool mEndsInNewline; // OUT
PRPackedBool mIsBreakable; // IN
PRPackedBool mComputeMaxWordWidth; // IN
TextReflowData(PRInt32 aStartingOffset,
PRInt32 aColumn,
PRBool aWrapping,
PRBool aSkipWhitespace,
PRBool aMeasureText,
PRBool aInWord,
PRBool aFirstLetterOK,
PRBool aIsBreakable)
PRBool aIsBreakable,
PRBool aComputeMaxWordWidth)
: mX(0),
mOffset(aStartingOffset),
mPrevOffset(-1),
mColumn(aColumn),
mPrevColumn(aColumn),
mLastWordLen(0),
mLastWordWidth(0),
mMaxWordWidth(0),
mPrevMaxWordWidth(0),
mWrapping(aWrapping),
mSkipWhitespace(aSkipWhitespace),
mMeasureText(aMeasureText),
mInWord(aInWord),
mFirstLetterOK(aFirstLetterOK),
mJustDidFirstLetter(PR_FALSE),
mIsBreakable(aIsBreakable),
mTextStartsWithNBSP(PR_FALSE),
mEndsInWhitespace(PR_FALSE),
mEndsInNewline(PR_FALSE)
mComputeMaxWordWidth(aComputeMaxWordWidth)
{}
};
@ -625,12 +609,13 @@ public:
PRInt32 aWordLength,
nscoord* aWidthResult);
void MeasureIndividualWords(nsIPresContext* aPresContext,
const nsHTMLReflowState& aReflowState,
nsTextTransformer& aTx,
TextStyle& aTs,
TextReflowData& aTextData);
nsReflowStatus MeasureText(nsIPresContext* aPresContext,
const nsHTMLReflowState& aReflowState,
nsTextTransformer& aTx,
nsILineBreaker* aLb,
TextStyle& aTs,
TextReflowData& aTextData);
void GetWidth(nsIRenderingContext& aRenderingContext,
TextStyle& aStyle,
PRUnichar* aBuffer, PRInt32 aLength,
@ -2901,31 +2886,210 @@ nsTextFrame::GetOffsets(PRInt32 &start, PRInt32 &end) const
return NS_OK;
}
void
nsTextFrame::MeasureIndividualWords(nsIPresContext* aPresContext,
const nsHTMLReflowState& aReflowState,
nsTextTransformer& aTx,
TextStyle& aTs,
TextReflowData& aTextData)
#define TEXT_MAX_NUM_SEGMENTS 65
struct SegmentData {
PRUint32 mIsWhitespace : 1;
PRUint32 mContentLen : 31; // content length
PRBool IsWhitespace() {return PRBool(mIsWhitespace);}
// Get the content length. This is a running total of all
// the previous segments as well
PRInt32 ContentLen() {return PRInt32(mContentLen);}
};
#define IS_ODD_NUMBER(n) \
(0 != ((n) & 0x1))
#define IS_EVEN_NUMBER(n) \
(0 == ((n) & 0x1))
#define IS_32BIT_ALIGNED_PTR(p) \
(0 == (long(p) & 0x3))
struct TextRun {
PRUnichar mBufSpace[256];
PRUnichar* mBuf;
PRInt32 mBufSize, mBufLength;
// Words and whitespace each count as a segment
PRInt32 mNumSegments;
// Possible break points specified as offsets into the buffer
PRInt32 mBreaks[TEXT_MAX_NUM_SEGMENTS];
// Per segment data
SegmentData mSegments[TEXT_MAX_NUM_SEGMENTS];
TextRun()
{
mBuf = mBufSpace;
mBufSize = 256;
mBufLength = mNumSegments = 0;
}
~TextRun()
{
if (mBuf != mBufSpace) {
delete []mBuf;
}
}
void Reset()
{
mBufLength = 0;
mNumSegments = 0;
}
// Returns PR_TRUE if we're currently buffering text
PRBool IsBuffering()
{
return mNumSegments > 0;
}
void GrowBuffer(PRInt32 aMinBufSize)
{
// Allocate a new buffer
do {
mBufSize += 100;
} while (mBufSize < aMinBufSize);
PRUnichar* newBufSpace = new PRUnichar[mBufSize];
memcpy(newBufSpace, mBuf, mBufLength * sizeof(PRUnichar));
if (mBuf != mBufSpace) {
delete []mBuf;
}
mBuf = newBufSpace;
}
void AddWhitespace(PRUnichar* aText,
PRInt32 aNumChars,
PRInt32 aContentLen)
{
NS_PRECONDITION(mNumSegments < TEXT_MAX_NUM_SEGMENTS, "segment overflow");
NS_PRECONDITION(IS_32BIT_ALIGNED_PTR(aText), "unexpected alignment of text pointer");
// See if we have room in the buffer
PRInt32 newBufLength = mBufLength + aNumChars;
if (newBufLength > mBufSize) {
GrowBuffer(newBufLength);
}
if (1 == aNumChars) {
mBuf[mBufLength] = *aText;
} else {
::memcpy(&mBuf[mBufLength], aText, aNumChars * sizeof(PRUnichar*));
}
mBufLength = newBufLength;
mBreaks[mNumSegments] = mBufLength;
mSegments[mNumSegments].mIsWhitespace = PR_TRUE;
mSegments[mNumSegments].mContentLen = PRUint32(aContentLen);
if (mNumSegments > 0) {
mSegments[mNumSegments].mContentLen += mSegments[mNumSegments - 1].ContentLen();
}
mNumSegments++;
}
void AddWord(PRUnichar* aText,
PRInt32 aNumChars,
PRInt32 aContentLen)
{
NS_PRECONDITION(mNumSegments < TEXT_MAX_NUM_SEGMENTS, "segment overflow");
NS_PRECONDITION(IS_32BIT_ALIGNED_PTR(aText), "unexpected alignment of text pointer");
// See if we have room in the buffer
PRInt32 newBufLength = mBufLength + aNumChars;
if (newBufLength > mBufSize) {
GrowBuffer(newBufLength);
}
if (IS_EVEN_NUMBER(mBufLength)) {
// We have a small amount of text (a single word) and we're at a point
// in our buffer that is 32-bit aligned so copy 32-bit chunks at a time
int* dest = (int*)&mBuf[mBufLength];
int* src = (int*)aText;
for (int i = aNumChars / 2; i > 0; i--) {
*dest++ = *src++;
}
if (IS_ODD_NUMBER(aNumChars)) {
// Copy the one remaining character
*((PRUnichar*)dest) = *((PRUnichar*)src);
}
} else {
::memcpy(&mBuf[mBufLength], aText, aNumChars * sizeof(PRUnichar*));
}
mBufLength = newBufLength;
mBreaks[mNumSegments] = mBufLength;
mSegments[mNumSegments].mIsWhitespace = PR_FALSE;
mSegments[mNumSegments].mContentLen = PRUint32(aContentLen);
if (mNumSegments > 0) {
mSegments[mNumSegments].mContentLen += mSegments[mNumSegments - 1].ContentLen();
}
mNumSegments++;
}
};
nsReflowStatus
nsTextFrame::MeasureText(nsIPresContext* aPresContext,
const nsHTMLReflowState& aReflowState,
nsTextTransformer& aTx,
nsILineBreaker* aLb,
TextStyle& aTs,
TextReflowData& aTextData)
{
PRBool firstThing = PR_TRUE;
nscoord maxWidth = aReflowState.availableWidth;
nsLineLayout& lineLayout = *aReflowState.mLineLayout;
PRInt32 contentLength = aTx.GetContentLength();
PRInt32 startingOffset = aTextData.mOffset;
PRInt32 prevOffset = -1;
PRInt32 column = mColumn;
PRInt32 prevColumn = column;
nscoord prevMaxWordWidth = 0;
PRInt32 lastWordLen = 0;
PRInt32 lastWordWidth = 0;
PRBool textStartsWithNBSP = PR_FALSE;
PRBool endsInWhitespace = PR_FALSE;
PRBool endsInNewline = PR_FALSE;
PRBool justDidFirstLetter = PR_FALSE;
#ifdef _WIN32
PRBool measureTextRuns = !aTextData.mComputeMaxWordWidth && !aTs.mPreformatted &&
!aTs.mSmallCaps && (0 == aTs.mWordSpacing);
#else
PRBool measureTextRuns = PR_FALSE;
#endif
TextRun textRun;
PRInt32 estimatedNumChars;
// Estimate the number of characters that will fit. Use 105% of the available
// width divided by the average character width
estimatedNumChars = (maxWidth - aTextData.mX) / aTs.mAveCharWidth;
estimatedNumChars += estimatedNumChars / 20;
aTextData.mX = 0;
aTextData.mTextStartsWithNBSP = PR_FALSE;
for (;;) {
for (;;firstThing = PR_FALSE) {
// Get next word/whitespace from the text
PRBool isWhitespace;
PRInt32 wordLen, contentLen;
PRUnichar* bp = aTx.GetNextWord(aTextData.mInWord, &wordLen, &contentLen, &isWhitespace);
if (nsnull == bp) {
// Advance the offset in case we just consumed a bunch of
// discarded characters. Otherwise, if this is the first piece
// of content for this frame we will attempt to break-before it.
aTextData.mOffset += contentLen;
break;
if (textRun.IsBuffering()) {
// Measure the remaining text
goto MeasureTextRun;
}
else {
// Advance the offset in case we just consumed a bunch of
// discarded characters. Otherwise, if this is the first piece
// of content for this frame we will attempt to break-before it.
aTextData.mOffset += contentLen;
break;
}
}
aTextData.mLastWordLen = wordLen;
lastWordLen = wordLen;
aTextData.mInWord = PR_FALSE;
// Measure the word/whitespace
@ -2934,10 +3098,10 @@ nsTextFrame::MeasureIndividualWords(nsIPresContext* aPresContext,
if ('\n' == bp[0]) {
// We hit a newline. Stop looping.
NS_WARN_IF_FALSE(aTs.mPreformatted, "newline w/o ts.mPreformatted");
aTextData.mPrevOffset = aTextData.mOffset;
prevOffset = aTextData.mOffset;
aTextData.mOffset++;
aTextData.mEndsInWhitespace = PR_TRUE;
aTextData.mEndsInNewline = PR_TRUE;
endsInWhitespace = PR_TRUE;
endsInNewline = PR_TRUE;
break;
}
if (aTextData.mSkipWhitespace) {
@ -2946,20 +3110,46 @@ nsTextFrame::MeasureIndividualWords(nsIPresContext* aPresContext,
// Only set flag when we actually do skip whitespace
mState |= TEXT_SKIP_LEADING_WS;
firstThing = PR_FALSE;
continue;
}
if ('\t' == bp[0]) {
// Expand tabs to the proper width
wordLen = 8 - (7 & aTextData.mColumn);
wordLen = 8 - (7 & column);
width = aTs.mSpaceWidth * wordLen;
}
else if (textRun.IsBuffering()) {
textRun.AddWhitespace(bp, wordLen, contentLen);
continue;
}
else {
width = (wordLen * aTs.mSpaceWidth) + aTs.mWordSpacing;// XXX simplistic
}
aTextData.mIsBreakable = PR_TRUE;
aTextData.mFirstLetterOK = PR_FALSE;
if (aTextData.mMeasureText) {
// See if there is room for the text
if ((0 != aTextData.mX) && aTextData.mWrapping && (aTextData.mX + width > maxWidth)) {
// The text will not fit.
break;
}
aTextData.mX += width;
}
prevColumn = column;
column += wordLen;
endsInWhitespace = PR_TRUE;
prevOffset = aTextData.mOffset;
aTextData.mOffset += contentLen;
} else {
// See if the first thing in the section of text is a
// non-breaking space (html nbsp entity). If it is then make
// note of that fact for the line layout logic.
if (aTextData.mWrapping && firstThing && (bp[0] == ' ')) {
textStartsWithNBSP = PR_TRUE;
}
aTextData.mSkipWhitespace = PR_FALSE;
if (aTextData.mFirstLetterOK) {
// XXX need a lookup function here; plus look ahead using the
// text-runs
@ -2971,60 +3161,277 @@ nsTextFrame::MeasureIndividualWords(nsIPresContext* aPresContext,
wordLen = 1;
contentLen = 1;
}
aTextData.mJustDidFirstLetter = PR_TRUE;
justDidFirstLetter = PR_TRUE;
}
if (aTextData.mMeasureText) {
if (aTs.mSmallCaps) {
MeasureSmallCapsText(aReflowState, aTs, bp, wordLen, &width);
}
else {
aReflowState.rendContext->GetWidth(bp, wordLen, width);
if (aTs.mLetterSpacing) {
width += aTs.mLetterSpacing * wordLen;
if (measureTextRuns && !justDidFirstLetter) {
// Add another word to the text run
textRun.AddWord(bp, wordLen, contentLen);
// See if we should measure the text
if ((textRun.mBufLength >= estimatedNumChars) ||
(textRun.mNumSegments >= (TEXT_MAX_NUM_SEGMENTS - 1))) {
goto MeasureTextRun;
}
}
aTextData.mLastWordWidth = width;
}
else {
if (aTs.mSmallCaps) {
MeasureSmallCapsText(aReflowState, aTs, bp, wordLen, &width);
}
else {
// Measure just the one word
aReflowState.rendContext->GetWidth(bp, wordLen, width);
if (aTs.mLetterSpacing) {
width += aTs.mLetterSpacing * wordLen;
}
}
lastWordWidth = width;
// See if the first thing in the section of text is a
// non-breaking space (html nbsp entity). If it is then make
// note of that fact for the line layout logic.
if (aTextData.mWrapping && firstThing && (bp[0] == ' ')) {
aTextData.mTextStartsWithNBSP = PR_TRUE;
}
aTextData.mSkipWhitespace = PR_FALSE;
}
firstThing = PR_FALSE;
// See if there is room for the text
if ((0 != aTextData.mX) && aTextData.mWrapping && (aTextData.mX + width > maxWidth)) {
// The text will not fit.
break;
}
aTextData.mX += width;
prevMaxWordWidth = aTextData.mMaxWordWidth;
if (width > aTextData.mMaxWordWidth) {
aTextData.mMaxWordWidth = width;
}
// See if there is room for the text
if (aTextData.mMeasureText) {
if ((0 != aTextData.mX) && aTextData.mWrapping && (aTextData.mX + width > maxWidth)) {
// The text will not fit.
#ifdef NOISY_REFLOW
ListTag(stdout);
printf(": won't fit (at offset=%d) x=%d width=%d maxWidth=%d\n",
offset, x, width, maxWidth);
#endif
break;
prevColumn = column;
column += wordLen;
endsInWhitespace = PR_FALSE;
prevOffset = aTextData.mOffset;
aTextData.mOffset += contentLen;
if (justDidFirstLetter) {
// Time to stop
break;
}
}
}
aTextData.mX += width;
aTextData.mPrevMaxWordWidth = aTextData.mMaxWordWidth;
if (width > aTextData.mMaxWordWidth) {
aTextData.mMaxWordWidth = width;
else {
// We didn't measure the text, but we need to update our state
prevColumn = column;
column += wordLen;
endsInWhitespace = PR_FALSE;
prevOffset = aTextData.mOffset;
aTextData.mOffset += contentLen;
if (justDidFirstLetter) {
// Time to stop
break;
}
}
}
continue;
aTextData.mPrevColumn = aTextData.mColumn;
aTextData.mColumn += wordLen;
aTextData.mEndsInWhitespace = isWhitespace;
aTextData.mPrevOffset = aTextData.mOffset;
aTextData.mOffset += contentLen;
if (!isWhitespace && aTextData.mJustDidFirstLetter) {
// Time to stop
MeasureTextRun:
#ifdef _WIN32
PRInt32 numCharsFit;
aReflowState.rendContext->GetWidth(textRun.mBuf, textRun.mBufLength,
maxWidth - aTextData.mX,
textRun.mBreaks, textRun.mNumSegments,
width, numCharsFit);
// See how much of the text fit
if ((0 != aTextData.mX) && aTextData.mWrapping && (aTextData.mX + width > maxWidth)) {
// None of the text fits
break;
}
// Find the index of the last segment that fit
PRInt32 lastSegment = textRun.mNumSegments - 1;
if (textRun.mBufLength != numCharsFit) {
for (lastSegment = 0; textRun.mBreaks[lastSegment] < numCharsFit; lastSegment++) ;
NS_ASSERTION(lastSegment < textRun.mNumSegments, "failed to find segment");
}
if (lastSegment == 0) {
// Only one segment fit
prevColumn = column;
prevOffset = aTextData.mOffset;
} else {
// The previous state is for the next to last word
prevColumn = textRun.mBreaks[lastSegment - 1];
prevOffset = textRun.mSegments[lastSegment - 1].ContentLen();
}
aTextData.mX += width;
column += numCharsFit;
aTextData.mOffset += textRun.mSegments[lastSegment].ContentLen();
endsInWhitespace = textRun.mSegments[lastSegment].IsWhitespace();
// If all the text didn't fit, then we're done
if (textRun.mBufLength != numCharsFit) {
break;
}
if (nsnull == bp) {
// No more text so we're all finished. Advance the offset in case we
// just consumed a bunch of discarded characters
aTextData.mOffset += contentLen;
break;
}
// Reset the number of text run segments
textRun.Reset();
// Estimate the number of characters we think will fit
estimatedNumChars = (maxWidth - aTextData.mX) / aTs.mAveCharWidth;
estimatedNumChars += estimatedNumChars / 20;
#endif
}
// If we didn't actually measure any text, then make sure it looks
// like we did
if (!aTextData.mMeasureText) {
aTextData.mX = mRect.width;
if (mState & TEXT_TRIMMED_WS) {
// Add back in the width of a space since it was trimmed away last time
aTextData.mX += aTs.mSpaceWidth;
}
}
// Post processing logic to deal with word-breaking that spans
// multiple frames.
if (lineLayout.InWord()) {
// We are already in a word. This means a text frame prior to this
// one had a fragment of a nbword that is joined with this
// frame. It also means that the prior frame already found this
// frame and recorded it as part of the word.
#ifdef DEBUG_WORD_WRAPPING
ListTag(stdout);
printf(": in word; skipping\n");
#endif
lineLayout.ForgetWordFrame(this);
}
if (!lineLayout.InWord()) {
// There is no currently active word. This frame may contain the
// start of one.
if (endsInWhitespace) {
// Nope, this frame doesn't start a word.
lineLayout.ForgetWordFrames();
}
else if ((aTextData.mOffset == contentLength) && (prevOffset >= 0)) {
// Force breakable to false when we aren't wrapping (this
// guarantees that the combined word will stay together)
if (!aTextData.mWrapping) {
aTextData.mIsBreakable = PR_FALSE;
}
// This frame does start a word. However, there is no point
// messing around with it if we are already out of room. We
// always have room if we are not breakable.
if (!aTextData.mIsBreakable || (aTextData.mX <= maxWidth)) {
// There is room for this word fragment. It's possible that
// this word fragment is the end of the text-run. If it's not
// then we continue with the look-ahead processing.
nsIFrame* next = lineLayout.FindNextText(this);
if (nsnull != next) {
#ifdef DEBUG_WORD_WRAPPING
nsAutoString tmp(aTx.GetWordBuffer(), lastWordLen);
ListTag(stdout);
printf(": start='");
fputs(tmp, stdout);
printf("' lastWordLen=%d baseWidth=%d prevOffset=%d offset=%d next=",
lastWordLen, lastWordWidth, prevOffset, aTextData.mOffset);
ListTag(stdout, next);
printf("\n");
#endif
PRUnichar* pWordBuf = aTx.GetWordBuffer();
PRUint32 wordBufLen = aTx.GetWordBufferLength();
// Look ahead in the text-run and compute the final word
// width, taking into account any style changes and stopping
// at the first breakable point.
if (!aTextData.mMeasureText) {
// We didn't measure any text so we don't know lastWordWidth.
// We have to compute it now
if (prevOffset == startingOffset) {
// There's only one word, so we don't have to measure after all
lastWordWidth = aTextData.mX;
}
else if (aTs.mSmallCaps) {
MeasureSmallCapsText(aReflowState, aTs, aTx.GetWordBuffer(),
lastWordLen, &lastWordWidth);
}
else {
aReflowState.rendContext->GetWidth(aTx.GetWordBuffer(),
lastWordLen, lastWordWidth);
if (aTs.mLetterSpacing) {
lastWordWidth += aTs.mLetterSpacing * lastWordLen;
}
}
}
nscoord wordWidth = ComputeTotalWordWidth(aPresContext, aLb,
lineLayout,
aReflowState, next,
lastWordWidth,
pWordBuf,
lastWordLen,
wordBufLen);
if (!aTextData.mIsBreakable || (aTextData.mX - lastWordWidth + wordWidth <= maxWidth)) {
// The fully joined word has fit. Account for the joined
// word's affect on the max-element-size here (since the
// joined word is large than it's pieces, the right effect
// will occur from the perspective of the container
// reflowing this frame)
if (wordWidth > aTextData.mMaxWordWidth) {
aTextData.mMaxWordWidth = wordWidth;
}
}
else {
#ifdef NOISY_REFLOW
ListTag(stdout);
printf(": look-ahead (didn't fit) x=%d wordWidth=%d lastWordWidth=%d\n",
aTextData.mX, wordWidth, lastWordWidth);
#endif
// The fully joined word won't fit. We need to reduce our
// size by the lastWordWidth.
aTextData.mX -= lastWordWidth;
aTextData.mMaxWordWidth = prevMaxWordWidth;
aTextData.mOffset = prevOffset;
column = prevColumn;
#ifdef DEBUG_WORD_WRAPPING
printf(" x=%d maxWordWidth=%d len=%d\n", aTextData.mX, aTextData.mMaxWordWidth,
aTextData.mOffset - startingOffset);
#endif
lineLayout.ForgetWordFrames();
}
}
}
}
}
// Inform line layout of how this piece of text ends in whitespace
// (only text objects do this). Note that if x is zero then this
// text object collapsed into nothingness which means it shouldn't
// effect the current setting of the ends-in-whitespace flag.
lineLayout.SetColumn(column);
lineLayout.SetUnderstandsWhiteSpace(PR_TRUE);
lineLayout.SetTextStartsWithNBSP(textStartsWithNBSP);
if (0 != aTextData.mX) {
lineLayout.SetEndsInWhiteSpace(endsInWhitespace);
}
if (justDidFirstLetter) {
lineLayout.SetFirstLetterFrame(this);
lineLayout.SetFirstLetterStyleOK(PR_FALSE);
mState |= TEXT_FIRST_LETTER;
}
// Return our reflow status
nsReflowStatus rs = (aTextData.mOffset == contentLength)
? NS_FRAME_COMPLETE
: NS_FRAME_NOT_COMPLETE;
if (endsInNewline) {
rs = NS_INLINE_LINE_BREAK_AFTER(rs);
}
else if ((aTextData.mOffset != contentLength) && (aTextData.mOffset == startingOffset)) {
// Break-before a long-word that doesn't fit here
rs = NS_INLINE_LINE_BREAK_BEFORE();
}
return rs;
}
NS_IMETHODIMP
@ -3124,8 +3531,7 @@ nsTextFrame::Reflow(nsIPresContext* aPresContext,
}
PRInt32 contentLength = tx.GetContentLength();
// Loop over words and whitespace in content and measure. Set inWord
// to true if we are part of a previous piece of text's word. This
// Set inWord to true if we are part of a previous piece of text's word. This
// is only valid for one pass through the measuring loop.
PRBool inWord = lineLayout.InWord() || ((nsnull != prevInFlow) && (((nsTextFrame*)prevInFlow)->mState & TEXT_FIRST_LETTER));
if (inWord) {
@ -3179,156 +3585,19 @@ nsTextFrame::Reflow(nsIPresContext* aPresContext,
}
// Local state passed to the routines that do the actual text measurement
TextReflowData textData(startingOffset, column, wrapping, skipWhitespace,
TextReflowData textData(startingOffset, wrapping, skipWhitespace,
measureText, inWord, lineLayout.GetFirstLetterStyleOK(),
lineLayout.LineIsBreakable());
lineLayout.LineIsBreakable(), nsnull != aMetrics.maxElementSize);
// Measure the inidvidual words
MeasureIndividualWords(aPresContext, aReflowState, tx, ts, textData);
// Measure the text
aStatus = MeasureText(aPresContext, aReflowState, tx, lb, ts, textData);
if (tx.HasMultibyte()) {
mState |= TEXT_HAS_MULTIBYTE;
}
// If we didn't actually measure any text, then make sure it looks
// like we did
if (!measureText) {
textData.mX = mRect.width;
if (mState & TEXT_TRIMMED_WS) {
// Add back in the width of a space since it was trimmed away last time
textData.mX += ts.mSpaceWidth;
}
}
mState &= ~TEXT_TRIMMED_WS;
// Post processing logic to deal with word-breaking that spans
// multiple frames.
if (lineLayout.InWord()) {
// We are already in a word. This means a text frame prior to this
// one had a fragment of a nbword that is joined with this
// frame. It also means that the prior frame already found this
// frame and recorded it as part of the word.
#ifdef DEBUG_WORD_WRAPPING
ListTag(stdout);
printf(": in word; skipping\n");
#endif
lineLayout.ForgetWordFrame(this);
}
if (!lineLayout.InWord()) {
// There is no currently active word. This frame may contain the
// start of one.
if (textData.mEndsInWhitespace) {
// Nope, this frame doesn't start a word.
lineLayout.ForgetWordFrames();
}
else if ((textData.mOffset == contentLength) && (textData.mPrevOffset >= 0)) {
// Force breakable to false when we aren't wrapping (this
// guarantees that the combined word will stay together)
if (!wrapping) {
textData.mIsBreakable = PR_FALSE;
}
// This frame does start a word. However, there is no point
// messing around with it if we are already out of room. We
// always have room if we are not breakable.
if (!textData.mIsBreakable || (textData.mX <= maxWidth)) {
// There is room for this word fragment. It's possible that
// this word fragment is the end of the text-run. If it's not
// then we continue with the look-ahead processing.
nsIFrame* next = lineLayout.FindNextText(this);
if (nsnull != next) {
#ifdef DEBUG_WORD_WRAPPING
nsAutoString tmp(tx.GetWordBuffer(), lastWordLen);
ListTag(stdout);
printf(": start='");
fputs(tmp, stdout);
printf("' lastWordLen=%d baseWidth=%d prevOffset=%d offset=%d next=",
lastWordLen, lastWordWidth, prevOffset, offset);
ListTag(stdout, next);
printf("\n");
#endif
PRUnichar* pWordBuf = tx.GetWordBuffer();
PRUint32 wordBufLen = tx.GetWordBufferLength();
// Look ahead in the text-run and compute the final word
// width, taking into account any style changes and stopping
// at the first breakable point.
if (!measureText) {
// We didn't measure any text so we don't know lastWordWidth.
// We have to compute it now
if (textData.mPrevOffset == startingOffset) {
// There's only one word, so we don't have to measure after all
textData.mLastWordWidth = textData.mX;
}
else if (ts.mSmallCaps) {
MeasureSmallCapsText(aReflowState, ts, tx.GetWordBuffer(),
textData.mLastWordLen, &textData.mLastWordWidth);
}
else {
aReflowState.rendContext->GetWidth(tx.GetWordBuffer(),
textData.mLastWordLen, textData.mLastWordWidth);
if (ts.mLetterSpacing) {
textData.mLastWordWidth += ts.mLetterSpacing * textData.mLastWordLen;
}
}
}
nscoord wordWidth = ComputeTotalWordWidth(aPresContext, lb,
lineLayout,
aReflowState, next,
textData.mLastWordWidth,
pWordBuf,
textData.mLastWordLen,
wordBufLen);
if (!textData.mIsBreakable || (textData.mX - textData.mLastWordWidth + wordWidth <= maxWidth)) {
// The fully joined word has fit. Account for the joined
// word's affect on the max-element-size here (since the
// joined word is large than it's pieces, the right effect
// will occur from the perspective of the container
// reflowing this frame)
if (wordWidth > textData.mMaxWordWidth) {
textData.mMaxWordWidth = wordWidth;
}
}
else {
#ifdef NOISY_REFLOW
ListTag(stdout);
printf(": look-ahead (didn't fit) x=%d wordWidth=%d lastWordWidth=%d\n",
x, wordWidth, lastWordWidth);
#endif
// The fully joined word won't fit. We need to reduce our
// size by the lastWordWidth.
textData.mX -= textData.mLastWordWidth;
textData.mMaxWordWidth = textData.mPrevMaxWordWidth;
textData.mOffset = textData.mPrevOffset;
textData.mColumn = textData.mPrevColumn;
#ifdef DEBUG_WORD_WRAPPING
printf(" x=%d maxWordWidth=%d len=%d\n", textData.mX, textData.mMaxWordWidth,
textData.mOffset - startingOffset);
#endif
lineLayout.ForgetWordFrames();
}
}
}
}
}
lineLayout.SetColumn(textData.mColumn);
// Inform line layout of how this piece of text ends in whitespace
// (only text objects do this). Note that if x is zero then this
// text object collapsed into nothingness which means it shouldn't
// effect the current setting of the ends-in-whitespace flag.
lineLayout.SetUnderstandsWhiteSpace(PR_TRUE);
lineLayout.SetTextStartsWithNBSP(textData.mTextStartsWithNBSP);
if (0 != textData.mX) {
lineLayout.SetEndsInWhiteSpace(textData.mEndsInWhitespace);
}
if (textData.mJustDidFirstLetter) {
lineLayout.SetFirstLetterFrame(this);
lineLayout.SetFirstLetterStyleOK(PR_FALSE);
mState |= TEXT_FIRST_LETTER;
}
// Setup metrics for caller; store final max-element-size information
aMetrics.width = textData.mX;
if ((0 == textData.mX) && !ts.mPreformatted) {
@ -3401,18 +3670,6 @@ nsTextFrame::Reflow(nsIPresContext* aPresContext,
Invalidate(aPresContext, mRect);
}
nsReflowStatus rs = (textData.mOffset == contentLength)
? NS_FRAME_COMPLETE
: NS_FRAME_NOT_COMPLETE;
if (textData.mEndsInNewline) {
rs = NS_INLINE_LINE_BREAK_AFTER(rs);
}
else if ((textData.mOffset != contentLength) && (textData.mOffset == startingOffset)) {
// Break-before a long-word that doesn't fit here
rs = NS_INLINE_LINE_BREAK_BEFORE();
}
aStatus = rs;
// For future resize reflows we would like to avoid measuring the text.
// We can only do this if after this reflow we're:
// - complete. If we're not complete then our desired width doesn't

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

@ -453,6 +453,7 @@ public:
nscolor mSelectionTextColor;
nscolor mSelectionBGColor;
nscoord mSpaceWidth;
nscoord mAveCharWidth;
PRBool mJustifying;
PRBool mPreformatted;
PRIntn mNumSpaces;
@ -483,6 +484,7 @@ public:
aPresContext->GetMetricsFor(*plainFont, &mNormalFont);
aRenderingContext.SetFont(mNormalFont);
aRenderingContext.GetWidth(' ', mSpaceWidth);
mNormalFont->GetAveCharWidth(mAveCharWidth);
mLastFont = mNormalFont;
// Get the small-caps font if needed
@ -540,51 +542,33 @@ public:
struct TextReflowData {
PRInt32 mX; // OUT
PRInt32 mOffset; // IN/OUT How far along we are in the content
PRInt32 mPrevOffset; // OUT
PRInt32 mColumn; // IN/OUT
PRInt32 mPrevColumn; // IN/OUT
PRInt32 mLastWordLen; // OUT
PRInt32 mLastWordWidth; // OUT
nscoord mMaxWordWidth; // OUT
nscoord mPrevMaxWordWidth; // OUT
PRPackedBool mWrapping; // IN
PRPackedBool mSkipWhitespace; // IN
PRPackedBool mMeasureText; // IN
PRPackedBool mInWord; // IN
PRPackedBool mFirstLetterOK; // IN
PRPackedBool mJustDidFirstLetter; // OUT
PRPackedBool mIsBreakable; // IN/OUT
PRPackedBool mTextStartsWithNBSP; // OUT
PRPackedBool mEndsInWhitespace; // OUT
PRPackedBool mEndsInNewline; // OUT
PRPackedBool mIsBreakable; // IN
PRPackedBool mComputeMaxWordWidth; // IN
TextReflowData(PRInt32 aStartingOffset,
PRInt32 aColumn,
PRBool aWrapping,
PRBool aSkipWhitespace,
PRBool aMeasureText,
PRBool aInWord,
PRBool aFirstLetterOK,
PRBool aIsBreakable)
PRBool aIsBreakable,
PRBool aComputeMaxWordWidth)
: mX(0),
mOffset(aStartingOffset),
mPrevOffset(-1),
mColumn(aColumn),
mPrevColumn(aColumn),
mLastWordLen(0),
mLastWordWidth(0),
mMaxWordWidth(0),
mPrevMaxWordWidth(0),
mWrapping(aWrapping),
mSkipWhitespace(aSkipWhitespace),
mMeasureText(aMeasureText),
mInWord(aInWord),
mFirstLetterOK(aFirstLetterOK),
mJustDidFirstLetter(PR_FALSE),
mIsBreakable(aIsBreakable),
mTextStartsWithNBSP(PR_FALSE),
mEndsInWhitespace(PR_FALSE),
mEndsInNewline(PR_FALSE)
mComputeMaxWordWidth(aComputeMaxWordWidth)
{}
};
@ -625,12 +609,13 @@ public:
PRInt32 aWordLength,
nscoord* aWidthResult);
void MeasureIndividualWords(nsIPresContext* aPresContext,
const nsHTMLReflowState& aReflowState,
nsTextTransformer& aTx,
TextStyle& aTs,
TextReflowData& aTextData);
nsReflowStatus MeasureText(nsIPresContext* aPresContext,
const nsHTMLReflowState& aReflowState,
nsTextTransformer& aTx,
nsILineBreaker* aLb,
TextStyle& aTs,
TextReflowData& aTextData);
void GetWidth(nsIRenderingContext& aRenderingContext,
TextStyle& aStyle,
PRUnichar* aBuffer, PRInt32 aLength,
@ -2901,31 +2886,210 @@ nsTextFrame::GetOffsets(PRInt32 &start, PRInt32 &end) const
return NS_OK;
}
void
nsTextFrame::MeasureIndividualWords(nsIPresContext* aPresContext,
const nsHTMLReflowState& aReflowState,
nsTextTransformer& aTx,
TextStyle& aTs,
TextReflowData& aTextData)
#define TEXT_MAX_NUM_SEGMENTS 65
struct SegmentData {
PRUint32 mIsWhitespace : 1;
PRUint32 mContentLen : 31; // content length
PRBool IsWhitespace() {return PRBool(mIsWhitespace);}
// Get the content length. This is a running total of all
// the previous segments as well
PRInt32 ContentLen() {return PRInt32(mContentLen);}
};
#define IS_ODD_NUMBER(n) \
(0 != ((n) & 0x1))
#define IS_EVEN_NUMBER(n) \
(0 == ((n) & 0x1))
#define IS_32BIT_ALIGNED_PTR(p) \
(0 == (long(p) & 0x3))
struct TextRun {
PRUnichar mBufSpace[256];
PRUnichar* mBuf;
PRInt32 mBufSize, mBufLength;
// Words and whitespace each count as a segment
PRInt32 mNumSegments;
// Possible break points specified as offsets into the buffer
PRInt32 mBreaks[TEXT_MAX_NUM_SEGMENTS];
// Per segment data
SegmentData mSegments[TEXT_MAX_NUM_SEGMENTS];
TextRun()
{
mBuf = mBufSpace;
mBufSize = 256;
mBufLength = mNumSegments = 0;
}
~TextRun()
{
if (mBuf != mBufSpace) {
delete []mBuf;
}
}
void Reset()
{
mBufLength = 0;
mNumSegments = 0;
}
// Returns PR_TRUE if we're currently buffering text
PRBool IsBuffering()
{
return mNumSegments > 0;
}
void GrowBuffer(PRInt32 aMinBufSize)
{
// Allocate a new buffer
do {
mBufSize += 100;
} while (mBufSize < aMinBufSize);
PRUnichar* newBufSpace = new PRUnichar[mBufSize];
memcpy(newBufSpace, mBuf, mBufLength * sizeof(PRUnichar));
if (mBuf != mBufSpace) {
delete []mBuf;
}
mBuf = newBufSpace;
}
void AddWhitespace(PRUnichar* aText,
PRInt32 aNumChars,
PRInt32 aContentLen)
{
NS_PRECONDITION(mNumSegments < TEXT_MAX_NUM_SEGMENTS, "segment overflow");
NS_PRECONDITION(IS_32BIT_ALIGNED_PTR(aText), "unexpected alignment of text pointer");
// See if we have room in the buffer
PRInt32 newBufLength = mBufLength + aNumChars;
if (newBufLength > mBufSize) {
GrowBuffer(newBufLength);
}
if (1 == aNumChars) {
mBuf[mBufLength] = *aText;
} else {
::memcpy(&mBuf[mBufLength], aText, aNumChars * sizeof(PRUnichar*));
}
mBufLength = newBufLength;
mBreaks[mNumSegments] = mBufLength;
mSegments[mNumSegments].mIsWhitespace = PR_TRUE;
mSegments[mNumSegments].mContentLen = PRUint32(aContentLen);
if (mNumSegments > 0) {
mSegments[mNumSegments].mContentLen += mSegments[mNumSegments - 1].ContentLen();
}
mNumSegments++;
}
void AddWord(PRUnichar* aText,
PRInt32 aNumChars,
PRInt32 aContentLen)
{
NS_PRECONDITION(mNumSegments < TEXT_MAX_NUM_SEGMENTS, "segment overflow");
NS_PRECONDITION(IS_32BIT_ALIGNED_PTR(aText), "unexpected alignment of text pointer");
// See if we have room in the buffer
PRInt32 newBufLength = mBufLength + aNumChars;
if (newBufLength > mBufSize) {
GrowBuffer(newBufLength);
}
if (IS_EVEN_NUMBER(mBufLength)) {
// We have a small amount of text (a single word) and we're at a point
// in our buffer that is 32-bit aligned so copy 32-bit chunks at a time
int* dest = (int*)&mBuf[mBufLength];
int* src = (int*)aText;
for (int i = aNumChars / 2; i > 0; i--) {
*dest++ = *src++;
}
if (IS_ODD_NUMBER(aNumChars)) {
// Copy the one remaining character
*((PRUnichar*)dest) = *((PRUnichar*)src);
}
} else {
::memcpy(&mBuf[mBufLength], aText, aNumChars * sizeof(PRUnichar*));
}
mBufLength = newBufLength;
mBreaks[mNumSegments] = mBufLength;
mSegments[mNumSegments].mIsWhitespace = PR_FALSE;
mSegments[mNumSegments].mContentLen = PRUint32(aContentLen);
if (mNumSegments > 0) {
mSegments[mNumSegments].mContentLen += mSegments[mNumSegments - 1].ContentLen();
}
mNumSegments++;
}
};
nsReflowStatus
nsTextFrame::MeasureText(nsIPresContext* aPresContext,
const nsHTMLReflowState& aReflowState,
nsTextTransformer& aTx,
nsILineBreaker* aLb,
TextStyle& aTs,
TextReflowData& aTextData)
{
PRBool firstThing = PR_TRUE;
nscoord maxWidth = aReflowState.availableWidth;
nsLineLayout& lineLayout = *aReflowState.mLineLayout;
PRInt32 contentLength = aTx.GetContentLength();
PRInt32 startingOffset = aTextData.mOffset;
PRInt32 prevOffset = -1;
PRInt32 column = mColumn;
PRInt32 prevColumn = column;
nscoord prevMaxWordWidth = 0;
PRInt32 lastWordLen = 0;
PRInt32 lastWordWidth = 0;
PRBool textStartsWithNBSP = PR_FALSE;
PRBool endsInWhitespace = PR_FALSE;
PRBool endsInNewline = PR_FALSE;
PRBool justDidFirstLetter = PR_FALSE;
#ifdef _WIN32
PRBool measureTextRuns = !aTextData.mComputeMaxWordWidth && !aTs.mPreformatted &&
!aTs.mSmallCaps && (0 == aTs.mWordSpacing);
#else
PRBool measureTextRuns = PR_FALSE;
#endif
TextRun textRun;
PRInt32 estimatedNumChars;
// Estimate the number of characters that will fit. Use 105% of the available
// width divided by the average character width
estimatedNumChars = (maxWidth - aTextData.mX) / aTs.mAveCharWidth;
estimatedNumChars += estimatedNumChars / 20;
aTextData.mX = 0;
aTextData.mTextStartsWithNBSP = PR_FALSE;
for (;;) {
for (;;firstThing = PR_FALSE) {
// Get next word/whitespace from the text
PRBool isWhitespace;
PRInt32 wordLen, contentLen;
PRUnichar* bp = aTx.GetNextWord(aTextData.mInWord, &wordLen, &contentLen, &isWhitespace);
if (nsnull == bp) {
// Advance the offset in case we just consumed a bunch of
// discarded characters. Otherwise, if this is the first piece
// of content for this frame we will attempt to break-before it.
aTextData.mOffset += contentLen;
break;
if (textRun.IsBuffering()) {
// Measure the remaining text
goto MeasureTextRun;
}
else {
// Advance the offset in case we just consumed a bunch of
// discarded characters. Otherwise, if this is the first piece
// of content for this frame we will attempt to break-before it.
aTextData.mOffset += contentLen;
break;
}
}
aTextData.mLastWordLen = wordLen;
lastWordLen = wordLen;
aTextData.mInWord = PR_FALSE;
// Measure the word/whitespace
@ -2934,10 +3098,10 @@ nsTextFrame::MeasureIndividualWords(nsIPresContext* aPresContext,
if ('\n' == bp[0]) {
// We hit a newline. Stop looping.
NS_WARN_IF_FALSE(aTs.mPreformatted, "newline w/o ts.mPreformatted");
aTextData.mPrevOffset = aTextData.mOffset;
prevOffset = aTextData.mOffset;
aTextData.mOffset++;
aTextData.mEndsInWhitespace = PR_TRUE;
aTextData.mEndsInNewline = PR_TRUE;
endsInWhitespace = PR_TRUE;
endsInNewline = PR_TRUE;
break;
}
if (aTextData.mSkipWhitespace) {
@ -2946,20 +3110,46 @@ nsTextFrame::MeasureIndividualWords(nsIPresContext* aPresContext,
// Only set flag when we actually do skip whitespace
mState |= TEXT_SKIP_LEADING_WS;
firstThing = PR_FALSE;
continue;
}
if ('\t' == bp[0]) {
// Expand tabs to the proper width
wordLen = 8 - (7 & aTextData.mColumn);
wordLen = 8 - (7 & column);
width = aTs.mSpaceWidth * wordLen;
}
else if (textRun.IsBuffering()) {
textRun.AddWhitespace(bp, wordLen, contentLen);
continue;
}
else {
width = (wordLen * aTs.mSpaceWidth) + aTs.mWordSpacing;// XXX simplistic
}
aTextData.mIsBreakable = PR_TRUE;
aTextData.mFirstLetterOK = PR_FALSE;
if (aTextData.mMeasureText) {
// See if there is room for the text
if ((0 != aTextData.mX) && aTextData.mWrapping && (aTextData.mX + width > maxWidth)) {
// The text will not fit.
break;
}
aTextData.mX += width;
}
prevColumn = column;
column += wordLen;
endsInWhitespace = PR_TRUE;
prevOffset = aTextData.mOffset;
aTextData.mOffset += contentLen;
} else {
// See if the first thing in the section of text is a
// non-breaking space (html nbsp entity). If it is then make
// note of that fact for the line layout logic.
if (aTextData.mWrapping && firstThing && (bp[0] == ' ')) {
textStartsWithNBSP = PR_TRUE;
}
aTextData.mSkipWhitespace = PR_FALSE;
if (aTextData.mFirstLetterOK) {
// XXX need a lookup function here; plus look ahead using the
// text-runs
@ -2971,60 +3161,277 @@ nsTextFrame::MeasureIndividualWords(nsIPresContext* aPresContext,
wordLen = 1;
contentLen = 1;
}
aTextData.mJustDidFirstLetter = PR_TRUE;
justDidFirstLetter = PR_TRUE;
}
if (aTextData.mMeasureText) {
if (aTs.mSmallCaps) {
MeasureSmallCapsText(aReflowState, aTs, bp, wordLen, &width);
}
else {
aReflowState.rendContext->GetWidth(bp, wordLen, width);
if (aTs.mLetterSpacing) {
width += aTs.mLetterSpacing * wordLen;
if (measureTextRuns && !justDidFirstLetter) {
// Add another word to the text run
textRun.AddWord(bp, wordLen, contentLen);
// See if we should measure the text
if ((textRun.mBufLength >= estimatedNumChars) ||
(textRun.mNumSegments >= (TEXT_MAX_NUM_SEGMENTS - 1))) {
goto MeasureTextRun;
}
}
aTextData.mLastWordWidth = width;
}
else {
if (aTs.mSmallCaps) {
MeasureSmallCapsText(aReflowState, aTs, bp, wordLen, &width);
}
else {
// Measure just the one word
aReflowState.rendContext->GetWidth(bp, wordLen, width);
if (aTs.mLetterSpacing) {
width += aTs.mLetterSpacing * wordLen;
}
}
lastWordWidth = width;
// See if the first thing in the section of text is a
// non-breaking space (html nbsp entity). If it is then make
// note of that fact for the line layout logic.
if (aTextData.mWrapping && firstThing && (bp[0] == ' ')) {
aTextData.mTextStartsWithNBSP = PR_TRUE;
}
aTextData.mSkipWhitespace = PR_FALSE;
}
firstThing = PR_FALSE;
// See if there is room for the text
if ((0 != aTextData.mX) && aTextData.mWrapping && (aTextData.mX + width > maxWidth)) {
// The text will not fit.
break;
}
aTextData.mX += width;
prevMaxWordWidth = aTextData.mMaxWordWidth;
if (width > aTextData.mMaxWordWidth) {
aTextData.mMaxWordWidth = width;
}
// See if there is room for the text
if (aTextData.mMeasureText) {
if ((0 != aTextData.mX) && aTextData.mWrapping && (aTextData.mX + width > maxWidth)) {
// The text will not fit.
#ifdef NOISY_REFLOW
ListTag(stdout);
printf(": won't fit (at offset=%d) x=%d width=%d maxWidth=%d\n",
offset, x, width, maxWidth);
#endif
break;
prevColumn = column;
column += wordLen;
endsInWhitespace = PR_FALSE;
prevOffset = aTextData.mOffset;
aTextData.mOffset += contentLen;
if (justDidFirstLetter) {
// Time to stop
break;
}
}
}
aTextData.mX += width;
aTextData.mPrevMaxWordWidth = aTextData.mMaxWordWidth;
if (width > aTextData.mMaxWordWidth) {
aTextData.mMaxWordWidth = width;
else {
// We didn't measure the text, but we need to update our state
prevColumn = column;
column += wordLen;
endsInWhitespace = PR_FALSE;
prevOffset = aTextData.mOffset;
aTextData.mOffset += contentLen;
if (justDidFirstLetter) {
// Time to stop
break;
}
}
}
continue;
aTextData.mPrevColumn = aTextData.mColumn;
aTextData.mColumn += wordLen;
aTextData.mEndsInWhitespace = isWhitespace;
aTextData.mPrevOffset = aTextData.mOffset;
aTextData.mOffset += contentLen;
if (!isWhitespace && aTextData.mJustDidFirstLetter) {
// Time to stop
MeasureTextRun:
#ifdef _WIN32
PRInt32 numCharsFit;
aReflowState.rendContext->GetWidth(textRun.mBuf, textRun.mBufLength,
maxWidth - aTextData.mX,
textRun.mBreaks, textRun.mNumSegments,
width, numCharsFit);
// See how much of the text fit
if ((0 != aTextData.mX) && aTextData.mWrapping && (aTextData.mX + width > maxWidth)) {
// None of the text fits
break;
}
// Find the index of the last segment that fit
PRInt32 lastSegment = textRun.mNumSegments - 1;
if (textRun.mBufLength != numCharsFit) {
for (lastSegment = 0; textRun.mBreaks[lastSegment] < numCharsFit; lastSegment++) ;
NS_ASSERTION(lastSegment < textRun.mNumSegments, "failed to find segment");
}
if (lastSegment == 0) {
// Only one segment fit
prevColumn = column;
prevOffset = aTextData.mOffset;
} else {
// The previous state is for the next to last word
prevColumn = textRun.mBreaks[lastSegment - 1];
prevOffset = textRun.mSegments[lastSegment - 1].ContentLen();
}
aTextData.mX += width;
column += numCharsFit;
aTextData.mOffset += textRun.mSegments[lastSegment].ContentLen();
endsInWhitespace = textRun.mSegments[lastSegment].IsWhitespace();
// If all the text didn't fit, then we're done
if (textRun.mBufLength != numCharsFit) {
break;
}
if (nsnull == bp) {
// No more text so we're all finished. Advance the offset in case we
// just consumed a bunch of discarded characters
aTextData.mOffset += contentLen;
break;
}
// Reset the number of text run segments
textRun.Reset();
// Estimate the number of characters we think will fit
estimatedNumChars = (maxWidth - aTextData.mX) / aTs.mAveCharWidth;
estimatedNumChars += estimatedNumChars / 20;
#endif
}
// If we didn't actually measure any text, then make sure it looks
// like we did
if (!aTextData.mMeasureText) {
aTextData.mX = mRect.width;
if (mState & TEXT_TRIMMED_WS) {
// Add back in the width of a space since it was trimmed away last time
aTextData.mX += aTs.mSpaceWidth;
}
}
// Post processing logic to deal with word-breaking that spans
// multiple frames.
if (lineLayout.InWord()) {
// We are already in a word. This means a text frame prior to this
// one had a fragment of a nbword that is joined with this
// frame. It also means that the prior frame already found this
// frame and recorded it as part of the word.
#ifdef DEBUG_WORD_WRAPPING
ListTag(stdout);
printf(": in word; skipping\n");
#endif
lineLayout.ForgetWordFrame(this);
}
if (!lineLayout.InWord()) {
// There is no currently active word. This frame may contain the
// start of one.
if (endsInWhitespace) {
// Nope, this frame doesn't start a word.
lineLayout.ForgetWordFrames();
}
else if ((aTextData.mOffset == contentLength) && (prevOffset >= 0)) {
// Force breakable to false when we aren't wrapping (this
// guarantees that the combined word will stay together)
if (!aTextData.mWrapping) {
aTextData.mIsBreakable = PR_FALSE;
}
// This frame does start a word. However, there is no point
// messing around with it if we are already out of room. We
// always have room if we are not breakable.
if (!aTextData.mIsBreakable || (aTextData.mX <= maxWidth)) {
// There is room for this word fragment. It's possible that
// this word fragment is the end of the text-run. If it's not
// then we continue with the look-ahead processing.
nsIFrame* next = lineLayout.FindNextText(this);
if (nsnull != next) {
#ifdef DEBUG_WORD_WRAPPING
nsAutoString tmp(aTx.GetWordBuffer(), lastWordLen);
ListTag(stdout);
printf(": start='");
fputs(tmp, stdout);
printf("' lastWordLen=%d baseWidth=%d prevOffset=%d offset=%d next=",
lastWordLen, lastWordWidth, prevOffset, aTextData.mOffset);
ListTag(stdout, next);
printf("\n");
#endif
PRUnichar* pWordBuf = aTx.GetWordBuffer();
PRUint32 wordBufLen = aTx.GetWordBufferLength();
// Look ahead in the text-run and compute the final word
// width, taking into account any style changes and stopping
// at the first breakable point.
if (!aTextData.mMeasureText) {
// We didn't measure any text so we don't know lastWordWidth.
// We have to compute it now
if (prevOffset == startingOffset) {
// There's only one word, so we don't have to measure after all
lastWordWidth = aTextData.mX;
}
else if (aTs.mSmallCaps) {
MeasureSmallCapsText(aReflowState, aTs, aTx.GetWordBuffer(),
lastWordLen, &lastWordWidth);
}
else {
aReflowState.rendContext->GetWidth(aTx.GetWordBuffer(),
lastWordLen, lastWordWidth);
if (aTs.mLetterSpacing) {
lastWordWidth += aTs.mLetterSpacing * lastWordLen;
}
}
}
nscoord wordWidth = ComputeTotalWordWidth(aPresContext, aLb,
lineLayout,
aReflowState, next,
lastWordWidth,
pWordBuf,
lastWordLen,
wordBufLen);
if (!aTextData.mIsBreakable || (aTextData.mX - lastWordWidth + wordWidth <= maxWidth)) {
// The fully joined word has fit. Account for the joined
// word's affect on the max-element-size here (since the
// joined word is large than it's pieces, the right effect
// will occur from the perspective of the container
// reflowing this frame)
if (wordWidth > aTextData.mMaxWordWidth) {
aTextData.mMaxWordWidth = wordWidth;
}
}
else {
#ifdef NOISY_REFLOW
ListTag(stdout);
printf(": look-ahead (didn't fit) x=%d wordWidth=%d lastWordWidth=%d\n",
aTextData.mX, wordWidth, lastWordWidth);
#endif
// The fully joined word won't fit. We need to reduce our
// size by the lastWordWidth.
aTextData.mX -= lastWordWidth;
aTextData.mMaxWordWidth = prevMaxWordWidth;
aTextData.mOffset = prevOffset;
column = prevColumn;
#ifdef DEBUG_WORD_WRAPPING
printf(" x=%d maxWordWidth=%d len=%d\n", aTextData.mX, aTextData.mMaxWordWidth,
aTextData.mOffset - startingOffset);
#endif
lineLayout.ForgetWordFrames();
}
}
}
}
}
// Inform line layout of how this piece of text ends in whitespace
// (only text objects do this). Note that if x is zero then this
// text object collapsed into nothingness which means it shouldn't
// effect the current setting of the ends-in-whitespace flag.
lineLayout.SetColumn(column);
lineLayout.SetUnderstandsWhiteSpace(PR_TRUE);
lineLayout.SetTextStartsWithNBSP(textStartsWithNBSP);
if (0 != aTextData.mX) {
lineLayout.SetEndsInWhiteSpace(endsInWhitespace);
}
if (justDidFirstLetter) {
lineLayout.SetFirstLetterFrame(this);
lineLayout.SetFirstLetterStyleOK(PR_FALSE);
mState |= TEXT_FIRST_LETTER;
}
// Return our reflow status
nsReflowStatus rs = (aTextData.mOffset == contentLength)
? NS_FRAME_COMPLETE
: NS_FRAME_NOT_COMPLETE;
if (endsInNewline) {
rs = NS_INLINE_LINE_BREAK_AFTER(rs);
}
else if ((aTextData.mOffset != contentLength) && (aTextData.mOffset == startingOffset)) {
// Break-before a long-word that doesn't fit here
rs = NS_INLINE_LINE_BREAK_BEFORE();
}
return rs;
}
NS_IMETHODIMP
@ -3124,8 +3531,7 @@ nsTextFrame::Reflow(nsIPresContext* aPresContext,
}
PRInt32 contentLength = tx.GetContentLength();
// Loop over words and whitespace in content and measure. Set inWord
// to true if we are part of a previous piece of text's word. This
// Set inWord to true if we are part of a previous piece of text's word. This
// is only valid for one pass through the measuring loop.
PRBool inWord = lineLayout.InWord() || ((nsnull != prevInFlow) && (((nsTextFrame*)prevInFlow)->mState & TEXT_FIRST_LETTER));
if (inWord) {
@ -3179,156 +3585,19 @@ nsTextFrame::Reflow(nsIPresContext* aPresContext,
}
// Local state passed to the routines that do the actual text measurement
TextReflowData textData(startingOffset, column, wrapping, skipWhitespace,
TextReflowData textData(startingOffset, wrapping, skipWhitespace,
measureText, inWord, lineLayout.GetFirstLetterStyleOK(),
lineLayout.LineIsBreakable());
lineLayout.LineIsBreakable(), nsnull != aMetrics.maxElementSize);
// Measure the inidvidual words
MeasureIndividualWords(aPresContext, aReflowState, tx, ts, textData);
// Measure the text
aStatus = MeasureText(aPresContext, aReflowState, tx, lb, ts, textData);
if (tx.HasMultibyte()) {
mState |= TEXT_HAS_MULTIBYTE;
}
// If we didn't actually measure any text, then make sure it looks
// like we did
if (!measureText) {
textData.mX = mRect.width;
if (mState & TEXT_TRIMMED_WS) {
// Add back in the width of a space since it was trimmed away last time
textData.mX += ts.mSpaceWidth;
}
}
mState &= ~TEXT_TRIMMED_WS;
// Post processing logic to deal with word-breaking that spans
// multiple frames.
if (lineLayout.InWord()) {
// We are already in a word. This means a text frame prior to this
// one had a fragment of a nbword that is joined with this
// frame. It also means that the prior frame already found this
// frame and recorded it as part of the word.
#ifdef DEBUG_WORD_WRAPPING
ListTag(stdout);
printf(": in word; skipping\n");
#endif
lineLayout.ForgetWordFrame(this);
}
if (!lineLayout.InWord()) {
// There is no currently active word. This frame may contain the
// start of one.
if (textData.mEndsInWhitespace) {
// Nope, this frame doesn't start a word.
lineLayout.ForgetWordFrames();
}
else if ((textData.mOffset == contentLength) && (textData.mPrevOffset >= 0)) {
// Force breakable to false when we aren't wrapping (this
// guarantees that the combined word will stay together)
if (!wrapping) {
textData.mIsBreakable = PR_FALSE;
}
// This frame does start a word. However, there is no point
// messing around with it if we are already out of room. We
// always have room if we are not breakable.
if (!textData.mIsBreakable || (textData.mX <= maxWidth)) {
// There is room for this word fragment. It's possible that
// this word fragment is the end of the text-run. If it's not
// then we continue with the look-ahead processing.
nsIFrame* next = lineLayout.FindNextText(this);
if (nsnull != next) {
#ifdef DEBUG_WORD_WRAPPING
nsAutoString tmp(tx.GetWordBuffer(), lastWordLen);
ListTag(stdout);
printf(": start='");
fputs(tmp, stdout);
printf("' lastWordLen=%d baseWidth=%d prevOffset=%d offset=%d next=",
lastWordLen, lastWordWidth, prevOffset, offset);
ListTag(stdout, next);
printf("\n");
#endif
PRUnichar* pWordBuf = tx.GetWordBuffer();
PRUint32 wordBufLen = tx.GetWordBufferLength();
// Look ahead in the text-run and compute the final word
// width, taking into account any style changes and stopping
// at the first breakable point.
if (!measureText) {
// We didn't measure any text so we don't know lastWordWidth.
// We have to compute it now
if (textData.mPrevOffset == startingOffset) {
// There's only one word, so we don't have to measure after all
textData.mLastWordWidth = textData.mX;
}
else if (ts.mSmallCaps) {
MeasureSmallCapsText(aReflowState, ts, tx.GetWordBuffer(),
textData.mLastWordLen, &textData.mLastWordWidth);
}
else {
aReflowState.rendContext->GetWidth(tx.GetWordBuffer(),
textData.mLastWordLen, textData.mLastWordWidth);
if (ts.mLetterSpacing) {
textData.mLastWordWidth += ts.mLetterSpacing * textData.mLastWordLen;
}
}
}
nscoord wordWidth = ComputeTotalWordWidth(aPresContext, lb,
lineLayout,
aReflowState, next,
textData.mLastWordWidth,
pWordBuf,
textData.mLastWordLen,
wordBufLen);
if (!textData.mIsBreakable || (textData.mX - textData.mLastWordWidth + wordWidth <= maxWidth)) {
// The fully joined word has fit. Account for the joined
// word's affect on the max-element-size here (since the
// joined word is large than it's pieces, the right effect
// will occur from the perspective of the container
// reflowing this frame)
if (wordWidth > textData.mMaxWordWidth) {
textData.mMaxWordWidth = wordWidth;
}
}
else {
#ifdef NOISY_REFLOW
ListTag(stdout);
printf(": look-ahead (didn't fit) x=%d wordWidth=%d lastWordWidth=%d\n",
x, wordWidth, lastWordWidth);
#endif
// The fully joined word won't fit. We need to reduce our
// size by the lastWordWidth.
textData.mX -= textData.mLastWordWidth;
textData.mMaxWordWidth = textData.mPrevMaxWordWidth;
textData.mOffset = textData.mPrevOffset;
textData.mColumn = textData.mPrevColumn;
#ifdef DEBUG_WORD_WRAPPING
printf(" x=%d maxWordWidth=%d len=%d\n", textData.mX, textData.mMaxWordWidth,
textData.mOffset - startingOffset);
#endif
lineLayout.ForgetWordFrames();
}
}
}
}
}
lineLayout.SetColumn(textData.mColumn);
// Inform line layout of how this piece of text ends in whitespace
// (only text objects do this). Note that if x is zero then this
// text object collapsed into nothingness which means it shouldn't
// effect the current setting of the ends-in-whitespace flag.
lineLayout.SetUnderstandsWhiteSpace(PR_TRUE);
lineLayout.SetTextStartsWithNBSP(textData.mTextStartsWithNBSP);
if (0 != textData.mX) {
lineLayout.SetEndsInWhiteSpace(textData.mEndsInWhitespace);
}
if (textData.mJustDidFirstLetter) {
lineLayout.SetFirstLetterFrame(this);
lineLayout.SetFirstLetterStyleOK(PR_FALSE);
mState |= TEXT_FIRST_LETTER;
}
// Setup metrics for caller; store final max-element-size information
aMetrics.width = textData.mX;
if ((0 == textData.mX) && !ts.mPreformatted) {
@ -3401,18 +3670,6 @@ nsTextFrame::Reflow(nsIPresContext* aPresContext,
Invalidate(aPresContext, mRect);
}
nsReflowStatus rs = (textData.mOffset == contentLength)
? NS_FRAME_COMPLETE
: NS_FRAME_NOT_COMPLETE;
if (textData.mEndsInNewline) {
rs = NS_INLINE_LINE_BREAK_AFTER(rs);
}
else if ((textData.mOffset != contentLength) && (textData.mOffset == startingOffset)) {
// Break-before a long-word that doesn't fit here
rs = NS_INLINE_LINE_BREAK_BEFORE();
}
aStatus = rs;
// For future resize reflows we would like to avoid measuring the text.
// We can only do this if after this reflow we're:
// - complete. If we're not complete then our desired width doesn't