gecko-dev/layout/generic/nsTextFrame.cpp

2267 строки
72 KiB
C++

/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
*
* The contents of this file are subject to the Netscape Public License
* Version 1.0 (the "NPL"); you may not use this file except in
* compliance with the NPL. You may obtain a copy of the NPL at
* http://www.mozilla.org/NPL/
*
* Software distributed under the NPL is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the NPL
* for the specific language governing rights and limitations under the
* NPL.
*
* The Initial Developer of this code under the NPL is Netscape
* Communications Corporation. Portions created by Netscape are
* Copyright (C) 1998 Netscape Communications Corporation. All Rights
* Reserved.
*/
#include "nsHTMLParts.h"
#include "nsCRT.h"
#include "nsSplittableFrame.h"
#include "nsLineLayout.h"
#include "nsString.h"
#include "nsIPresContext.h"
#include "nsStyleConsts.h"
#include "nsIStyleContext.h"
#include "nsCoord.h"
#include "nsIFontMetrics.h"
#include "nsIRenderingContext.h"
#include "nsHTMLIIDs.h"
#include "nsIPresShell.h"
#include "nsIView.h"
#include "nsIViewManager.h"
#include "nsITimerCallback.h"
#include "nsITimer.h"
#include "prtime.h"
#include "nsVoidArray.h"
#include "prprf.h"
#include "nsIDOMText.h"
#include "nsIDocument.h"
#include "nsIDeviceContext.h"
#include "nsXIFConverter.h"
#include "nsISelection.h"
#include "nsSelectionRange.h"
#include "nsHTMLAtoms.h"
#include "nsITextContent.h"
#include "nsTextReflow.h"/* XXX rename to nsTextRun */
#include "nsTextFragment.h"
#include "nsTextTransformer.h"
static NS_DEFINE_IID(kIDOMTextIID, NS_IDOMTEXT_IID);
#ifdef NS_DEBUG
#undef NOISY
#undef NOISY_BLINK
#undef DEBUG_WORD_WRAPPING
#else
#undef NOISY
#undef NOISY_BLINK
#undef DEBUG_WORD_WRAPPING
#endif
#define WORD_BUF_SIZE 100
#define TEXT_BUF_SIZE 1000
// XXX these are a copy of nsTextTransformer's version and they aren't I18N'd!
#define XP_IS_LOWERCASE(_ch) \
(((_ch) >= 'a') && ((_ch) <= 'z'))
#define XP_TO_UPPER(_ch) ((_ch) & ~32)
#define XP_IS_SPACE(_ch) \
(((_ch) == ' ') || ((_ch) == '\t') || ((_ch) == '\n'))
static NS_DEFINE_IID(kITextContentIID, NS_ITEXT_CONTENT_IID);
class TextFrame;
class BlinkTimer : public nsITimerCallback {
public:
BlinkTimer();
~BlinkTimer();
NS_DECL_ISUPPORTS
void AddFrame(TextFrame* aFrame);
PRBool RemoveFrame(TextFrame* aFrame);
PRInt32 FrameCount();
void Start();
void Stop();
virtual void Notify(nsITimer *timer);
nsITimer* mTimer;
nsVoidArray mFrames;
};
class TextFrame : public nsSplittableFrame {
public:
TextFrame(nsIContent* aContent, nsIFrame* aParentFrame);
// nsIFrame
NS_IMETHOD CreateContinuingFrame(nsIPresContext& aPresContext,
nsIFrame* aParent,
nsIStyleContext* aStyleContext,
nsIFrame*& aContinuingFrame);
NS_IMETHOD Paint(nsIPresContext& aPresContext,
nsIRenderingContext& aRenderingContext,
const nsRect& aDirtyRect);
NS_IMETHOD GetCursorAndContentAt(nsIPresContext& aPresContext,
const nsPoint& aPoint,
nsIFrame** aFrame,
nsIContent** aContent,
PRInt32& aCursor);
NS_IMETHOD ContentChanged(nsIPresContext* aPresContext,
nsIContent* aChild,
nsISupports* aSubContent);
NS_IMETHOD List(FILE* out, PRInt32 aIndent, nsIListFilter *aFilter) const;
NS_IMETHOD ListTag(FILE* out) const;
NS_IMETHOD GetPosition(nsIPresContext& aCX,
nsIRenderingContext * aRendContext,
nsGUIEvent* aEvent,
nsIFrame * aNewFrame,
PRUint32& aAcutalContentOffset,
PRInt32& aOffset);
// nsIHTMLReflow
NS_IMETHOD FindTextRuns(nsLineLayout& aLineLayout);
NS_IMETHOD Reflow(nsIPresContext& aPresContext,
nsHTMLReflowMetrics& aMetrics,
const nsHTMLReflowState& aReflowState,
nsReflowStatus& aStatus);
NS_IMETHOD AdjustFrameSize(nscoord aExtraSpace, nscoord& aUsedSpace);
NS_IMETHOD TrimTrailingWhiteSpace(nsIPresContext& aPresContext,
nsIRenderingContext& aRC,
nscoord& aDeltaWidth);
// TextFrame methods
struct SelectionInfo {
PRInt32 mStartOffset;
PRInt32 mEndOffset;
PRBool mEmptySelection;
};
void ComputeSelectionInfo(nsIRenderingContext& aRenderingContext,
nsIDocument* aDocument,
PRInt32* aIndicies, PRInt32 aTextLength,
SelectionInfo& aResult);
struct TextStyle {
const nsStyleFont* mFont;
const nsStyleText* mText;
const nsStyleColor* mColor;
nsIFontMetrics* mNormalFont;
nsIFontMetrics* mSmallFont;
nsIFontMetrics* mLastFont;
PRBool mSmallCaps;
nscoord mWordSpacing;
nscoord mLetterSpacing;
nscolor mSelectionTextColor;
nscolor mSelectionBGColor;
nscoord mSpaceWidth;
PRBool mJustifying;
PRIntn mNumSpaces;
nscoord mExtraSpacePerSpace;
nscoord mRemainingExtraSpace;
TextStyle(nsIPresContext& aPresContext,
nsIRenderingContext& aRenderingContext,
nsIStyleContext* sc)
{
// Get style data
mColor = (const nsStyleColor*) sc->GetStyleData(eStyleStruct_Color);
mFont = (const nsStyleFont*) sc->GetStyleData(eStyleStruct_Font);
mText = (const nsStyleText*) sc->GetStyleData(eStyleStruct_Text);
aRenderingContext.SetColor(mColor->mColor);
// Get the normal font
nsFont plainFont(mFont->mFont);
plainFont.decorations = NS_FONT_DECORATION_NONE;
mNormalFont = aPresContext.GetMetricsFor(plainFont);
aRenderingContext.SetFont(mNormalFont);
aRenderingContext.GetWidth(' ', mSpaceWidth);
mLastFont = mNormalFont;
// Get the small-caps font if needed
mSmallCaps = NS_STYLE_FONT_VARIANT_SMALL_CAPS == plainFont.variant;
if (mSmallCaps) {
plainFont.size = nscoord(0.7 * plainFont.size);
mSmallFont = aPresContext.GetMetricsFor(plainFont);
}
else {
mSmallFont = nsnull;
}
// XXX Get these from style
mSelectionBGColor = NS_RGB(0, 0, 0);
mSelectionTextColor = NS_RGB(255, 255, 255);
// Get the word and letter spacing
mWordSpacing = 0;
mLetterSpacing = 0;
if (NS_STYLE_WHITESPACE_PRE != mText->mWhiteSpace) {
PRIntn unit = mText->mWordSpacing.GetUnit();
if (eStyleUnit_Coord == unit) {
mWordSpacing = mText->mWordSpacing.GetCoordValue();
}
unit = mText->mLetterSpacing.GetUnit();
if (eStyleUnit_Coord == unit) {
mLetterSpacing = mText->mLetterSpacing.GetCoordValue();
}
}
mNumSpaces = 0;
mRemainingExtraSpace = 0;
mExtraSpacePerSpace = 0;
}
~TextStyle() {
NS_RELEASE(mNormalFont);
NS_IF_RELEASE(mSmallFont);
}
};
PRIntn PrepareUnicodeText(nsIRenderingContext& aRenderingContext,
nsTextTransformer& aTransformer,
PRInt32* aIndicies,
PRUnichar* aBuffer,
PRInt32& aTextLen,
nscoord& aNewWidth);
void PaintTextDecorations(nsIRenderingContext& aRenderingContext,
nsIStyleContext* aStyleContext,
TextStyle& aStyle,
nscoord aX, nscoord aY, nscoord aWidth);
void PaintTextSlowly(nsIPresContext& aPresContext,
nsIRenderingContext& aRenderingContext,
nsIStyleContext* aStyleContext,
TextStyle& aStyle,
nscoord aX, nscoord aY);
void RenderString(nsIRenderingContext& aRenderingContext,
nsIStyleContext* aStyleContext,
TextStyle& aStyle,
PRUnichar* aBuffer, PRInt32 aLength,
nscoord aX, nscoord aY,
nscoord aWidth);
void MeasureSmallCapsText(const nsHTMLReflowState& aReflowState,
TextStyle& aStyle,
PRUnichar* aWord,
PRInt32 aWordLength,
nscoord& aWidthResult);
void GetWidth(nsIRenderingContext& aRenderingContext,
TextStyle& aStyle,
PRUnichar* aBuffer, PRInt32 aLength,
nscoord& aWidthResult);
void PaintUnicodeText(nsIPresContext& aPresContext,
nsIRenderingContext& aRenderingContext,
nsIStyleContext* aStyleContext,
TextStyle& aStyle,
nscoord dx, nscoord dy);
void PaintAsciiText(nsIPresContext& aPresContext,
nsIRenderingContext& aRenderingContext,
nsIStyleContext* aStyleContext,
TextStyle& aStyle,
nscoord dx, nscoord dy);
nscoord ComputeTotalWordWidth(nsLineLayout& aLineLayout,
const nsHTMLReflowState& aReflowState,
nsIFrame* aNextFrame,
nscoord aBaseWidth);
nscoord ComputeWordFragmentWidth(nsLineLayout& aLineLayout,
const nsHTMLReflowState& aReflowState,
nsIFrame* aNextFrame,
nsITextContent* aText,
PRBool& aStop);
void ToCString(nsString& aBuf, PRInt32& aContentLength) const;
protected:
virtual ~TextFrame();
// XXX pack this tighter?
PRInt32 mContentOffset;
PRInt32 mContentLength;
PRUint32 mFlags;
PRInt32 mColumn;
nscoord mComputedWidth;
};
// Flag information used by rendering code. This information is
// computed by the ResizeReflow code. Remaining bits are used by the
// tab count.
// Flag indicating that whitespace was skipped
#define TEXT_SKIP_LEADING_WS 0x01
#define TEXT_HAS_MULTIBYTE 0x02
#define TEXT_BLINK_ON 0x04
#define TEXT_IN_WORD 0x08
#define TEXT_FIRST_LINE 0x10
#define TEXT_FIRST_LETTER 0x20
#define TEXT_TRIMMED_WS 0x40
//----------------------------------------------------------------------
static PRBool gBlinkTextOff;
static BlinkTimer* gTextBlinker;
#ifdef NOISY_BLINK
static PRTime gLastTick;
#endif
BlinkTimer::BlinkTimer()
{
NS_INIT_REFCNT();
mTimer = nsnull;
}
BlinkTimer::~BlinkTimer()
{
Stop();
}
void BlinkTimer::Start()
{
nsresult rv = NS_NewTimer(&mTimer);
if (NS_OK == rv) {
mTimer->Init(this, 750);
}
}
void BlinkTimer::Stop()
{
if (nsnull != mTimer) {
mTimer->Cancel();
NS_RELEASE(mTimer);
}
}
static NS_DEFINE_IID(kITimerCallbackIID, NS_ITIMERCALLBACK_IID);
NS_IMPL_ISUPPORTS(BlinkTimer, kITimerCallbackIID);
void BlinkTimer::AddFrame(TextFrame* aFrame) {
mFrames.AppendElement(aFrame);
if (1 == mFrames.Count()) {
Start();
}
}
PRBool BlinkTimer::RemoveFrame(TextFrame* aFrame) {
PRBool rv = mFrames.RemoveElement(aFrame);
if (0 == mFrames.Count()) {
Stop();
}
return rv;
}
PRInt32 BlinkTimer::FrameCount() {
return mFrames.Count();
}
void BlinkTimer::Notify(nsITimer *timer)
{
// Toggle blink state bit so that text code knows whether or not to
// render. All text code shares the same flag so that they all blink
// in unison.
gBlinkTextOff = PRBool(!gBlinkTextOff);
// XXX hack to get auto-repeating timers; restart before doing
// expensive work so that time between ticks is more even
Stop();
Start();
#ifdef NOISY_BLINK
PRTime now = PR_Now();
char buf[50];
PRTime delta;
LL_SUB(delta, now, gLastTick);
gLastTick = now;
PR_snprintf(buf, sizeof(buf), "%lldusec", delta);
printf("%s\n", buf);
#endif
PRInt32 i, n = mFrames.Count();
for (i = 0; i < n; i++) {
TextFrame* text = (TextFrame*) mFrames.ElementAt(i);
// Determine damaged area and tell view manager to redraw it
nsPoint offset;
nsRect bounds;
text->GetRect(bounds);
nsIView* view;
text->GetOffsetFromView(offset, view);
nsIViewManager* vm;
view->GetViewManager(vm);
bounds.x = offset.x;
bounds.y = offset.y;
vm->UpdateView(view, bounds, 0);
NS_RELEASE(vm);
}
}
//----------------------------------------------------------------------
nsresult
NS_NewTextFrame(nsIContent* aContent, nsIFrame* aParentFrame,
nsIFrame*& aResult)
{
nsIFrame* frame = new TextFrame(aContent, aParentFrame);
if (nsnull == frame) {
return NS_ERROR_OUT_OF_MEMORY;
}
aResult = frame;
return NS_OK;
}
TextFrame::TextFrame(nsIContent* aContent, nsIFrame* aParentFrame)
: nsSplittableFrame(aContent, aParentFrame)
{
if (nsnull == gTextBlinker) {
// Create text timer the first time out
gTextBlinker = new BlinkTimer();
}
NS_ADDREF(gTextBlinker);
}
TextFrame::~TextFrame()
{
if (0 != (mFlags & TEXT_BLINK_ON)) {
NS_ASSERTION(nsnull != gTextBlinker, "corrupted blinker");
gTextBlinker->RemoveFrame(this);
}
if (0 == gTextBlinker->Release()) {
// Release text timer when the last text frame is gone
gTextBlinker = nsnull;
}
}
NS_IMETHODIMP
TextFrame::GetCursorAndContentAt(nsIPresContext& aPresContext,
const nsPoint& aPoint,
nsIFrame** aFrame,
nsIContent** aContent,
PRInt32& aCursor)
{
*aContent = mContent;
aCursor = NS_STYLE_CURSOR_TEXT;
return NS_OK;
}
NS_IMETHODIMP
TextFrame::CreateContinuingFrame(nsIPresContext& aCX,
nsIFrame* aParent,
nsIStyleContext* aStyleContext,
nsIFrame*& aContinuingFrame)
{
TextFrame* cf = new TextFrame(mContent, aParent);
if (nsnull == cf) {
return NS_ERROR_OUT_OF_MEMORY;
}
cf->SetStyleContext(&aCX, aStyleContext);
cf->AppendToFlow(this);
aContinuingFrame = cf;
return NS_OK;
}
NS_IMETHODIMP
TextFrame::ContentChanged(nsIPresContext* aPresContext,
nsIContent* aChild,
nsISupports* aSubContent)
{
// Generate a reflow command with this frame as the target frame
nsIReflowCommand* cmd;
nsresult result;
result = NS_NewHTMLReflowCommand(&cmd, this, nsIReflowCommand::ContentChanged);
if (NS_OK == result) {
nsIPresShell* shell = aPresContext->GetShell();
shell->AppendReflowCommand(cmd);
NS_RELEASE(shell);
NS_RELEASE(cmd);
}
return result;
}
NS_IMETHODIMP
TextFrame::Paint(nsIPresContext& aPresContext,
nsIRenderingContext& aRenderingContext,
const nsRect& aDirtyRect)
{
if ((0 != (mFlags & TEXT_BLINK_ON)) && gBlinkTextOff) {
return NS_OK;
}
nsIStyleContext* sc = mStyleContext;
nsIStyleContext* firstLineStyle = nsnull;
if ((TEXT_FIRST_LETTER | TEXT_FIRST_LINE) & mFlags) {
firstLineStyle = aPresContext.
ProbePseudoStyleContextFor(nsHTMLAtoms::firstLinePseudo, mContentParent);
if (nsnull != firstLineStyle) {
sc = firstLineStyle;
}
}
const nsStyleDisplay* disp = (const nsStyleDisplay*)
sc->GetStyleData(eStyleStruct_Display);
if (disp->mVisible) {
TextStyle ts(aPresContext, aRenderingContext, sc);
if (ts.mSmallCaps || (0 != ts.mWordSpacing) || (0 != ts.mLetterSpacing) ||
((NS_STYLE_TEXT_ALIGN_JUSTIFY == ts.mText->mTextAlign) &&
(mRect.width > mComputedWidth))) {
PaintTextSlowly(aPresContext, aRenderingContext, sc, ts, 0, 0);
}
else {
// Choose rendering pathway based on rendering context
// performance hint.
PRUint32 hints = 0;
aRenderingContext.GetHints(hints);
if ((TEXT_HAS_MULTIBYTE & mFlags) ||
(0 == (hints & NS_RENDERING_HINT_FAST_8BIT_TEXT))) {
// Use PRUnichar rendering routine
PaintUnicodeText(aPresContext, aRenderingContext, sc, ts, 0, 0);
}
else {
// Use char rendering routine
PaintAsciiText(aPresContext, aRenderingContext, sc, ts, 0, 0);
}
}
}
NS_IF_RELEASE(firstLineStyle);
return NS_OK;
}
/**
* This method computes the starting and ending offsets of the
* selection for this frame. The results are placed into
* aResult. There are 5 cases that we represent with a starting offset
* (aResult.mStartOffset), ending offset (aResult.mEndOffset) and an
* empty selection flag (aResult.mEmptySelection):
*
* case 1: The selection completely misses this content/frame. In this
* case mStartOffset and mEndOffset will be set to aTextLength and
* mEmptySelection will be false.
*
* case 2: The selection begins somewhere before or at this frame and
* ends somewhere in this frame. In this case mStartOffset will be set
* to 0 and mEndOffset will be set to the end of the selection and
* mEmptySelection will be false.
*
* case 3: The selection begins somewhere in this frame and ends
* somewhere in this frame. In this case mStartOffset and mEndOffset
* are set accordingly and if they happen to be the same value then
* mEmptySelection is set to true (otherwise it is set to false).
*
* case 4: The selection begins somewhere in this frame and ends
* somewhere else. In this case mStartOffset is set to where the
* selection begins and mEndOffset is set to aTextLength and
* mEmptySelection is set to false.
*
* case 5: The selection covers the entire content/frame. In this case
* mStartOffset is set to zero and mEndOffset is set to aTextLength and
* mEmptySelection is set to false.
*/
void
TextFrame::ComputeSelectionInfo(nsIRenderingContext& aRenderingContext,
nsIDocument* aDocument,
PRInt32* aIndicies, PRInt32 aTextLength,
SelectionInfo& aResult)
{
// Assume, for now, that the selection misses this section of
// content completely.
aResult.mStartOffset = aTextLength;
aResult.mEndOffset = aTextLength;
aResult.mEmptySelection = PR_FALSE;
nsISelection * selection;
aDocument->GetSelection(selection);
nsSelectionRange * range = selection->GetRange();
nsSelectionPoint * startPnt = range->GetStartPoint();
nsSelectionPoint * endPnt = range->GetEndPoint();
nsIContent * startContent = startPnt->GetContent();
nsIContent * endContent = endPnt->GetContent();
PRInt32 startOffset = startPnt->GetOffset() - mContentOffset;
PRInt32 endOffset = endPnt->GetOffset() - mContentOffset;
// Check for the case that requires up to 3 sections first. This
// case also handles the empty selection.
if ((mContent == startContent) && (mContent == endContent)) {
// Selection starts and ends in this content (but maybe not this
// frame)
if ((startOffset >= mContentLength) || (endOffset <= 0)) {
// Selection doesn't intersect this frame
}
else if (endOffset < mContentLength) {
// End of selection is in this frame
aResult.mEndOffset = aIndicies[endOffset] - mContentOffset;
if (startOffset > 0) {
// Beginning of selection is also in this frame (this is the 3
// section case)
aResult.mStartOffset = aIndicies[startOffset] - mContentOffset;
}
else {
// This is a 2 section case
aResult.mStartOffset = 0;
}
if (startOffset == endOffset) {
aResult.mEmptySelection = PR_TRUE;
}
} else if (startOffset > 0) {
// This is a 2 section case
aResult.mStartOffset = aIndicies[startOffset] - mContentOffset;
} else {
// This is a 1 section case (where the entire section is
// selected)
aResult.mStartOffset = 0;
}
}
else if (aDocument->IsInRange(startContent, endContent, mContent)) {
if (mContent == startContent) {
// Selection starts (but does not end) in this content (but
// maybe not in this frame)
if (startOffset <= 0) {
// Selection starts before or at this frame
aResult.mStartOffset = 0;
}
else if (startOffset < mContentLength) {
// Selection starts somewhere in this frame
aResult.mStartOffset = aIndicies[startOffset] - mContentOffset;
}
else {
// Selection starts after this frame
}
}
else if (mContent == endContent) {
// Selection ends (but does not start) in this content (but
// maybe not in this frame)
if (endOffset <= 0) {
// Selection ends before this frame
}
else if (endOffset < mContentLength) {
// Selection ends in this frame
aResult.mStartOffset = 0;
aResult.mEndOffset = aIndicies[endOffset] - mContentOffset;
}
else {
// Selection ends after this frame (the entire frame is selected)
aResult.mStartOffset = 0;
}
}
else {
// Selection starts before this content and ends after this
// content therefore the entire frame is selected
aResult.mStartOffset = 0;
}
}
NS_IF_RELEASE(startContent);
NS_IF_RELEASE(endContent);
NS_RELEASE(selection);
}
#define XP_IS_SPACE_W XP_IS_SPACE
/**
* Prepare the text in the content for rendering. If aIndexes is not nsnull
* then fill in aIndexes's with the mapping from the original input to
* the prepared output.
*/
PRIntn
TextFrame::PrepareUnicodeText(nsIRenderingContext& aRenderingContext,
nsTextTransformer& aTX,
PRInt32* aIndexes,
PRUnichar* aBuffer,
PRInt32& aTextLen,
nscoord& aNewWidth)
{
PRIntn numSpaces = 0;
PRUnichar* dst = aBuffer;
// Setup transform to operate starting in the content at our content
// offset
aTX.Init(this, mContentOffset);
PRInt32 mappingInx = 0;
PRInt32 strInx = mContentOffset;
// Skip over the leading whitespace
PRInt32 n = mContentLength;
if (0 != (mFlags & TEXT_SKIP_LEADING_WS)) {
PRBool isWhitespace;
PRInt32 wordLen, contentLen;
aTX.GetNextWord(PR_FALSE, wordLen, contentLen, isWhitespace);
NS_ASSERTION(isWhitespace, "mFlags and content are out of sync");
if (isWhitespace) {
if (nsnull != aIndexes) {
// Point mapping indicies at the same content index since
// all of the compressed whitespace maps down to the same
// renderable character.
PRInt32 i = contentLen;
while (--i >= 0) {
*aIndexes++ = strInx;
}
}
n -= contentLen;
NS_ASSERTION(n >= 0, "whoops");
}
}
// Rescan the content and transform it. Stop when we have consumed
// mContentLength characters.
PRBool inWord = (TEXT_IN_WORD & mFlags) ? PR_TRUE : PR_FALSE;
PRInt32 column = mColumn;
PRInt32 textLength = 0;
while (0 != n) {
PRUnichar* bp;
PRBool isWhitespace;
PRInt32 wordLen, contentLen;
// Get the next word
bp = aTX.GetNextWord(inWord, wordLen, contentLen, isWhitespace);
if (nsnull == bp) {
break;
}
inWord = PR_FALSE;
if (isWhitespace) {
numSpaces++;
if ('\t' == bp[0]) {
PRInt32 spaces = 8 - (7 & column);
PRUnichar* tp = bp;
wordLen = spaces;
while (--spaces >= 0) {
*tp++ = ' ';
}
// XXX This is a one to many mapping that I think isn't handled well
if (nsnull != aIndexes) {
*aIndexes++ = strInx;
strInx++;
}
}
else if (0 == wordLen) {
break;
}
else if (nsnull != aIndexes) {
// Point mapping indicies at the same content index since
// all of the compressed whitespace maps down to the same
// renderable character.
PRInt32 i = contentLen;
while (--i >= 0) {
*aIndexes++ = strInx;
}
strInx++;
}
}
else {
if (nsnull != aIndexes) {
// Point mapping indicies at each content index in the word
PRInt32 i = contentLen;
while (--i >= 0) {
*aIndexes++ = strInx++;
}
}
}
column += wordLen;
textLength += wordLen;
n -= contentLen;
nsCRT::memcpy(dst, bp, sizeof(PRUnichar) * wordLen);
dst += wordLen;
NS_ASSERTION(n >= 0, "whoops");
}
// Remove trailing whitespace if it was trimmed after reflow
if (TEXT_TRIMMED_WS & mFlags) {
if (--dst >= aBuffer) {
PRUnichar ch = *dst;
if (XP_IS_SPACE(ch)) {
textLength--;
}
}
numSpaces--;
}
aTextLen = textLength;
return numSpaces;
}
// XXX This clearly needs to be done by the container, *somehow*
#define CURSOR_COLOR NS_RGB(0,0,255)
static void
RenderSelectionCursor(nsIRenderingContext& aRenderingContext,
nscoord dx, nscoord dy, nscoord aHeight,
nscolor aCursorColor)
{
nsPoint pnts[4];
nscoord ox = aHeight / 4;
nscoord oy = ox;
nscoord x0 = dx;
nscoord y0 = dy + aHeight;
pnts[0].x = x0 - ox;
pnts[0].y = y0;
pnts[1].x = x0;
pnts[1].y = y0 - oy;
pnts[2].x = x0 + ox;
pnts[2].y = y0;
pnts[3].x = x0 - ox;
pnts[3].y = y0;
// Draw little blue triangle
aRenderingContext.SetColor(aCursorColor);
aRenderingContext.FillPolygon(pnts, 4);
}
// XXX letter-spacing
// XXX word-spacing
void
TextFrame::PaintTextDecorations(nsIRenderingContext& aRenderingContext,
nsIStyleContext* aStyleContext,
TextStyle& aTextStyle,
nscoord aX, nscoord aY, nscoord aWidth)
{
nscolor overColor;
nscolor underColor;
nscolor strikeColor;
nsIStyleContext* context = aStyleContext;
PRUint8 decorations = aTextStyle.mFont->mFont.decorations;
PRUint8 decorMask = decorations;
NS_ADDREF(context);
do { // find decoration colors
const nsStyleText* styleText =
(const nsStyleText*)context->GetStyleData(eStyleStruct_Text);
if (decorMask & styleText->mTextDecoration) { // a decoration defined here
const nsStyleColor* styleColor =
(const nsStyleColor*)context->GetStyleData(eStyleStruct_Color);
if (NS_STYLE_TEXT_DECORATION_UNDERLINE & decorMask & styleText->mTextDecoration) {
underColor = styleColor->mColor;
decorMask &= ~NS_STYLE_TEXT_DECORATION_UNDERLINE;
}
if (NS_STYLE_TEXT_DECORATION_OVERLINE & decorMask & styleText->mTextDecoration) {
overColor = styleColor->mColor;
decorMask &= ~NS_STYLE_TEXT_DECORATION_OVERLINE;
}
if (NS_STYLE_TEXT_DECORATION_LINE_THROUGH & decorMask & styleText->mTextDecoration) {
strikeColor = styleColor->mColor;
decorMask &= ~NS_STYLE_TEXT_DECORATION_LINE_THROUGH;
}
}
if (0 != decorMask) {
nsIStyleContext* lastContext = context;
context = context->GetParent();
NS_RELEASE(lastContext);
}
} while ((nsnull != context) && (0 != decorMask));
NS_IF_RELEASE(context);
nscoord offset;
nscoord size;
nscoord baseline;
aTextStyle.mNormalFont->GetMaxAscent(baseline);
if (decorations & (NS_FONT_DECORATION_OVERLINE | NS_FONT_DECORATION_UNDERLINE)) {
aTextStyle.mNormalFont->GetUnderline(offset, size);
if (decorations & NS_FONT_DECORATION_OVERLINE) {
aRenderingContext.SetColor(overColor);
aRenderingContext.FillRect(aX, aY, aWidth, size);
}
if (decorations & NS_FONT_DECORATION_UNDERLINE) {
aRenderingContext.SetColor(underColor);
aRenderingContext.FillRect(aX, aY + baseline - offset, aWidth, size);
}
}
if (decorations & NS_FONT_DECORATION_LINE_THROUGH) {
aTextStyle.mNormalFont->GetStrikeout(offset, size);
aRenderingContext.SetColor(strikeColor);
aRenderingContext.FillRect(aX, aY + baseline - offset, aWidth, size);
}
}
void
TextFrame::PaintUnicodeText(nsIPresContext& aPresContext,
nsIRenderingContext& aRenderingContext,
nsIStyleContext* aStyleContext,
TextStyle& aTextStyle,
nscoord dx, nscoord dy)
{
nsIPresShell* shell = aPresContext.GetShell();
nsIDocument* doc = shell->GetDocument();
PRBool displaySelection;
displaySelection = doc->GetDisplaySelection();
// Make enough space to transform
PRUnichar wordBufMem[WORD_BUF_SIZE];
PRUnichar paintBufMem[TEXT_BUF_SIZE];
PRInt32 indicies[TEXT_BUF_SIZE];
PRInt32* ip = indicies;
PRUnichar* paintBuf = paintBufMem;
if (mContentLength > TEXT_BUF_SIZE) {
ip = new PRInt32[mContentLength];
paintBuf = new PRUnichar[mContentLength];
}
nscoord width = mRect.width;
PRInt32 textLength;
// Transform text from content into renderable form
nsTextTransformer tx(wordBufMem, WORD_BUF_SIZE);
PrepareUnicodeText(aRenderingContext, tx,
displaySelection ? ip : nsnull,
paintBuf, textLength, width);
PRUnichar* text = paintBuf;
if (0 != textLength) {
if (!displaySelection) {
// When there is no selection showing, use the fastest and
// simplest rendering approach
aRenderingContext.DrawString(text, textLength, dx, dy, width);
PaintTextDecorations(aRenderingContext, aStyleContext, aTextStyle,
dx, dy, width);
}
else {
SelectionInfo si;
ComputeSelectionInfo(aRenderingContext, doc, ip, textLength, si);
nscoord textWidth;
if (si.mEmptySelection) {
aRenderingContext.DrawString(text, textLength, dx, dy, width);
PaintTextDecorations(aRenderingContext, aStyleContext,
aTextStyle, dx, dy, width);
aRenderingContext.GetWidth(text, PRUint32(si.mStartOffset), textWidth);
RenderSelectionCursor(aRenderingContext,
dx + textWidth, dy, mRect.height,
CURSOR_COLOR);
}
else {
nscoord x = dx;
if (0 != si.mStartOffset) {
// Render first (unselected) section
aRenderingContext.GetWidth(text, PRUint32(si.mStartOffset),
textWidth);
aRenderingContext.DrawString(text, si.mStartOffset,
x, dy, textWidth);
PaintTextDecorations(aRenderingContext, aStyleContext, aTextStyle,
x, dy, textWidth);
x += textWidth;
}
PRInt32 secondLen = si.mEndOffset - si.mStartOffset;
if (0 != secondLen) {
// Get the width of the second (selected) section
aRenderingContext.GetWidth(text + si.mStartOffset,
PRUint32(secondLen), textWidth);
// Render second (selected) section
aRenderingContext.SetColor(aTextStyle.mSelectionBGColor);
aRenderingContext.FillRect(x, dy, textWidth, mRect.height);
aRenderingContext.SetColor(aTextStyle.mSelectionTextColor);
aRenderingContext.DrawString(text + si.mStartOffset, secondLen,
x, dy, textWidth);
PaintTextDecorations(aRenderingContext, aStyleContext, aTextStyle,
x, dy, textWidth);
aRenderingContext.SetColor(aTextStyle.mColor->mColor);
x += textWidth;
}
if (textLength != si.mEndOffset) {
PRInt32 thirdLen = textLength - si.mEndOffset;
// Render third (unselected) section
aRenderingContext.GetWidth(text + si.mEndOffset, PRUint32(thirdLen),
textWidth);
aRenderingContext.DrawString(text + si.mEndOffset,
thirdLen, x, dy, textWidth);
PaintTextDecorations(aRenderingContext, aStyleContext, aTextStyle,
x, dy, textWidth);
}
}
}
}
// Cleanup
if (paintBuf != paintBufMem) {
delete [] paintBuf;
}
if (ip != indicies) {
delete [] ip;
}
NS_RELEASE(shell);
NS_RELEASE(doc);
}
void
TextFrame::RenderString(nsIRenderingContext& aRenderingContext,
nsIStyleContext* aStyleContext,
TextStyle& aTextStyle,
PRUnichar* aBuffer, PRInt32 aLength,
nscoord aX, nscoord aY,
nscoord aWidth)
{
PRUnichar buf[TEXT_BUF_SIZE];
PRUnichar* bp0 = buf;
if (aLength > TEXT_BUF_SIZE) {
bp0 = new PRUnichar[aLength];
}
PRUnichar* bp = bp0;
PRBool spacing = (0 != aTextStyle.mLetterSpacing) ||
(0 != aTextStyle.mWordSpacing) || (mRect.width > mComputedWidth);
nscoord spacingMem[TEXT_BUF_SIZE];
PRIntn* sp0 = spacingMem;
if (spacing && (aLength > TEXT_BUF_SIZE)) {
sp0 = new nscoord[aLength];
}
PRIntn* sp = sp0;
nscoord smallY = aY;
if (aTextStyle.mSmallCaps) {
nscoord normalAscent, smallAscent;
aTextStyle.mNormalFont->GetMaxAscent(normalAscent);
aTextStyle.mSmallFont->GetMaxAscent(smallAscent);
if (normalAscent > smallAscent) {
smallY = aY + normalAscent - smallAscent;
}
}
nsIFontMetrics* lastFont = aTextStyle.mLastFont;
nscoord lastY = aY;
if (lastFont == aTextStyle.mSmallFont) {
lastY = smallY;
}
PRInt32 pendingCount;
PRUnichar* runStart = bp;
nscoord charWidth, width = 0;
for (; --aLength >= 0; aBuffer++) {
nsIFontMetrics* nextFont;
nscoord nextY, glyphWidth;
PRUnichar ch = *aBuffer;
if (aTextStyle.mSmallCaps && XP_IS_LOWERCASE(ch)) {
nextFont = aTextStyle.mSmallFont;
nextY = smallY;
ch = XP_TO_UPPER(ch);
if (lastFont != aTextStyle.mSmallFont) {
aRenderingContext.SetFont(aTextStyle.mSmallFont);
aRenderingContext.GetWidth(ch, charWidth);
aRenderingContext.SetFont(aTextStyle.mNormalFont);
}
else {
aRenderingContext.GetWidth(ch, charWidth);
}
glyphWidth = charWidth + aTextStyle.mLetterSpacing;
}
else if (ch == ' ') {
nextFont = aTextStyle.mNormalFont;
nextY = aY;
glyphWidth = aTextStyle.mSpaceWidth + aTextStyle.mWordSpacing;
nscoord extra = aTextStyle.mExtraSpacePerSpace;
if (--aTextStyle.mNumSpaces == 0) {
extra += aTextStyle.mRemainingExtraSpace;
}
glyphWidth += extra;
}
else {
if (lastFont != aTextStyle.mNormalFont) {
aRenderingContext.SetFont(aTextStyle.mNormalFont);
aRenderingContext.GetWidth(ch, charWidth);
aRenderingContext.SetFont(aTextStyle.mSmallFont);
}
else {
aRenderingContext.GetWidth(ch, charWidth);
}
nextFont = aTextStyle.mNormalFont;
nextY = aY;
glyphWidth = charWidth + aTextStyle.mLetterSpacing;
}
if (nextFont != lastFont) {
pendingCount = bp - runStart;
if (0 != pendingCount) {
// Measure previous run of characters using the previous font
aRenderingContext.DrawString(runStart, pendingCount,
aX, lastY, width,
spacing ? sp0 : nsnull);
// Note: use aY not small-y so that decorations are drawn with
// respect to the normal-font not the current font.
PaintTextDecorations(aRenderingContext, aStyleContext, aTextStyle,
aX, aY, width);
aWidth -= width;
aX += width;
runStart = bp = bp0;
sp = sp0;
width = 0;
}
aRenderingContext.SetFont(nextFont);
lastFont = nextFont;
lastY = nextY;
}
*bp++ = ch;
*sp++ = glyphWidth;
width += glyphWidth;
}
pendingCount = bp - runStart;
if (0 != pendingCount) {
// Measure previous run of characters using the previous font
aRenderingContext.DrawString(runStart, pendingCount, aX, lastY, width,
spacing ? sp0 : nsnull);
// Note: use aY not small-y so that decorations are drawn with
// respect to the normal-font not the current font.
PaintTextDecorations(aRenderingContext, aStyleContext, aTextStyle,
aX, aY, aWidth);
}
aTextStyle.mLastFont = lastFont;
if (bp0 != buf) {
delete [] bp0;
}
if (sp0 != spacingMem) {
delete [] sp0;
}
}
inline void
TextFrame::MeasureSmallCapsText(const nsHTMLReflowState& aReflowState,
TextStyle& aTextStyle,
PRUnichar* aWord,
PRInt32 aWordLength,
nscoord& aWidthResult)
{
nsIRenderingContext& rc = *aReflowState.rendContext;
GetWidth(rc, aTextStyle, aWord, aWordLength, aWidthResult);
if (aTextStyle.mLastFont != aTextStyle.mNormalFont) {
rc.SetFont(aTextStyle.mNormalFont);
aTextStyle.mLastFont = aTextStyle.mNormalFont;
}
}
// XXX factor in logic from RenderString into here; gaps, justification, etc.
void
TextFrame::GetWidth(nsIRenderingContext& aRenderingContext,
TextStyle& aTextStyle,
PRUnichar* aBuffer, PRInt32 aLength,
nscoord& aWidthResult)
{
PRUnichar buf[TEXT_BUF_SIZE];
PRUnichar* bp0 = buf;
if (aLength > TEXT_BUF_SIZE) {
bp0 = new PRUnichar[aLength];
}
PRUnichar* bp = bp0;
nsIFontMetrics* lastFont = aTextStyle.mLastFont;
nscoord w, sum = 0;
PRInt32 pendingCount;
PRUnichar* runStart = bp;
for (; --aLength >= 0; aBuffer++) {
nsIFontMetrics* nextFont;
PRUnichar ch = *aBuffer;
if (aTextStyle.mSmallCaps && XP_IS_LOWERCASE(ch)) {
nextFont = aTextStyle.mSmallFont;
ch = XP_TO_UPPER(ch);
}
else {
nextFont = aTextStyle.mNormalFont;
}
if (nextFont != lastFont) {
pendingCount = bp - runStart;
if (0 != pendingCount) {
// Measure previous run of characters using the previous font
aRenderingContext.GetWidth(runStart, pendingCount, w);
sum += w;
runStart = bp;
}
aRenderingContext.SetFont(nextFont);
lastFont = nextFont;
}
*bp++ = ch;
}
pendingCount = bp - runStart;
if (0 != pendingCount) {
// Measure previous run of characters using the previous font
aRenderingContext.GetWidth(runStart, pendingCount, w);
sum += w;
}
if (bp0 != buf) {
delete [] bp0;
}
aTextStyle.mLastFont = lastFont;
aWidthResult = sum;
}
void
TextFrame::PaintTextSlowly(nsIPresContext& aPresContext,
nsIRenderingContext& aRenderingContext,
nsIStyleContext* aStyleContext,
TextStyle& aTextStyle,
nscoord dx, nscoord dy)
{
nsIPresShell* shell = aPresContext.GetShell();
nsIDocument* doc = shell->GetDocument();
PRBool displaySelection;
displaySelection = doc->GetDisplaySelection();
// Make enough space to transform
PRUnichar wordBufMem[WORD_BUF_SIZE];
PRUnichar paintBufMem[TEXT_BUF_SIZE];
PRInt32 indicies[TEXT_BUF_SIZE];
PRInt32* ip = indicies;
PRUnichar* paintBuf = paintBufMem;
if (mContentLength > TEXT_BUF_SIZE) {
ip = new PRInt32[mContentLength];
paintBuf = new PRUnichar[mContentLength];
}
nscoord width = mRect.width;
PRInt32 textLength;
// Transform text from content into renderable form
nsTextTransformer tx(wordBufMem, WORD_BUF_SIZE);
aTextStyle.mNumSpaces = PrepareUnicodeText(aRenderingContext, tx,
displaySelection ? ip : nsnull,
paintBuf, textLength, width);
if (mRect.width > mComputedWidth) {
if (0 != aTextStyle.mNumSpaces) {
nscoord extra = mRect.width - mComputedWidth;
nscoord adjustPerSpace =
aTextStyle.mExtraSpacePerSpace = extra / aTextStyle.mNumSpaces;
aTextStyle.mRemainingExtraSpace = extra -
(aTextStyle.mExtraSpacePerSpace * aTextStyle.mNumSpaces);
}
else {
// We have no whitespace but were given some extra space. There
// are two plausible places to put the extra space: to the left
// and to the right. If this is anywhere but the last place on
// the line then the correct answer is to the right.
}
}
PRUnichar* text = paintBuf;
if (0 != textLength) {
if (!displaySelection) {
// When there is no selection showing, use the fastest and
// simplest rendering approach
RenderString(aRenderingContext, aStyleContext, aTextStyle,
text, textLength, dx, dy, width);
}
else {
SelectionInfo si;
ComputeSelectionInfo(aRenderingContext, doc, ip, textLength, si);
nscoord textWidth;
if (si.mEmptySelection) {
RenderString(aRenderingContext, aStyleContext, aTextStyle,
text, textLength, dx, dy, width);
GetWidth(aRenderingContext, aTextStyle,
text, PRUint32(si.mStartOffset), textWidth);
RenderSelectionCursor(aRenderingContext,
dx + textWidth, dy, mRect.height,
CURSOR_COLOR);
}
else {
nscoord x = dx;
if (0 != si.mStartOffset) {
// Render first (unselected) section
GetWidth(aRenderingContext, aTextStyle,
text, PRUint32(si.mStartOffset),
textWidth);
RenderString(aRenderingContext, aStyleContext, aTextStyle,
text, si.mStartOffset,
x, dy, textWidth);
x += textWidth;
}
PRInt32 secondLen = si.mEndOffset - si.mStartOffset;
if (0 != secondLen) {
// Get the width of the second (selected) section
GetWidth(aRenderingContext, aTextStyle,
text + si.mStartOffset,
PRUint32(secondLen), textWidth);
// Render second (selected) section
aRenderingContext.SetColor(aTextStyle.mSelectionBGColor);
aRenderingContext.FillRect(x, dy, textWidth, mRect.height);
aRenderingContext.SetColor(aTextStyle.mSelectionTextColor);
RenderString(aRenderingContext, aStyleContext, aTextStyle,
text + si.mStartOffset, secondLen,
x, dy, textWidth);
aRenderingContext.SetColor(aTextStyle.mColor->mColor);
x += textWidth;
}
if (textLength != si.mEndOffset) {
PRInt32 thirdLen = textLength - si.mEndOffset;
// Render third (unselected) section
GetWidth(aRenderingContext, aTextStyle,
text + si.mEndOffset, PRUint32(thirdLen),
textWidth);
RenderString(aRenderingContext, aStyleContext, aTextStyle,
text + si.mEndOffset,
thirdLen, x, dy, textWidth);
}
}
}
}
// Cleanup
if (paintBuf != paintBufMem) {
delete [] paintBuf;
}
if (ip != indicies) {
delete [] ip;
}
NS_RELEASE(shell);
NS_RELEASE(doc);
}
void
TextFrame::PaintAsciiText(nsIPresContext& aPresContext,
nsIRenderingContext& aRenderingContext,
nsIStyleContext* aStyleContext,
TextStyle& aTextStyle,
nscoord dx, nscoord dy)
{
nsIPresShell* shell = aPresContext.GetShell();
nsIDocument* doc = shell->GetDocument();
PRBool displaySelection;
displaySelection = doc->GetDisplaySelection();
// Make enough space to transform
PRUnichar wordBufMem[WORD_BUF_SIZE];
char paintBufMem[TEXT_BUF_SIZE];
PRUnichar rawPaintBufMem[TEXT_BUF_SIZE];
PRInt32 indicies[TEXT_BUF_SIZE];
PRInt32* ip = indicies;
char* paintBuf = paintBufMem;
PRUnichar* rawPaintBuf = rawPaintBufMem;
if (mContentLength > TEXT_BUF_SIZE) {
ip = new PRInt32[mContentLength];
paintBuf = new char[mContentLength];
rawPaintBuf = new PRUnichar[mContentLength];
}
nscoord width = mRect.width;
PRInt32 textLength;
// Transform text from content into renderable form
nsTextTransformer tx(wordBufMem, WORD_BUF_SIZE);
PrepareUnicodeText(aRenderingContext, tx,
displaySelection ? ip : nsnull,
rawPaintBuf, textLength, width);
// Translate unicode data into ascii for rendering
char* dst = paintBuf;
char* end = dst + textLength;
PRUnichar* src = rawPaintBuf;
while (dst < end) {
*dst++ = (char) ((unsigned char) *src++);
}
char* text = paintBuf;
if (0 != textLength) {
if (!displaySelection) {
// When there is no selection showing, use the fastest and
// simplest rendering approach
aRenderingContext.DrawString(text, textLength, dx, dy, width);
PaintTextDecorations(aRenderingContext, aStyleContext, aTextStyle,
dx, dy, width);
}
else {
SelectionInfo si;
ComputeSelectionInfo(aRenderingContext, doc, ip, textLength, si);
nscoord textWidth;
if (si.mEmptySelection) {
aRenderingContext.DrawString(text, textLength, dx, dy, width);
PaintTextDecorations(aRenderingContext, aStyleContext, aTextStyle,
dx, dy, width);
aRenderingContext.GetWidth(text, PRUint32(si.mStartOffset), textWidth);
RenderSelectionCursor(aRenderingContext,
dx + textWidth, dy, mRect.height,
CURSOR_COLOR);
}
else {
nscoord x = dx;
if (0 != si.mStartOffset) {
// Render first (unselected) section
aRenderingContext.GetWidth(text, PRUint32(si.mStartOffset),
textWidth);
aRenderingContext.DrawString(text, si.mStartOffset,
x, dy, textWidth);
PaintTextDecorations(aRenderingContext, aStyleContext, aTextStyle,
x, dy, textWidth);
x += textWidth;
}
PRInt32 secondLen = si.mEndOffset - si.mStartOffset;
if (0 != secondLen) {
// Get the width of the second (selected) section
aRenderingContext.GetWidth(text + si.mStartOffset,
PRUint32(secondLen), textWidth);
// Render second (selected) section
aRenderingContext.SetColor(aTextStyle.mSelectionBGColor);
aRenderingContext.FillRect(x, dy, textWidth, mRect.height);
aRenderingContext.SetColor(aTextStyle.mSelectionTextColor);
aRenderingContext.DrawString(text + si.mStartOffset, secondLen,
x, dy, textWidth);
PaintTextDecorations(aRenderingContext, aStyleContext, aTextStyle,
x, dy, textWidth);
aRenderingContext.SetColor(aTextStyle.mColor->mColor);
x += textWidth;
}
if (textLength != si.mEndOffset) {
PRInt32 thirdLen = textLength - si.mEndOffset;
// Render third (unselected) section
aRenderingContext.GetWidth(text + si.mEndOffset, PRUint32(thirdLen),
textWidth);
aRenderingContext.DrawString(text + si.mEndOffset,
thirdLen, x, dy, textWidth);
PaintTextDecorations(aRenderingContext, aStyleContext, aTextStyle,
x, dy, textWidth);
}
}
}
}
// Cleanup
if (paintBuf != paintBufMem) {
delete [] paintBuf;
}
if (rawPaintBuf != rawPaintBufMem) {
delete [] rawPaintBuf;
}
if (ip != indicies) {
delete [] ip;
}
NS_RELEASE(shell);
NS_RELEASE(doc);
}
NS_IMETHODIMP
TextFrame::FindTextRuns(nsLineLayout& aLineLayout)
{
if (nsnull == mPrevInFlow) {
aLineLayout.AddText(this);
}
return NS_OK;
}
//---------------------------------------------------
// Uses a binary search for find where the cursor falls in the line of text
// It also keeps track of the part of the string that has already been measured
// so it doesn't have to keep measuring the same text over and over
//
// Param "aBaseWidth" contains the width in twips of the portion
// of the text that has already been measured, and aBaseInx contains
// the index of the text that has already been measured.
//
// aTextWidth returns the (in twips) the length of the text that falls before the cursor
// aIndex contains the index of the text where the cursor falls
static PRBool
BinarySearchForPosition(nsIRenderingContext* acx,
PRUnichar* aText,
PRInt32 aBaseWidth,
PRInt32 aBaseInx,
PRInt32 aStartInx,
PRInt32 aEndInx,
PRInt32 aCursorPos,
PRInt32& aIndex,
PRInt32& aTextWidth)
{
PRInt32 range = aEndInx - aStartInx;
if (range == 1) {
aIndex = aStartInx + aBaseInx;
return PR_TRUE;
}
PRInt32 inx = aStartInx + (range / 2);
PRInt32 textWidth;
acx->GetWidth(aText, inx, textWidth);
PRInt32 fullWidth = aBaseWidth + textWidth;
if (fullWidth == aCursorPos) {
aIndex = inx;
return PR_TRUE;
} else if (aCursorPos < fullWidth) {
aTextWidth = aBaseWidth;
if (BinarySearchForPosition(acx, aText, aBaseWidth, aBaseInx, aStartInx, inx, aCursorPos, aIndex, aTextWidth)) {
return PR_TRUE;
}
} else {
aTextWidth = fullWidth;
PRInt32 end = aEndInx - inx;
if (BinarySearchForPosition(acx, aText+inx, fullWidth, aBaseInx+inx, 0, end, aCursorPos, aIndex, aTextWidth)) {
return PR_TRUE;
}
}
return PR_FALSE;
}
//---------------------------------------------------------------------------
// Uses a binary search to find the position of the cursor in the text.
// The "indices array is used to map from the compressed text back to the
// un-compressed text, selection is based on the un-compressed text, the visual
// display of selection is based on the compressed text.
//---------------------------------------------------------------------------
NS_IMETHODIMP
TextFrame::GetPosition(nsIPresContext& aCX,
nsIRenderingContext * aRendContext,
nsGUIEvent* aEvent,
nsIFrame* aNewFrame,
PRUint32& aAcutalContentOffset,
PRInt32& aOffset)
{
PRUnichar wordBufMem[WORD_BUF_SIZE];
PRUnichar paintBufMem[TEXT_BUF_SIZE];
PRInt32 indicies[TEXT_BUF_SIZE];
PRInt32* ip = indicies;
PRUnichar* paintBuf = paintBufMem;
if (mContentLength >= TEXT_BUF_SIZE) {
ip = new PRInt32[mContentLength+1];
paintBuf = new PRUnichar[mContentLength];
}
nscoord width = 0;
PRInt32 textLength;
// Find the font metrics for this text
nsIStyleContext* styleContext;
aNewFrame->GetStyleContext(&aCX, styleContext);
const nsStyleFont *font = (const nsStyleFont*)
styleContext->GetStyleData(eStyleStruct_Font);
NS_RELEASE(styleContext);
nsIFontMetrics* fm = aCX.GetMetricsFor(font->mFont);
aRendContext->SetFont(fm);
PRBool smallCaps = NS_STYLE_FONT_VARIANT_SMALL_CAPS == font->mFont.variant;
// Get the renderable form of the text
nsTextTransformer tx(wordBufMem, WORD_BUF_SIZE);
PrepareUnicodeText(*aRendContext, tx,
ip, paintBuf, textLength, width);
ip[mContentLength] = ip[mContentLength-1]+1;
PRInt32 offset = mContentOffset + mContentLength;
PRInt32 index;
PRInt32 textWidth;
PRUnichar* text = paintBuf;
PRBool found = BinarySearchForPosition(aRendContext, text, 0, 0, 0,
PRInt32(textLength),
PRInt32(aEvent->point.x),
index, textWidth);
if (found) {
PRInt32 charWidth;
aRendContext->GetWidth(text[index], charWidth);
charWidth /= 2;
if (PRInt32(aEvent->point.x) > textWidth+charWidth) {
index++;
}
offset = 0;
PRInt32 j;
PRInt32* ptr = ip;
for (j=0;j<=PRInt32(mContentLength);j++) {
if (*ptr == index+mContentOffset) {
offset = j+mContentOffset;
break;
}
ptr++;
}
}
NS_RELEASE(fm);
if (ip != indicies) {
delete [] ip;
}
if (paintBuf != paintBufMem) {
delete [] paintBuf;
}
aAcutalContentOffset = ((TextFrame *)aNewFrame)->mContentOffset;
aOffset = offset;
return NS_OK;
}
NS_IMETHODIMP
TextFrame::Reflow(nsIPresContext& aPresContext,
nsHTMLReflowMetrics& aMetrics,
const nsHTMLReflowState& aReflowState,
nsReflowStatus& aStatus)
{
NS_PRECONDITION(nsnull != aReflowState.lineLayout, "no line layout");
NS_FRAME_TRACE(NS_FRAME_TRACE_CALLS,
("enter TextFrame::Reflow: aMaxSize=%d,%d",
aReflowState.maxSize.width, aReflowState.maxSize.height));
// Get starting offset into the content
PRInt32 startingOffset = 0;
if (nsnull != mPrevInFlow) {
TextFrame* prev = (TextFrame*) mPrevInFlow;
startingOffset = prev->mContentOffset + prev->mContentLength;
}
// Find out what style context should be used
nsLineLayout& lineLayout = *aReflowState.lineLayout;
nsIStyleContext* firstLineStyle = nsnull;
nsIStyleContext* sc = mStyleContext;
if (0 == lineLayout.GetLineNumber()) {
firstLineStyle = lineLayout.mPresContext.
ProbePseudoStyleContextFor(nsHTMLAtoms::firstLinePseudo, mContentParent);
if (nsnull != firstLineStyle) {
sc = firstLineStyle;
}
}
TextStyle ts(aPresContext, *aReflowState.rendContext, sc);
// Initialize mFlags (without destroying the TEXT_BLINK_ON bit) bits
// that are filled in by the reflow routines.
mFlags &= TEXT_BLINK_ON;
if (ts.mFont->mFont.decorations & NS_STYLE_TEXT_DECORATION_BLINK) {
if (0 == (mFlags & TEXT_BLINK_ON)) {
mFlags |= TEXT_BLINK_ON;
gTextBlinker->AddFrame(this);
}
}
if (nsnull != firstLineStyle) {
mFlags |= TEXT_FIRST_LINE;
}
PRBool wrapping = NS_STYLE_WHITESPACE_NORMAL == ts.mText->mWhiteSpace;
// Set whitespace skip flag
PRBool skipWhitespace = PR_FALSE;
if (NS_STYLE_WHITESPACE_PRE != ts.mText->mWhiteSpace) {
if (lineLayout.GetSkipLeadingWhiteSpace()) {
skipWhitespace = PR_TRUE;
}
}
nscoord x = 0;
nscoord maxWidth = aReflowState.maxSize.width;
nscoord maxWordWidth = 0;
nscoord prevMaxWordWidth = 0;
PRBool endsInWhitespace = PR_FALSE;
PRBool endsInNewline = PR_FALSE;
PRBool firstWord = PR_TRUE;
// Setup text transformer to transform this frames text content
nsTextRun* textRun = lineLayout.FindTextRunFor(this);
PRUnichar wordBuf[WORD_BUF_SIZE];
nsTextTransformer tx(wordBuf, WORD_BUF_SIZE);
nsresult rv = tx.Init(/**textRun, XXX*/ this, startingOffset);
if (NS_OK != rv) {
return rv;
}
PRInt32 contentLength = tx.GetContentLength();
// Offset tracks how far along we are in the content
PRInt32 offset = startingOffset;
PRInt32 prevOffset = -1;
nscoord lastWordWidth = 0;
// 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
// is only valid for one pass through the measuring loop.
PRBool inWord = lineLayout.InWord();
if (inWord) {
mFlags |= TEXT_IN_WORD;
}
PRInt32 column = lineLayout.GetColumn();
PRInt32 prevColumn = column;
mColumn = column;
for (;;) {
// Get next word/whitespace from the text
PRBool isWhitespace;
PRInt32 wordLen, contentLen;
PRUnichar* bp = tx.GetNextWord(inWord, wordLen, contentLen, isWhitespace);
if (nsnull == bp) {
break;
}
inWord = PR_FALSE;
// Measure the word/whitespace
nscoord width;
if (isWhitespace) {
if (0 == wordLen) {
// We hit a newline. Stop looping.
prevOffset = offset;
offset++;
endsInWhitespace = PR_TRUE;
endsInNewline = PR_TRUE;
break;
}
if (skipWhitespace) {
offset += contentLen;
skipWhitespace = PR_FALSE;
// Only set flag when we actually do skip whitespace
mFlags |= TEXT_SKIP_LEADING_WS;
continue;
}
if ('\t' == bp[0]) {
// Expand tabs to the proper width
wordLen = 8 - (7 & column);
width = ts.mSpaceWidth * wordLen;
}
else {
width = ts.mSpaceWidth + ts.mWordSpacing;/* XXX simplistic */
}
} else {
if (ts.mSmallCaps) {
MeasureSmallCapsText(aReflowState, ts, bp, wordLen, width);
}
else {
aReflowState.rendContext->GetWidth(bp, wordLen, width);
}
if (ts.mLetterSpacing) {
width += ts.mLetterSpacing * wordLen;
}
skipWhitespace = PR_FALSE;
lastWordWidth = width;
}
// See if there is room for the text
if ((0 != x) && wrapping && (x + width > maxWidth)) {
// The text will not fit.
break;
}
prevColumn = column;
column += wordLen;
x += width;
prevMaxWordWidth = maxWordWidth;
if (width > maxWordWidth) {
maxWordWidth = width;
}
endsInWhitespace = isWhitespace;
prevOffset = offset;
offset += contentLen;
}
if (tx.HasMultibyte()) {
mFlags |= TEXT_HAS_MULTIBYTE;
}
// 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);
}
else {
// 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 ((offset == contentLength) && (prevOffset >= 0)) {
// This frame does start a word. However, there is no point
// messing around with it if we are already out of room.
if ((0 == lineLayout.GetPlacedFrames()) || (x <= 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.GetTextAt(prevOffset), offset-prevOffset);
ListTag(stdout);
printf(": start='");
fputs(tmp, stdout);
printf("' baseWidth=%d\n", lastWordWidth);
#endif
// 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.
nscoord wordWidth = ComputeTotalWordWidth(lineLayout, aReflowState,
next, lastWordWidth);
if ((0 == lineLayout.GetPlacedFrames()) ||
(x - 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 > maxWordWidth) {
maxWordWidth = wordWidth;
}
}
else {
// The fully joined word won't fit. We need to reduce our
// size by the lastWordWidth.
x -= lastWordWidth;
maxWordWidth = prevMaxWordWidth;
offset = prevOffset;
column = prevColumn;
#ifdef DEBUG_WORD_WRAPPING
printf(" x=%d maxWordWidth=%d len=%d\n", x, maxWordWidth,
offset - aStartingOffset);
#endif
lineLayout.ForgetWordFrames();
}
}
}
}
}
lineLayout.SetColumn(column);
if (0 == x) {
// Since we collapsed into nothingness (all our whitespace is
// ignored) leave the skip-leading-whitespace flag alone so that
// whitespace that immediately follows this frame can be properly
// skipped.
}
else {
lineLayout.SetSkipLeadingWhiteSpace(endsInWhitespace);
}
// Setup metrics for caller; store final max-element-size information
aMetrics.width = x;
mComputedWidth = x;
if (0 == x) {
aMetrics.height = 0;
aMetrics.ascent = 0;
aMetrics.descent = 0;
}
else {
ts.mNormalFont->GetHeight(aMetrics.height);
ts.mNormalFont->GetMaxAscent(aMetrics.ascent);
ts.mNormalFont->GetMaxDescent(aMetrics.descent);
}
if (!wrapping) {
maxWordWidth = x;
}
if (nsnull != aMetrics.maxElementSize) {
aMetrics.maxElementSize->width = maxWordWidth;
aMetrics.maxElementSize->height = aMetrics.height;
}
if (nsnull != firstLineStyle) {
NS_RELEASE(firstLineStyle);
}
// Set content offset and length
mContentOffset = startingOffset;
mContentLength = offset - startingOffset;
nsReflowStatus rs = (offset == contentLength)
? NS_FRAME_COMPLETE
: NS_FRAME_NOT_COMPLETE;
if (endsInNewline) {
rs = NS_INLINE_LINE_BREAK_AFTER(rs);
}
else if (offset == startingOffset) {
rs = NS_INLINE_LINE_BREAK_BEFORE();
}
aStatus = rs;
NS_FRAME_TRACE(NS_FRAME_TRACE_CALLS,
("exit TextFrame::Reflow: status=%x width=%d",
aStatus, aMetrics.width));
return NS_OK;
}
NS_IMETHODIMP
TextFrame::AdjustFrameSize(nscoord aExtraSpace, nscoord& aUsedSpace)
{
// Get the text fragments that make up our content
nsTextFragment* frag;
PRInt32 numFrags;
nsITextContent* tc;
if (NS_OK == mContent->QueryInterface(kITextContentIID, (void**) &tc)) {
tc->GetText(frag, numFrags);
NS_RELEASE(tc);
// Find fragment that contains the end of the mapped content
PRInt32 endIndex = mContentOffset + mContentLength;
PRInt32 offset = 0;
nsTextFragment* lastFrag = frag + numFrags;
while (frag < lastFrag) {
PRInt32 fragLen = frag->GetLength();
if (endIndex <= offset + fragLen) {
offset = mContentOffset - offset;
if (frag->Is2b()) {
const PRUnichar* cp = frag->Get2b() + offset;
const PRUnichar* end = cp + mContentLength;
while (cp < end) {
PRUnichar ch = *cp++;
if (XP_IS_SPACE(ch)) {
aUsedSpace = aExtraSpace;
mRect.width += aExtraSpace;
return NS_OK;
}
}
}
else {
const unsigned char* cp =
((const unsigned char*)frag->Get1b()) + offset;
const unsigned char* end = cp + mContentLength;
while (cp < end) {
PRUnichar ch = PRUnichar(*cp++);
if (XP_IS_SPACE(ch)) {
aUsedSpace = aExtraSpace;
mRect.width += aExtraSpace;
return NS_OK;
}
}
}
break;
}
offset += fragLen;
frag++;
}
}
aUsedSpace = 0;
return NS_OK;
}
NS_IMETHODIMP
TextFrame::TrimTrailingWhiteSpace(nsIPresContext& aPresContext,
nsIRenderingContext& aRC,
nscoord& aDeltaWidth)
{
nscoord dw = 0;
const nsStyleText* textStyle = (const nsStyleText*)
mStyleContext->GetStyleData(eStyleStruct_Text);
if (NS_STYLE_WHITESPACE_PRE != textStyle->mWhiteSpace) {
// Get font metrics for a space so we can adjust the width by the
// right amount.
const nsStyleFont* fontStyle = (const nsStyleFont*)
mStyleContext->GetStyleData(eStyleStruct_Font);
nscoord spaceWidth;
aRC.SetFont(fontStyle->mFont);
aRC.GetWidth(' ', spaceWidth);
// Get the text fragments that make up our content
nsTextFragment* frag;
PRInt32 numFrags;
nsITextContent* tc;
if (NS_OK == mContent->QueryInterface(kITextContentIID, (void**) &tc)) {
tc->GetText(frag, numFrags);
NS_RELEASE(tc);
// Find fragment that contains the end of the mapped content
PRInt32 endIndex = mContentOffset + mContentLength;
PRInt32 offset = 0;
nsTextFragment* lastFrag = frag + numFrags;
while (frag < lastFrag) {
PRInt32 fragLen = frag->GetLength();
if (endIndex <= offset + fragLen) {
// Look inside the fragments last few characters and see if they
// are whitespace. If so, count how much width was supplied by
// them.
offset = mContentOffset - offset;
if (frag->Is2b()) {
// XXX If by chance the last content fragment is *all*
// whitespace then this won't back up far enough.
const PRUnichar* cp = frag->Get2b() + offset;
const PRUnichar* end = cp + mContentLength;
if (--end >= cp) {
PRUnichar ch = *end;
if (XP_IS_SPACE(ch)) {
dw = spaceWidth;
}
}
}
else {
const unsigned char* cp =
((const unsigned char*)frag->Get1b()) + offset;
const unsigned char* end = cp + mContentLength;
if (--end >= cp) {
PRUnichar ch = PRUnichar(*end);
if (XP_IS_SPACE(ch)) {
dw = spaceWidth;
}
}
}
break;
}
offset += fragLen;
frag++;
}
}
mRect.width -= dw;
mComputedWidth -= dw;
}
if (0 != dw) {
mFlags |= TEXT_TRIMMED_WS;
}
else {
mFlags &= ~TEXT_TRIMMED_WS;
}
aDeltaWidth = dw;
return NS_OK;
}
nscoord
TextFrame::ComputeTotalWordWidth(nsLineLayout& aLineLayout,
const nsHTMLReflowState& aReflowState,
nsIFrame* aNextFrame,
nscoord aBaseWidth)
{
nscoord addedWidth = 0;
while (nsnull != aNextFrame) {
nsIFrame* frame = aNextFrame;
while (nsnull != frame) {
nsIContent* content = nsnull;
if ((NS_OK == frame->GetContent(content)) && (nsnull != content)) {
#ifdef DEBUG_WORD_WRAPPING
printf(" next textRun=");
frame->ListTag(stdout);
printf("\n");
#endif
nsITextContent* tc;
if (NS_OK == content->QueryInterface(kITextContentIID, (void**)&tc)) {
PRBool stop;
nscoord moreWidth = ComputeWordFragmentWidth(aLineLayout,
aReflowState,
frame, tc,
stop);
NS_RELEASE(tc);
NS_RELEASE(content);
addedWidth += moreWidth;
#ifdef DEBUG_WORD_WRAPPING
printf(" moreWidth=%d (addedWidth=%d) stop=%c\n", moreWidth,
addedWidth, stop?'T':'F');
#endif
if (stop) {
goto done;
}
}
else {
// It claimed it was text but it doesn't implement the
// nsITextContent API. Therefore I don't know what to do with it
// and can't look inside it. Oh well.
NS_RELEASE(content);
goto done;
}
}
// XXX This assumes that a next-in-flow will follow it's
// prev-in-flow in the text-run. Maybe not a good assumption?
// What if the next-in-flow isn't actually part of the flow?
frame->GetNextInFlow(frame);
}
// Move on to the next frame in the text-run
aNextFrame = aLineLayout.FindNextText(aNextFrame);
}
done:;
#ifdef DEBUG_WORD_WRAPPING
printf(" total word width=%d\n", aBaseWidth + addedWidth);
#endif
return aBaseWidth + addedWidth;
}
nscoord
TextFrame::ComputeWordFragmentWidth(nsLineLayout& aLineLayout,
const nsHTMLReflowState& aReflowState,
nsIFrame* aTextFrame,
nsITextContent* aText,
PRBool& aStop)
{
nsTextRun* textRun = aLineLayout.FindTextRunFor(aTextFrame);
PRUnichar buf[TEXT_BUF_SIZE];
nsTextTransformer tx(buf, TEXT_BUF_SIZE);
// XXX we need the content-offset of the text frame!!! 0 won't
// always be right when continuations are in action
tx.Init(/**textRun, XXX*/ aTextFrame, 0);
PRBool isWhitespace;
PRInt32 wordLen, contentLen;
PRUnichar* bp = tx.GetNextWord(PR_TRUE, wordLen, contentLen, isWhitespace);
if ((nsnull == bp) || isWhitespace) {
// Don't bother measuring nothing
aStop = PR_TRUE;
return 0;
}
aStop = contentLen < tx.GetContentLength();
nsIStyleContext* sc;
if ((NS_OK == aTextFrame->GetStyleContext(&aLineLayout.mPresContext, sc)) &&
(nsnull != sc)) {
// Measure the piece of text. Note that we have to select the
// appropriate font into the text first because the rendering
// context has our font in it, not the font that aText is using.
nscoord width;
nsIRenderingContext& rc = *aReflowState.rendContext;
nsIFontMetrics* oldfm;
rc.GetFontMetrics(oldfm);
TextStyle ts(aLineLayout.mPresContext, rc, sc);
if (ts.mSmallCaps) {
MeasureSmallCapsText(aReflowState, ts, buf, wordLen, width);
}
else {
rc.GetWidth(buf, wordLen, width);
}
rc.SetFont(oldfm);
NS_RELEASE(sc);
#ifdef DEBUG_WORD_WRAPPING
nsAutoString tmp(bp, wordLen);
printf(" fragment='");
fputs(tmp, stdout);
printf("' width=%d [wordLen=%d contentLen=%d ContentLength=%d]\n",
width, wordLen, contentLen, tx.GetContentLength());
#endif
// Remember the text frame for later so that we don't bother doing
// the word look ahead.
aLineLayout.RecordWordFrame(aTextFrame);
return width;
}
aStop = PR_TRUE;
return 0;
}
// Translate the mapped content into a string that's printable
void
TextFrame::ToCString(nsString& aBuf, PRInt32& aContentLength) const
{
const nsTextFragment* frag;
PRInt32 numFrags;
// Get the frames text content
nsITextContent* tc;
if (NS_OK != mContent->QueryInterface(kITextContentIID, (void**) &tc)) {
return;
}
tc->GetText(frag, numFrags);
NS_RELEASE(tc);
// Compute the total length of the text content.
PRInt32 sum = 0;
PRInt32 i, n = numFrags;
for (i = 0; i < n; i++) {
sum += frag[i].GetLength();
}
aContentLength = sum;
// Set current fragment and current fragment offset
PRInt32 fragOffset = 0, offset = 0;
n = numFrags;
while (--n >= 0) {
if (mContentOffset < offset + frag->GetLength()) {
fragOffset = mContentOffset - offset;
break;
}
offset += frag->GetLength();
frag++;
}
if (0 == mContentLength) {
return;
}
n = mContentLength;
for (;;) {
PRUnichar ch = frag->CharAt(fragOffset);
if (ch == '\r') {
aBuf.Append("\\r");
} else if (ch == '\n') {
aBuf.Append("\\n");
} else if (ch == '\t') {
aBuf.Append("\\t");
} else if ((ch < ' ') || (ch >= 127)) {
aBuf.Append("\\0");
aBuf.Append((PRInt32)ch, 8);
} else {
aBuf.Append(ch);
}
if (--n == 0) {
break;
}
if (++fragOffset == frag->GetLength()) {
frag++;
fragOffset = 0;
}
}
}
NS_IMETHODIMP
TextFrame::ListTag(FILE* out) const
{
fprintf(out, "Text(%d)@%p", ContentIndexInContainer(this), this);
return NS_OK;
}
NS_IMETHODIMP
TextFrame::List(FILE* out, PRInt32 aIndent, nsIListFilter *aFilter) const
{
PRInt32 i;
for (i = aIndent; --i >= 0; ) fputs(" ", out);
// Output the tag
ListTag(out);
nsIView* view;
GetView(view);
if (nsnull != view) {
fprintf(out, " [view=%p]", view);
}
PRInt32 contentLength;
nsAutoString tmp;
ToCString(tmp, contentLength);
// Output the first/last content offset and prev/next in flow info
PRBool isComplete = (mContentOffset + mContentLength) == contentLength;
fprintf(out, "[%d,%d,%c] ",
mContentOffset, mContentOffset+mContentLength-1,
isComplete ? 'T':'F');
if (nsnull != mPrevInFlow) {
fprintf(out, "prev-in-flow=%p ", mPrevInFlow);
}
if (nsnull != mNextInFlow) {
fprintf(out, "next-in-flow=%p ", mNextInFlow);
}
// Output the rect and state
out << mRect;
if (0 != mState) {
fprintf(out, " [state=%08x]", mState);
}
// Output the text
fputs("<\n", out);
aIndent++;
for (i = aIndent; --i >= 0; ) fputs(" ", out);
fputs("\"", out);
fputs(tmp, out);
fputs("\"\n", out);
aIndent--;
for (i = aIndent; --i >= 0; ) fputs(" ", out);
fputs(">\n", out);
return NS_OK;
}