/* -*- 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 "nsCOMPtr.h" #include "nsHTMLParts.h" #include "nsCRT.h" #include "nsSplittableFrame.h" #include "nsLineLayout.h" #include "nsString.h" #include "nsIPresContext.h" #include "nsIContent.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 "nsIFocusTracker.h" #include "nsICaret.h" #include "nsXIFConverter.h" #include "nsHTMLAtoms.h" #include "nsILineBreaker.h" #include "nsIWordBreaker.h" #include "nsITextContent.h" #include "nsTextRun.h" #include "nsTextFragment.h" #include "nsTextTransformer.h" #include "nsLayoutAtoms.h" #include "nsIFrameSelection.h" #include "nsIDOMSelection.h" #include "nsIDOMRange.h" #include "nsILineIterator.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 // Helper class for managing blinking text class nsBlinkTimer : public nsITimerCallback { public: nsBlinkTimer(); virtual ~nsBlinkTimer(); NS_DECL_ISUPPORTS void AddFrame(nsIFrame* aFrame); PRBool RemoveFrame(nsIFrame* aFrame); PRInt32 FrameCount(); void Start(); void Stop(); virtual void Notify(nsITimer *timer); nsITimer* mTimer; nsVoidArray mFrames; }; static PRBool gBlinkTextOff; static nsBlinkTimer* gTextBlinker; #ifdef NOISY_BLINK static PRTime gLastTick; #endif nsBlinkTimer::nsBlinkTimer() { NS_INIT_REFCNT(); mTimer = nsnull; } nsBlinkTimer::~nsBlinkTimer() { Stop(); } void nsBlinkTimer::Start() { nsresult rv = NS_NewTimer(&mTimer); if (NS_OK == rv) { mTimer->Init(this, 750); } } void nsBlinkTimer::Stop() { if (nsnull != mTimer) { mTimer->Cancel(); NS_RELEASE(mTimer); } } static NS_DEFINE_IID(kITimerCallbackIID, NS_ITIMERCALLBACK_IID); NS_IMPL_ISUPPORTS(nsBlinkTimer, kITimerCallbackIID); void nsBlinkTimer::AddFrame(nsIFrame* aFrame) { mFrames.AppendElement(aFrame); if (1 == mFrames.Count()) { Start(); } } PRBool nsBlinkTimer::RemoveFrame(nsIFrame* aFrame) { PRBool rv = mFrames.RemoveElement(aFrame); if (0 == mFrames.Count()) { Stop(); } return rv; } PRInt32 nsBlinkTimer::FrameCount() { return mFrames.Count(); } void nsBlinkTimer::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++) { nsIFrame* text = (nsIFrame*) 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); } } //---------------------------------------------------------------------- class nsTextFrame : public nsSplittableFrame { public: nsTextFrame(); // nsIFrame NS_IMETHOD Paint(nsIPresContext& aPresContext, nsIRenderingContext& aRenderingContext, const nsRect& aDirtyRect, nsFramePaintLayer aWhichLayer); NS_IMETHOD GetCursor(nsIPresContext& aPresContext, nsPoint& aPoint, PRInt32& aCursor); NS_IMETHOD ContentChanged(nsIPresContext* aPresContext, nsIContent* aChild, nsISupports* aSubContent); NS_IMETHOD List(FILE* out, PRInt32 aIndent) const; /** * Get the "type" of the frame * * @see nsLayoutAtoms::textFrame */ NS_IMETHOD GetFrameType(nsIAtom** aType) const; NS_IMETHOD GetFrameName(nsString& aResult) const; NS_IMETHOD GetPosition(nsIPresContext& aCX, nscoord aXCoord, nsIContent ** aNewContent, PRInt32& aContentOffset, PRInt32& aContentOffsetEnd); NS_IMETHOD GetPositionSlowly(nsIPresContext& aCX, nsIRenderingContext * aRendContext, nscoord aXCoord, nsIContent ** aNewContent, PRInt32& aOffset); NS_IMETHOD SetSelected(nsIDOMRange *aRange,PRBool aSelected, nsSpread aSpread); NS_IMETHOD PeekOffset(nsIFocusTracker *aTracker, nscoord aDesiredX, nsSelectionAmount aAmount, nsDirection aDirection, PRInt32 aStartOffset, nsIContent **aResultContent, PRInt32 *aContentOffset, PRBool aEatingWS); NS_IMETHOD HandleMultiplePress(nsIPresContext& aPresContext, nsGUIEvent * aEvent, nsEventStatus& aEventStatus); NS_IMETHOD GetOffsets(PRInt32 &start, PRInt32 &end)const; NS_IMETHOD GetPointFromOffset(nsIPresContext* inPresContext, nsIRenderingContext* inRendContext, PRInt32 inOffset, nsPoint* outPoint); NS_IMETHOD GetChildFrameContainingOffset(PRInt32 inContentOffset, PRInt32* outFrameContentOffset, nsIFrame* *outChildFrame); // 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); 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; PRBool mPreformatted; 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); // Cache the original decorations and reuse the current font // to query metrics, rather than creating a new font which is expensive. nsFont* plainFont = (nsFont *)&mFont->mFont; //XXX: Change to use a CONST_CAST macro. PRUint8 originalDecorations = plainFont->decorations; plainFont->decorations = NS_FONT_DECORATION_NONE; aPresContext->GetMetricsFor(*plainFont, &mNormalFont); 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) { nscoord originalSize = plainFont->size; plainFont->size = nscoord(0.7 * plainFont->size); aPresContext->GetMetricsFor(*plainFont, &mSmallFont); // Reset to the size value saved earlier. plainFont->size = originalSize; } else { mSmallFont = nsnull; } // Reset to the decoration saved earlier plainFont->decorations = originalDecorations; // 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; 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; mPreformatted = (NS_STYLE_WHITESPACE_PRE == mText->mWhiteSpace) || (NS_STYLE_WHITESPACE_MOZ_PRE_WRAP == mText->mWhiteSpace); } ~TextStyle() { NS_RELEASE(mNormalFont); NS_IF_RELEASE(mSmallFont); } }; nsIDocument* GetDocument(nsIPresContext* aPresContext); PRIntn PrepareUnicodeText(nsTextTransformer& aTransformer, PRInt32* aIndicies, PRUnichar* aBuffer, PRInt32* aTextLen); void PaintTextDecorations(nsIRenderingContext& aRenderingContext, nsIStyleContext* aStyleContext, TextStyle& aStyle, nscoord aX, nscoord aY, nscoord aWidth, PRUnichar* aText = nsnull, SelectionDetails *aDetails = nsnull, PRUint32 aIndex = 0, PRUint32 aLength = 0, const nscoord* aSpacing = nsnull); 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, SelectionDetails *aDetails = nsnull); 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(nsIPresContext* aPresContext, nsLineLayout& aLineLayout, const nsHTMLReflowState& aReflowState, nsIFrame* aNextFrame, nscoord aBaseWidth); nscoord ComputeWordFragmentWidth(nsIPresContext* aPresContext, nsLineLayout& aLineLayout, const nsHTMLReflowState& aReflowState, nsIFrame* aNextFrame, nsITextContent* aText, PRBool* aStop); void ToCString(nsString& aBuf, PRInt32* aContentLength) const; protected: virtual ~nsTextFrame(); // 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_TRIMMED_WS 0x10 // This bit is set on the first frame in a continuation indicating // that it was choppsed short because of :first-letter style. #define TEXT_FIRST_LETTER 0x20 //---------------------------------------------------------------------- nsresult NS_NewTextFrame(nsIFrame** aNewFrame) { NS_PRECONDITION(aNewFrame, "null OUT ptr"); if (nsnull == aNewFrame) { return NS_ERROR_NULL_POINTER; } nsTextFrame* it = new nsTextFrame; if (nsnull == it) { return NS_ERROR_OUT_OF_MEMORY; } *aNewFrame = it; return NS_OK; } nsTextFrame::nsTextFrame() : nsSplittableFrame() { if (nsnull == gTextBlinker) { // Create text timer the first time out gTextBlinker = new nsBlinkTimer(); } NS_ADDREF(gTextBlinker); } nsTextFrame::~nsTextFrame() { 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; } } nsIDocument* nsTextFrame::GetDocument(nsIPresContext* aPresContext) { nsIDocument* result = nsnull; if (mContent) { mContent->GetDocument(result); } if (!result && aPresContext) { nsIPresShell* shell; aPresContext->GetShell(&shell); if (shell) { shell->GetDocument(&result); NS_RELEASE(shell); } } return result; } NS_IMETHODIMP nsTextFrame::GetCursor(nsIPresContext& aPresContext, nsPoint& aPoint, PRInt32& aCursor) { const nsStyleColor* styleColor; GetStyleData(eStyleStruct_Color, (const nsStyleStruct*&)styleColor); aCursor = styleColor->mCursor; if (NS_STYLE_CURSOR_AUTO == aCursor && nsnull != mParent) { mParent->GetCursor(aPresContext, aPoint, aCursor); if (NS_STYLE_CURSOR_AUTO == aCursor) { aCursor = NS_STYLE_CURSOR_TEXT; } } return NS_OK; } NS_IMETHODIMP nsTextFrame::ContentChanged(nsIPresContext* aPresContext, nsIContent* aChild, nsISupports* aSubContent) { // Generate a reflow command with this frame as the target frame nsIReflowCommand* cmd; nsresult rv; rv = NS_NewHTMLReflowCommand(&cmd, this, nsIReflowCommand::ContentChanged); if (NS_SUCCEEDED(rv)) { nsCOMPtr shell; rv = aPresContext->GetShell(getter_AddRefs(shell)); if (NS_SUCCEEDED(rv) && shell) { shell->AppendReflowCommand(cmd); NS_RELEASE(cmd); } } return rv; } NS_IMETHODIMP nsTextFrame::Paint(nsIPresContext& aPresContext, nsIRenderingContext& aRenderingContext, const nsRect& aDirtyRect, nsFramePaintLayer aWhichLayer) { if (NS_FRAME_PAINT_LAYER_FOREGROUND != aWhichLayer) { return NS_OK; } if ((0 != (mFlags & TEXT_BLINK_ON)) && gBlinkTextOff) { return NS_OK; } nsIStyleContext* sc = mStyleContext; const nsStyleDisplay* disp = (const nsStyleDisplay*) sc->GetStyleData(eStyleStruct_Display); if (disp->mVisible) { TextStyle ts(&aPresContext, aRenderingContext, mStyleContext); 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); } } } return NS_OK; } /** * 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 nsTextFrame::PrepareUnicodeText(nsTextTransformer& aTX, PRInt32* aIndexes, PRUnichar* aBuffer, PRInt32* aTextLen) { PRIntn numSpaces = 0; PRUnichar* dst = aBuffer; // Setup transform to operate starting in the content at our content // offset aTX.Init(this, mContentOffset); 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; } if (contentLen > n) { contentLen = n; } if (wordLen > n) { wordLen = n; } 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 += wordLen; } } else if (0 == wordLen) { if (nsnull != aIndexes) *aIndexes++ = strInx; 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; } // 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; } //#define SHOW_SELECTION_CURSOR // should be turned off when the caret code is activated #ifdef SHOW_SELECTION_CURSOR // 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); } #endif // XXX letter-spacing // XXX word-spacing void nsTextFrame::PaintTextDecorations(nsIRenderingContext& aRenderingContext, nsIStyleContext* aStyleContext, TextStyle& aTextStyle, nscoord aX, nscoord aY, nscoord aWidth, PRUnichar *aText, /*=nsnull*/ SelectionDetails *aDetails,/*= nsnull*/ PRUint32 aIndex, /*= 0*/ PRUint32 aLength, /*= 0*/ const nscoord* aSpacing /* = nsnull*/ ) { 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); } if (aDetails){ nsRect rect; GetRect(rect); PRInt32 startOffset = 0; PRInt32 textWidth = 0; while(aDetails){ PRInt32 start = PR_MAX(0,(aDetails->mStart - (PRInt32)aIndex)); PRInt32 end = PR_MIN((PRInt32)aLength,(aDetails->mEnd - (PRInt32)aIndex)); PRInt32 i; if (start < end && (aLength - start) > 0) { //aDetails allready processed to have offsets from frame start not content offsets if (start < end){ if (aLength == 1) textWidth = aWidth; else { if (aDetails->mStart > 0){ if (aSpacing) { for (i = 0; i < start;i ++){ startOffset += *aSpacing ++; } } else aRenderingContext.GetWidth(aText, start, startOffset); } if (aSpacing){ for (i = start; i < end;i ++){ textWidth += *aSpacing ++; } } else aRenderingContext.GetWidth(aText + start, PRUint32(end - start), textWidth); } switch (aDetails->mType) { case SELECTION_NORMAL:{ // // XOR InvertRect is currently implemented only in the unix and windows // rendering contexts. When other platforms implement InvertRect(), they // can be added here. Eventually this #ifdef should die. // // For platforms that dont implement InvertRect(), the selection will be // a non-filled rectangle. #if defined(XP_PC) || defined(XP_UNIX) || defined(XP_MAC) aRenderingContext.SetColor(NS_RGB(255,255,255)); aRenderingContext.InvertRect(aX + startOffset, aY, textWidth, rect.height); #else aRenderingContext.SetColor(NS_RGB(0,0,0)); aRenderingContext.DrawRect(aX + startOffset, aY, textWidth, rect.height); #endif }break; case SELECTION_SPELLCHECK:{ aTextStyle.mNormalFont->GetUnderline(offset, size); aRenderingContext.SetColor(NS_RGB(255,0,0)); aRenderingContext.FillRect(aX, aY + baseline - offset, aWidth, size); }break; case SELECTION_IME_SOLID:{ aTextStyle.mNormalFont->GetUnderline(offset, size); aRenderingContext.SetColor(NS_RGB(0,0,255)); aRenderingContext.FillRect(aX, aY + baseline - offset, aWidth, size); }break; case SELECTION_IME_DASHED:{ aTextStyle.mNormalFont->GetUnderline(offset, size); aRenderingContext.SetColor(NS_RGB(128,0,255)); aRenderingContext.FillRect(aX, aY + baseline - offset, aWidth, size); }break; } } } aDetails = aDetails->mNext; } } } void nsTextFrame::PaintUnicodeText(nsIPresContext* aPresContext, nsIRenderingContext& aRenderingContext, nsIStyleContext* aStyleContext, TextStyle& aTextStyle, nscoord dx, nscoord dy) { nsCOMPtr doc(getter_AddRefs(GetDocument(aPresContext))); 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+1]; paintBuf = new PRUnichar[mContentLength]; } nscoord width = mRect.width; PRInt32 textLength; // Transform text from content into renderable form nsCOMPtr lb; doc->GetLineBreaker(getter_AddRefs(lb)); nsCOMPtr wb; doc->GetWordBreaker(getter_AddRefs(wb)); nsTextTransformer tx(wordBufMem, WORD_BUF_SIZE,lb,wb); PrepareUnicodeText(tx, (displaySelection ? ip : nsnull), paintBuf, &textLength); PRUnichar* text = paintBuf; nsFrameState frameState; PRBool isSelected; GetFrameState(&frameState); isSelected = (frameState & NS_FRAME_SELECTED_CONTENT) == NS_FRAME_SELECTED_CONTENT; if (0 != textLength) { if (!displaySelection || !isSelected ) { // When there is no selection showing, use the fastest and // simplest rendering approach aRenderingContext.SetColor(aTextStyle.mColor->mColor); aRenderingContext.DrawString(text, PRUint32(textLength), dx, dy); PaintTextDecorations(aRenderingContext, aStyleContext, aTextStyle, dx, dy, width); } else { ip[mContentLength] = ip[mContentLength-1]; if ((ip[mContentLength]-mContentOffset) < textLength) { //must set up last one for selection beyond edge if in boundary ip[mContentLength]++; } SelectionDetails *details = nsnull; nsCOMPtr shell; nsCOMPtr frameSelection; PRBool drawSelected = PR_FALSE; nsresult rv = aPresContext->GetShell(getter_AddRefs(shell)); if (NS_SUCCEEDED(rv) && shell){ rv = shell->GetFrameSelection(getter_AddRefs(frameSelection)); if (NS_SUCCEEDED(rv) && frameSelection){ nsCOMPtr content; rv = GetContent(getter_AddRefs(content)); if (NS_SUCCEEDED(rv) && content){ rv = frameSelection->LookUpSelection(content, mContentOffset, mContentLength , &details);// last param notused } } } //where are the selection points "really" SelectionDetails *sdptr = details; while (sdptr){ sdptr->mStart = ip[sdptr->mStart] - mContentOffset; sdptr->mEnd = ip[sdptr->mEnd] - mContentOffset; sdptr = sdptr->mNext; } aRenderingContext.SetColor(aTextStyle.mColor->mColor); aRenderingContext.DrawString(text, PRUint32(textLength), dx, dy); PaintTextDecorations(aRenderingContext, aStyleContext, aTextStyle, dx, dy, width, text, details,0,(PRUint32)textLength); sdptr = details; if (details){ while(sdptr = details->mNext){ delete details; details = sdptr; } delete details; } } } // Cleanup if (paintBuf != paintBufMem) { delete [] paintBuf; } if (ip != indicies) { delete [] ip; } } //measure Spaced Textvoid nsresult nsTextFrame::GetPositionSlowly(nsIPresContext& aPresContext, nsIRenderingContext* aRendContext, nscoord aXCoord, nsIContent** aNewContent, PRInt32& aOffset) { if (!aRendContext || !aNewContent) { return NS_ERROR_NULL_POINTER; } TextStyle ts(&aPresContext, *aRendContext, mStyleContext); if (!ts.mSmallCaps && !ts.mWordSpacing && !ts.mLetterSpacing) { return NS_ERROR_INVALID_ARG; } nsCOMPtr doc(getter_AddRefs(GetDocument(&aPresContext))); // Make enough space to transform PRUnichar paintBufMem[TEXT_BUF_SIZE]; PRInt32 indicies[TEXT_BUF_SIZE]; PRUnichar wordBufMem[WORD_BUF_SIZE]; PRInt32* ip = indicies; PRUnichar* paintBuf = paintBufMem; if (mContentLength > TEXT_BUF_SIZE) { ip = new PRInt32[mContentLength+1]; paintBuf = new PRUnichar[mContentLength]; } PRInt32 textLength; // Transform text from content into renderable form nsCOMPtr lb; doc->GetLineBreaker(getter_AddRefs(lb)); nsCOMPtr wb; doc->GetWordBreaker(getter_AddRefs(wb)); nsTextTransformer tx(wordBufMem, WORD_BUF_SIZE, lb,wb); PrepareUnicodeText(tx, ip, paintBuf, &textLength); if (textLength <= 0) return NS_ERROR_FAILURE; nsPoint origin; nsIView * view; GetView(&view); GetOffsetFromView(origin, &view); nscoord charWidth,widthsofar = 0; PRInt32 index = 0; PRBool found = PR_FALSE; PRUnichar* startBuf = paintBuf; nsIFontMetrics* lastFont = ts.mLastFont; for (; --textLength >= 0; paintBuf++,index++) { nsIFontMetrics* nextFont; nscoord glyphWidth; PRUnichar ch = *paintBuf; if (ts.mSmallCaps && nsCRT::IsLower(ch)) { nextFont = ts.mSmallFont; ch = nsCRT::ToUpper(ch); if (lastFont != ts.mSmallFont) { aRendContext->SetFont(ts.mSmallFont); aRendContext->GetWidth(ch, charWidth); aRendContext->SetFont(ts.mNormalFont); } else { aRendContext->GetWidth(ch, charWidth); } glyphWidth = charWidth + ts.mLetterSpacing; } else if (ch == ' ') { nextFont = lastFont; glyphWidth = ts.mSpaceWidth + ts.mWordSpacing; nscoord extra = ts.mExtraSpacePerSpace; if (--ts.mNumSpaces == 0) { extra += ts.mRemainingExtraSpace; } glyphWidth += extra; } else { nextFont = lastFont; aRendContext->GetWidth(ch, charWidth); glyphWidth = charWidth + ts.mLetterSpacing; } if ((aXCoord - origin.x) >= widthsofar && (aXCoord - origin.x) <= (widthsofar + glyphWidth)){ if ( ((aXCoord - origin.x) - widthsofar) <= (glyphWidth /2)){ aOffset = index; found = PR_TRUE; break; } else{ aOffset = index+1; found = PR_TRUE; break; } } if (nextFont != lastFont) lastFont = nextFont; widthsofar += glyphWidth; } paintBuf = startBuf; if (!found){ aOffset = textLength; } aOffset += mContentOffset;//offset;//((nsTextFrame *)aNewFrame)->mContentOffset; PRInt32 i; for (i = 0;i <= mContentLength; i ++){ if (ip[i] == aOffset){ //reverse mapping aOffset = i + mContentOffset; break; } } // Cleanup if (paintBuf != paintBufMem) { delete [] paintBuf; } if (ip != indicies) { delete [] ip; } *aNewContent = mContent; if (*aNewContent) (*aNewContent)->AddRef(); return NS_OK; } void nsTextFrame::RenderString(nsIRenderingContext& aRenderingContext, nsIStyleContext* aStyleContext, TextStyle& aTextStyle, PRUnichar* aBuffer, PRInt32 aLength, nscoord aX, nscoord aY, nscoord aWidth, SelectionDetails *aDetails /*=nsnull*/) { 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; PRInt32 countSoFar = 0; for (; --aLength >= 0; aBuffer++) { nsIFontMetrics* nextFont; nscoord nextY, glyphWidth; PRUnichar ch = *aBuffer; if (aTextStyle.mSmallCaps && nsCRT::IsLower(ch)) { nextFont = aTextStyle.mSmallFont; nextY = smallY; ch = nsCRT::ToUpper(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.SetColor(aTextStyle.mColor->mColor); aRenderingContext.DrawString(runStart, pendingCount, aX, lastY, -1, 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, runStart, aDetails,countSoFar,pendingCount, spacing ? sp0 : nsnull); countSoFar += pendingCount; 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.SetColor(aTextStyle.mColor->mColor); aRenderingContext.DrawString(runStart, pendingCount, aX, lastY, -1, 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, runStart, aDetails,countSoFar,pendingCount, spacing ? sp0 : nsnull); } aTextStyle.mLastFont = lastFont; if (bp0 != buf) { delete [] bp0; } if (sp0 != spacingMem) { delete [] sp0; } } inline void nsTextFrame::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 nsTextFrame::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 sum = 0; nscoord charWidth; for (; --aLength >= 0; aBuffer++) { nscoord glyphWidth; PRUnichar ch = *aBuffer; if (aTextStyle.mSmallCaps && nsCRT::IsLower(ch)) { ch = nsCRT::ToUpper(ch); if (lastFont != aTextStyle.mSmallFont) { lastFont = aTextStyle.mSmallFont; aRenderingContext.SetFont(lastFont); } aRenderingContext.GetWidth(ch, charWidth); glyphWidth = charWidth + aTextStyle.mLetterSpacing; } else if (ch == ' ') { glyphWidth = aTextStyle.mSpaceWidth + aTextStyle.mWordSpacing; nscoord extra = aTextStyle.mExtraSpacePerSpace; if (--aTextStyle.mNumSpaces == 0) { extra += aTextStyle.mRemainingExtraSpace; } glyphWidth += extra; } else { if (lastFont != aTextStyle.mNormalFont) { lastFont = aTextStyle.mNormalFont; aRenderingContext.SetFont(lastFont); } aRenderingContext.GetWidth(ch, charWidth); glyphWidth = charWidth + aTextStyle.mLetterSpacing; } sum += glyphWidth; *bp++ = ch; } if (bp0 != buf) { delete [] bp0; } aTextStyle.mLastFont = lastFont; *aWidthResult = sum; } void nsTextFrame::PaintTextSlowly(nsIPresContext* aPresContext, nsIRenderingContext& aRenderingContext, nsIStyleContext* aStyleContext, TextStyle& aTextStyle, nscoord dx, nscoord dy) { nsCOMPtr doc(getter_AddRefs(GetDocument(aPresContext))); 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+1]; paintBuf = new PRUnichar[mContentLength]; } nscoord width = mRect.width; PRInt32 textLength; // Transform text from content into renderable form nsCOMPtr lb; doc->GetLineBreaker(getter_AddRefs(lb)); nsCOMPtr wb; doc->GetWordBreaker(getter_AddRefs(wb)); nsTextTransformer tx(wordBufMem, WORD_BUF_SIZE, lb,wb); aTextStyle.mNumSpaces = PrepareUnicodeText(tx, displaySelection ? ip : nsnull, paintBuf, &textLength); if (mRect.width > mComputedWidth) { if (0 != aTextStyle.mNumSpaces) { nscoord extra = mRect.width - mComputedWidth; #if XXX nscoord adjustPerSpace = aTextStyle.mExtraSpacePerSpace = extra / aTextStyle.mNumSpaces; #endif 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; nsFrameState frameState; PRBool isSelected; GetFrameState(&frameState); isSelected = (frameState & NS_FRAME_SELECTED_CONTENT) == NS_FRAME_SELECTED_CONTENT; if (0 != textLength) { if (!displaySelection || !isSelected) { // When there is no selection showing, use the fastest and // simplest rendering approach RenderString(aRenderingContext, aStyleContext, aTextStyle, text, textLength, dx, dy, width); } else { SelectionDetails *details = nsnull; ip[mContentLength] = ip[mContentLength-1]; if ((ip[mContentLength]-mContentOffset) < textLength) { //must set up last one for selection beyond edge if in boundary ip[mContentLength]++; } PRInt32 selectionStartOffset = 0;//frame coordinates PRInt32 selectionEndOffset = 0;//frame coordinates nsCOMPtr shell; nsCOMPtr frameSelection; PRBool drawSelected = PR_FALSE; nsresult rv = aPresContext->GetShell(getter_AddRefs(shell)); if (NS_SUCCEEDED(rv) && shell){ rv = shell->GetFrameSelection(getter_AddRefs(frameSelection)); if (NS_SUCCEEDED(rv) && frameSelection){ nsCOMPtr content; rv = GetContent(getter_AddRefs(content)); if (NS_SUCCEEDED(rv)){ rv = frameSelection->LookUpSelection(content, mContentOffset, mContentLength , &details);// last param notused } } } //where are the selection points "really" SelectionDetails *sdptr = details; while (sdptr){ sdptr->mStart = ip[sdptr->mStart] - mContentOffset; sdptr->mEnd = ip[sdptr->mEnd] - mContentOffset; sdptr = sdptr->mNext; } aRenderingContext.SetColor(aTextStyle.mColor->mColor); RenderString(aRenderingContext,aStyleContext, aTextStyle, text, PRUint32(textLength), dx, dy, width, details); sdptr = details; if (details){ while(sdptr = details->mNext){ delete details; details = sdptr; } delete details; } } } // Cleanup if (paintBuf != paintBufMem) { delete [] paintBuf; } if (ip != indicies) { delete [] ip; } } void nsTextFrame::PaintAsciiText(nsIPresContext* aPresContext, nsIRenderingContext& aRenderingContext, nsIStyleContext* aStyleContext, TextStyle& aTextStyle, nscoord dx, nscoord dy) { nsCOMPtr doc(getter_AddRefs(GetDocument(aPresContext))); 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 nsCOMPtr lb; doc->GetLineBreaker(getter_AddRefs(lb)); nsCOMPtr wb; doc->GetWordBreaker(getter_AddRefs(wb)); nsTextTransformer tx(wordBufMem, WORD_BUF_SIZE,lb,wb); PrepareUnicodeText(tx, (displaySelection ? ip : nsnull), rawPaintBuf, &textLength); // 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; nsFrameState frameState; PRBool isSelected; GetFrameState(&frameState); isSelected = (frameState & NS_FRAME_SELECTED_CONTENT) == NS_FRAME_SELECTED_CONTENT; if (0 != textLength) { if (!displaySelection || !isSelected) { //if selection is > content length then selection has "slid off" // When there is no selection showing, use the fastest and // simplest rendering approach aRenderingContext.SetColor(aTextStyle.mColor->mColor); aRenderingContext.DrawString(text, PRUint32(textLength), dx, dy); PaintTextDecorations(aRenderingContext, aStyleContext, aTextStyle, dx, dy, width); } else { SelectionDetails *details; ip[mContentLength] = ip[mContentLength-1]; if ((ip[mContentLength]-mContentOffset) < textLength) { //must set up last one for selection beyond edge if in boundary ip[mContentLength]++; } PRInt32 selectionStartOffset = 0;//frame coordinates PRInt32 selectionEndOffset = 0;//frame coordinates nsCOMPtr shell; nsCOMPtr frameSelection; PRBool drawSelected = PR_FALSE; nsresult rv = aPresContext->GetShell(getter_AddRefs(shell)); if (NS_SUCCEEDED(rv) && shell){ rv = shell->GetFrameSelection(getter_AddRefs(frameSelection)); if (NS_SUCCEEDED(rv) && frameSelection){ nsCOMPtr content; rv = GetContent(getter_AddRefs(content)); if (NS_SUCCEEDED(rv)){ rv = frameSelection->LookUpSelection(content, mContentOffset, mContentLength , &details);// last param notused } } } //where are the selection points "really" SelectionDetails *sdptr = details; while (sdptr){ sdptr->mStart = ip[sdptr->mStart] - mContentOffset; sdptr->mEnd = ip[sdptr->mEnd] - mContentOffset; sdptr = sdptr->mNext; } aRenderingContext.SetColor(aTextStyle.mColor->mColor); aRenderingContext.DrawString(text, PRUint32(textLength), dx, dy); PaintTextDecorations(aRenderingContext, aStyleContext, aTextStyle, dx, dy, width, rawPaintBuf, details,0,textLength); sdptr = details; if (details){ while(sdptr = details->mNext){ delete details; details = sdptr; } delete details; } } } // Cleanup if (paintBuf != paintBufMem) { delete [] paintBuf; } if (ip != indicies) { delete [] ip; } } NS_IMETHODIMP nsTextFrame::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; acx->GetWidth(aText, aIndex, aTextWidth); return PR_TRUE; } PRInt32 inx = aStartInx + (range / 2); PRInt32 textWidth = 0; 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; if (BinarySearchForPosition(acx, aText, aBaseWidth, aBaseInx, inx, aEndInx, 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 nsTextFrame::GetPosition(nsIPresContext& aCX, nscoord aXCoord, nsIContent ** aNewContent, PRInt32& aContentOffset, PRInt32& aContentOffsetEnd) { nsCOMPtr shell; nsresult rv = aCX.GetShell(getter_AddRefs(shell)); if (NS_SUCCEEDED(rv) && shell) { nsCOMPtr acx; rv = shell->CreateRenderingContext(this, getter_AddRefs(acx)); if (NS_SUCCEEDED(rv)) { TextStyle ts(&aCX, *acx, mStyleContext); if (ts.mSmallCaps || ts.mWordSpacing || ts.mLetterSpacing) { nsresult result = GetPositionSlowly(aCX, acx, aXCoord, aNewContent, aContentOffset); aContentOffsetEnd = aContentOffset; return result; } 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]; } PRInt32 textLength; // Find the font metrics for this text nsIStyleContext* styleContext; GetStyleContext(&styleContext); const nsStyleFont *font = (const nsStyleFont*) styleContext->GetStyleData(eStyleStruct_Font); NS_RELEASE(styleContext); nsCOMPtr fm; aCX.GetMetricsFor(font->mFont, getter_AddRefs(fm)); acx->SetFont(fm); // Get the document nsCOMPtr doc(getter_AddRefs(GetDocument(&aCX))); // Get the renderable form of the text nsCOMPtr lb; doc->GetLineBreaker(getter_AddRefs(lb)); nsCOMPtr wb; doc->GetWordBreaker(getter_AddRefs(wb)); nsTextTransformer tx(wordBufMem, WORD_BUF_SIZE,lb,wb); PrepareUnicodeText(tx, ip, paintBuf, &textLength); if (textLength <=0) //invalid frame to get position on return NS_ERROR_FAILURE; ip[mContentLength] = ip[mContentLength-1]; if ((ip[mContentLength]-mContentOffset) < textLength) { //must set up last one for selection beyond edge if in boundary ip[mContentLength]++; } PRInt32 index; PRInt32 textWidth = 0; PRUnichar* text = paintBuf; nsPoint origin; nsIView * view; GetView(&view); GetOffsetFromView(origin, &view); PRBool found = BinarySearchForPosition(acx, text, origin.x, 0, 0, PRInt32(textLength), PRInt32(aXCoord) , //go to local coordinates index, textWidth); if (found) { PRInt32 charWidth; acx->GetWidth(text[index], charWidth); charWidth /= 2; if (PRInt32(aXCoord) - origin.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++; } */ } if (ip != indicies) { delete [] ip; } if (paintBuf != paintBufMem) { delete [] paintBuf; } aContentOffset = index + mContentOffset; //reusing wordBufMem PRInt32 i; for (i = 0;i <= mContentLength; i ++){ if (ip[i] == aContentOffset){ //reverse mapping aContentOffset = i + mContentOffset; break; } } aContentOffsetEnd = aContentOffset; NS_ASSERTION(i<= mContentLength, "offset we got from binary search is messed up"); *aNewContent = mContent; if (*aNewContent) { (*aNewContent)->AddRef(); } } } return NS_OK; } // [HACK] Foward Declarations void ForceDrawFrame(nsFrame * aFrame); //null range means the whole thing NS_IMETHODIMP nsTextFrame::SetSelected(nsIDOMRange *aRange,PRBool aSelected, nsSpread aSpread) { nsresult result; if (aSelected && ParentDisablesSelection()) return NS_OK; if (aSpread == eSpreadDown) { nsIFrame *frame = GetPrevInFlow(); while(frame){ frame->SetSelected(aRange,aSelected,eSpreadNone); result = frame->GetPrevInFlow(&frame); if (NS_FAILED(result)) break; } frame = GetNextInFlow(); while(frame){ frame->SetSelected(aRange,aSelected,eSpreadNone); result = frame->GetNextInFlow(&frame); if (NS_FAILED(result)) break; } } nsFrameState frameState; GetFrameState(&frameState); PRBool isSelected = ((frameState & NS_FRAME_SELECTED_CONTENT) == NS_FRAME_SELECTED_CONTENT); if (!aSelected && !isSelected) //already set thanks { return NS_OK; } PRBool found = PR_FALSE; if (aRange) { //lets see if the range contains us, if so we must redraw! nsCOMPtr endNode; PRInt32 endOffset; nsCOMPtr startNode; PRInt32 startOffset; aRange->GetEndParent(getter_AddRefs(endNode)); aRange->GetEndOffset(&endOffset); aRange->GetStartParent(getter_AddRefs(startNode)); aRange->GetStartOffset(&startOffset); nsCOMPtr content; result = GetContent(getter_AddRefs(content)); nsCOMPtr thisNode; thisNode = do_QueryInterface(content); if (thisNode == startNode){ if ((mContentOffset + mContentLength) >= startOffset){ found = PR_TRUE; if (thisNode == endNode){ //special case /*#ifndef SHOW_SELECTION_CURSOR if (aSelected && (endOffset == startOffset)) //no need to redraw since drawing takes place with cursor found = PR_FALSE; #endif */ if (mContentOffset > endOffset) found = PR_FALSE; } } } else if (thisNode == endNode){ if (mContentOffset < endOffset) found = PR_TRUE; else found = PR_FALSE; } else found = PR_TRUE; } else { if ( aSelected != (PRBool)(frameState | NS_FRAME_SELECTED_CONTENT) ){ found = PR_TRUE; } } if ( aSelected ) frameState |= NS_FRAME_SELECTED_CONTENT; else frameState &= ~NS_FRAME_SELECTED_CONTENT; SetFrameState(frameState); if (found){ //if range contains this frame... nsRect frameRect; GetRect(frameRect); nsRect rect(0, 0, frameRect.width, frameRect.height); Invalidate(rect, PR_FALSE); // ForceDrawFrame(this); } return NS_OK; } NS_IMETHODIMP nsTextFrame::GetPointFromOffset(nsIPresContext* aPresContext, nsIRenderingContext* inRendContext, PRInt32 inOffset, nsPoint* outPoint) { if (!aPresContext || !inRendContext || !outPoint) return NS_ERROR_NULL_POINTER; if (mContentLength <= 0) { outPoint->x = 0; outPoint->y = 0; return NS_OK; } inOffset-=mContentOffset; if (inOffset < 0){ NS_ASSERTION(0,"offset less than this frame has in GetPointFromOffset"); inOffset = 0; } TextStyle ts(aPresContext, *inRendContext, mStyleContext); PRUnichar wordBufMem[WORD_BUF_SIZE]; PRUnichar paintBufMem[TEXT_BUF_SIZE]; PRInt32 indicies[TEXT_BUF_SIZE]; PRUnichar* paintBuf = paintBufMem; PRInt32* ip = indicies; if (mContentLength > TEXT_BUF_SIZE) { ip = new PRInt32[mContentLength+1]; paintBuf = new PRUnichar[mContentLength]; } nscoord width = mRect.width; PRInt32 textLength; // Get the document nsCOMPtr doc(getter_AddRefs(GetDocument(aPresContext))); // Transform text from content into renderable form nsCOMPtr lb; doc->GetLineBreaker(getter_AddRefs(lb)); nsCOMPtr wb; doc->GetWordBreaker(getter_AddRefs(wb)); nsTextTransformer tx(wordBufMem, WORD_BUF_SIZE, lb,wb); PrepareUnicodeText(tx, ip, paintBuf, &textLength); ip[mContentLength] = ip[mContentLength-1]; if ((ip[mContentLength]-mContentOffset) < textLength)//must set up last one for selection beyond edge if in boundary ip[mContentLength]++; if (inOffset > mContentLength){ NS_ASSERTION(0, "invalid offset passed to GetPointFromOffset"); inOffset = mContentLength; } GetWidth(*inRendContext, ts, paintBuf, ip[inOffset]-mContentOffset, &width); (*outPoint).x = width; (*outPoint).y = 0; if (paintBuf != paintBufMem) delete [] paintBuf; if (ip != indicies) { delete [] ip; } return NS_OK; } NS_IMETHODIMP nsTextFrame::GetChildFrameContainingOffset(PRInt32 inContentOffset, PRInt32* outFrameContentOffset, nsIFrame **outChildFrame) { if (nsnull == outChildFrame) return NS_ERROR_NULL_POINTER; nsresult result; PRInt32 contentOffset = inContentOffset; if (contentOffset != -1) //-1 signified the end of the current content contentOffset = inContentOffset - mContentOffset; if (contentOffset > mContentLength) { //this is not the frame we are looking for. nsIFrame *nextInFlow; nextInFlow = GetNextInFlow(); if (nextInFlow) return nextInFlow->GetChildFrameContainingOffset(inContentOffset, outFrameContentOffset, outChildFrame); else return NS_ERROR_FAILURE; } else if (inContentOffset < mContentOffset) //could happen with floaters! { result = GetPrevInFlow(outChildFrame); if (NS_SUCCEEDED(result)) return (*outChildFrame)->GetChildFrameContainingOffset(inContentOffset, outFrameContentOffset,outChildFrame); else return result; } *outFrameContentOffset = contentOffset; *outChildFrame = this; return NS_OK; } NS_IMETHODIMP nsTextFrame::PeekOffset(nsIFocusTracker *aTracker, nscoord aDesiredX, nsSelectionAmount aAmount, nsDirection aDirection, PRInt32 aStartOffset, nsIContent **aResultContent, PRInt32 *aContentOffset, PRBool aEatingWS) { if (!aResultContent || !aContentOffset || !mContent) return NS_ERROR_NULL_POINTER; if (aStartOffset < 0) aStartOffset = mContentLength + mContentOffset; if (aStartOffset < mContentOffset){ aStartOffset = mContentOffset; } if (aStartOffset > (mContentOffset + mContentLength)){ nsIFrame *nextInFlow; nextInFlow = GetNextInFlow(); if (!nextInFlow){ NS_ASSERTION(PR_FALSE,"nsTextFrame::PeekOffset no more flow \n"); return NS_ERROR_INVALID_ARG; } return nextInFlow->PeekOffset(aTracker, aDesiredX, aAmount,aDirection,aStartOffset, aResultContent,aContentOffset,aEatingWS); } 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]; } PRInt32 textLength; // Transform text from content into renderable form nsresult result(NS_OK); nsIDocument* doc; result = mContent->GetDocument(doc); if (NS_FAILED(result) || !doc) return result; nsCOMPtr lb; doc->GetLineBreaker(getter_AddRefs(lb)); nsCOMPtr wb; doc->GetWordBreaker(getter_AddRefs(wb)); NS_RELEASE(doc); nsTextTransformer tx(wordBufMem, WORD_BUF_SIZE,lb,wb); switch (aAmount){ case eSelectNoAmount : { *aResultContent = mContent; if (*aResultContent) (*aResultContent)->AddRef(); *aContentOffset = aStartOffset; } break; case eSelectCharacter : { PrepareUnicodeText(tx, ip, paintBuf, &textLength); ip[mContentLength] = ip[mContentLength-1]; if ((ip[mContentLength]-mContentOffset) < textLength)//must set up last one for selection beyond edge if in boundary ip[mContentLength]++; nsIFrame *frameUsed = nsnull; PRInt32 start; PRBool found = PR_TRUE; if (aDirection == eDirPrevious){ PRInt32 i; for (i = aStartOffset -1 - mContentOffset; i >=0; i--){ if (ip[i] < ip[aStartOffset - mContentOffset]){ *aContentOffset = i + mContentOffset; break; } } if (i <0){ found = PR_FALSE; frameUsed = GetPrevInFlow(); start = mContentOffset; } } else if (aDirection == eDirNext){ PRInt32 i; for (i = aStartOffset +1 - mContentOffset; i <= mContentLength; i++){ if (ip[i] > ip[aStartOffset - mContentOffset]){ *aContentOffset = i + mContentOffset; break; } } /* if (aStartOffset == 0 && (mFlags & TEXT_SKIP_LEADING_WS)) i--; //back up because we just skipped over some white space. why skip over the char also? */ if (i > mContentLength){ found = PR_FALSE; frameUsed = GetNextInFlow(); start = mContentOffset + mContentLength; } } if (!found){ if (frameUsed){ result = frameUsed->PeekOffset(aTracker, aDesiredX, eSelectCharacter, aDirection, start, aResultContent, aContentOffset, aEatingWS); } else {//reached end ask the frame for help result = nsFrame::PeekOffset(aTracker, aDesiredX, eSelectCharacter, aDirection, start, aResultContent, aContentOffset, aEatingWS); } } else { *aResultContent = mContent; if (*aResultContent) (*aResultContent)->AddRef(); } } break; case eSelectWord : { nsIFrame *frameUsed = nsnull; PRBool keepSearching; //if you run out of chars before you hit the end of word, maybe next frame has more text to select? PRInt32 start; PRBool found = PR_FALSE; PRBool isWhitespace; PRInt32 wordLen, contentLen; if (aDirection == eDirPrevious){ keepSearching = PR_TRUE; tx.Init(this, aStartOffset); if (tx.GetPrevWord(PR_FALSE, wordLen, contentLen, isWhitespace, PR_FALSE)){ if ((aEatingWS && !isWhitespace) || !aEatingWS){ *aContentOffset = aStartOffset - contentLen; //check for whitespace next. if (*aContentOffset > mContentOffset) keepSearching = PR_FALSE;//reached the beginning of a word aEatingWS = !isWhitespace;//nowhite space, just eat chars. while (isWhitespace && tx.GetPrevWord(PR_FALSE, wordLen, contentLen, isWhitespace, PR_FALSE)){ *aContentOffset -= contentLen; aEatingWS = PR_FALSE; } keepSearching = *aContentOffset <= mContentOffset; if (!isWhitespace){ if (!keepSearching) found = PR_TRUE; else aEatingWS = PR_TRUE; } } else { *aContentOffset = mContentLength + mContentOffset; found = PR_TRUE; } } frameUsed = GetPrevInFlow(); start = -1; //start at end } else if (aDirection == eDirNext){ tx.Init(this, aStartOffset ); if (tx.GetNextWord(PR_FALSE, wordLen, contentLen, isWhitespace, PR_FALSE)){ if ((aEatingWS && isWhitespace) || !aEatingWS){ *aContentOffset = aStartOffset + contentLen; //check for whitespace next. keepSearching = PR_TRUE; isWhitespace = PR_TRUE; while (tx.GetNextWord(PR_FALSE, wordLen, contentLen, isWhitespace, PR_FALSE) && isWhitespace){ *aContentOffset += contentLen; keepSearching = PR_FALSE; isWhitespace = PR_FALSE; } } else if (aEatingWS) *aContentOffset = mContentOffset; if (!isWhitespace){ found = PR_TRUE; aEatingWS = PR_FALSE; } else if (!keepSearching) //we have found the "whole" word so just looking for WS aEatingWS = PR_TRUE; } frameUsed = GetNextInFlow(); start = 0; } if (!found || (*aContentOffset > (mContentOffset + mContentLength)) || (*aContentOffset < mContentOffset)){ //gone too far if (frameUsed){ result = frameUsed->PeekOffset(aTracker, aDesiredX, aAmount, aDirection, start, aResultContent, aContentOffset, aEatingWS); } else {//reached end ask the frame for help result = nsFrame::PeekOffset(aTracker, aDesiredX, aAmount, aDirection, start, aResultContent, aContentOffset, aEatingWS); } } else { *aResultContent = mContent; if (*aResultContent) (*aResultContent)->AddRef(); } } break; case eSelectLine : { // Cleanup if (paintBuf != paintBufMem) { delete [] paintBuf; } if (ip != indicies) { delete [] ip; } return nsFrame::PeekOffset(aTracker, aDesiredX, aAmount, aDirection, aStartOffset, aResultContent, aContentOffset, aEatingWS); } break; default: result = NS_ERROR_FAILURE; break; } // Cleanup if (paintBuf != paintBufMem) { delete [] paintBuf; } if (ip != indicies) { delete [] ip; } if (NS_FAILED(result)){ *aResultContent = mContent; if (*aResultContent) (*aResultContent)->AddRef(); *aContentOffset = aStartOffset; result = NS_OK; } return result; } NS_IMETHODIMP nsTextFrame::HandleMultiplePress(nsIPresContext& aPresContext, nsGUIEvent* aEvent, nsEventStatus& aEventStatus) { if (!DisplaySelection(aPresContext)) { return NS_OK; } nsCOMPtr shell; nsresult rv = aPresContext.GetShell(getter_AddRefs(shell)); if (NS_SUCCEEDED(rv) && shell) { nsCOMPtr acx; nsCOMPtr tracker; tracker = do_QueryInterface(shell, &rv); if (NS_FAILED(rv) || !tracker) return rv; rv = shell->CreateRenderingContext(this, getter_AddRefs(acx)); if (NS_SUCCEEDED(rv)){ PRInt32 startPos = 0; PRInt32 contentOffsetEnd = 0; nsCOMPtr newContent; if (NS_SUCCEEDED(GetPosition(aPresContext, aEvent->point.x, getter_AddRefs(newContent), startPos, contentOffsetEnd))){ //find which word needs to be selected! use peek offset one way then the other nsCOMPtr startContent; nsCOMPtr startNode; nsCOMPtr endContent; nsCOMPtr endNode; PRInt32 startOffset; PRInt32 endOffset; //peeks{} rv = PeekOffset(tracker, 0, eSelectWord, eDirPrevious, startPos, getter_AddRefs(startContent), &startOffset, PR_FALSE); if (NS_FAILED(rv)) return rv; rv = PeekOffset(tracker, 0, eSelectWord, eDirNext, startPos, getter_AddRefs(endContent), &endOffset, PR_FALSE); if (NS_FAILED(rv)) return rv; endNode = do_QueryInterface(endContent,&rv); if (NS_FAILED(rv)) return rv; startNode = do_QueryInterface(startContent,&rv); if (NS_FAILED(rv)) return rv; nsCOMPtr selection; if (NS_SUCCEEDED(shell->GetSelection(SELECTION_NORMAL, getter_AddRefs(selection)))){ rv = selection->Collapse(startNode,startOffset); if (NS_FAILED(rv)) return rv; rv = selection->Extend(endNode,endOffset); if (NS_FAILED(rv)) return rv; } //no release } } } return NS_OK; } NS_IMETHODIMP nsTextFrame::GetOffsets(PRInt32 &start, PRInt32 &end) const { start = mContentOffset; end = mContentOffset+mContentLength; return NS_OK; } NS_IMETHODIMP nsTextFrame::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 nsTextFrame::Reflow: aMaxSize=%d,%d", aReflowState.availableWidth, aReflowState.availableHeight)); // XXX If there's no line layout, we shouldn't even have created this // frame. This may happen if, for example, this is text inside a table // but not inside a cell. For now, just don't reflow. if (nsnull == aReflowState.mLineLayout) { // XXX Add a method to aMetrics that does this; we do it several places aMetrics.width = 0; aMetrics.height = 0; aMetrics.ascent = 0; aMetrics.descent = 0; if (nsnull != aMetrics.maxElementSize) { aMetrics.maxElementSize->width = 0; aMetrics.maxElementSize->height = 0; } return NS_OK; } // Get starting offset into the content PRInt32 startingOffset = 0; if (nsnull != mPrevInFlow) { nsTextFrame* prev = (nsTextFrame*) mPrevInFlow; startingOffset = prev->mContentOffset + prev->mContentLength; } nsLineLayout& lineLayout = *aReflowState.mLineLayout; TextStyle ts(&aPresContext, *aReflowState.rendContext, mStyleContext); // 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); } } else { if (0 != (mFlags & TEXT_BLINK_ON)) { mFlags &= ~TEXT_BLINK_ON; gTextBlinker->RemoveFrame(this); } } PRBool wrapping = (NS_STYLE_WHITESPACE_NORMAL == ts.mText->mWhiteSpace) || (NS_STYLE_WHITESPACE_MOZ_PRE_WRAP == ts.mText->mWhiteSpace); PRBool firstLetterOK = lineLayout.GetFirstLetterStyleOK(); PRBool justDidFirstLetter = PR_FALSE; // Set whitespace skip flag PRBool skipWhitespace = PR_FALSE; if (!ts.mPreformatted) { if (lineLayout.GetEndsInWhiteSpace()) { skipWhitespace = PR_TRUE; } } nscoord x = 0; nscoord maxWidth = aReflowState.availableWidth; nscoord maxWordWidth = 0; nscoord prevMaxWordWidth = 0; PRBool endsInWhitespace = PR_FALSE; PRBool endsInNewline = PR_FALSE; nsCOMPtr shell; aPresContext.GetShell(getter_AddRefs(shell)); nsCOMPtr doc; shell->GetDocument(getter_AddRefs(doc)); // Setup text transformer to transform this frames text content PRUnichar wordBuf[WORD_BUF_SIZE]; nsCOMPtr lb; doc->GetLineBreaker(getter_AddRefs(lb)); nsCOMPtr wb; doc->GetWordBreaker(getter_AddRefs(wb)); nsTextTransformer tx(wordBuf, WORD_BUF_SIZE,lb,wb); 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() || ((nsnull != mPrevInFlow) && (((nsTextFrame*)mPrevInFlow)->mFlags & TEXT_FIRST_LETTER)); if (inWord) { mFlags |= TEXT_IN_WORD; } mFlags &= ~TEXT_FIRST_LETTER; PRInt32 column = lineLayout.GetColumn(); PRInt32 prevColumn = column; mColumn = column; PRBool breakable = lineLayout.LineIsBreakable(); 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; if (ts.mPreformatted) { 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 */ } breakable = PR_TRUE; firstLetterOK = PR_FALSE; } else { if (firstLetterOK) { // XXX need a lookup function here; plus look ahead using the // text-runs if ((bp[0] == '\'') || (bp[0] == '\"')) { wordLen = 2; contentLen = 2; } else { wordLen = 1; contentLen = 1; } justDidFirstLetter = PR_TRUE; } 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 (!isWhitespace && justDidFirstLetter) { // Time to stop break; } } 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); } 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 ((offset == contentLength) && (prevOffset >= 0)) { // Force breakable to false when we aren't wrapping (this // guarantees that the combined word will stay together) if (!wrapping) { breakable = 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 (!breakable || (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(0), offset-prevOffset); ListTag(stdout); printf(": start='"); fputs(tmp, stdout); printf("' baseWidth=%d [%d,%d]\n", lastWordWidth, prevOffset, offset); #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(&aPresContext, lineLayout, aReflowState, next, lastWordWidth); if (!breakable || (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 - startingOffset); #endif lineLayout.ForgetWordFrames(); } } } } } lineLayout.SetColumn(column); // 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); if (0 != x) { lineLayout.SetEndsInWhiteSpace(endsInWhitespace); } if (justDidFirstLetter) { lineLayout.SetFirstLetterFrame(this); lineLayout.SetFirstLetterStyleOK(PR_FALSE); mFlags |= TEXT_FIRST_LETTER; } // Setup metrics for caller; store final max-element-size information aMetrics.width = x; mComputedWidth = x; if ((0 == x) && !ts.mPreformatted) { 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; } // Set content offset and length mContentOffset = startingOffset; mContentLength = offset - startingOffset; // If it's an incremental reflow command, then invalidate our existing // bounds. // XXX We need a finer granularity than this, but it isn't clear what // has actually changed... if (eReflowReason_Incremental == aReflowState.reason) { Invalidate(mRect); } //go to selection and ask if we are selected here. and where!! nsReflowStatus rs = (offset == contentLength) ? NS_FRAME_COMPLETE : NS_FRAME_NOT_COMPLETE; if (endsInNewline) { rs = NS_INLINE_LINE_BREAK_AFTER(rs); } else if ((offset != contentLength) && (offset == startingOffset)) { // Break-before a long-word that doesn't fit here rs = NS_INLINE_LINE_BREAK_BEFORE(); } aStatus = rs; NS_FRAME_TRACE(NS_FRAME_TRACE_CALLS, ("exit nsTextFrame::Reflow: status=%x width=%d", aStatus, aMetrics.width)); return NS_OK; } NS_IMETHODIMP nsTextFrame::AdjustFrameSize(nscoord aExtraSpace, nscoord& aUsedSpace) { // Get the text fragments that make up our content const 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; const 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 nsTextFrame::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) && (NS_STYLE_WHITESPACE_MOZ_PRE_WRAP != 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 const 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; const 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++; } } if (mRect.width > dw) { mRect.width -= dw; } else { dw = mRect.width; mRect.width = 0; } mComputedWidth -= dw; } if (0 != dw) { mFlags |= TEXT_TRIMMED_WS; } else { mFlags &= ~TEXT_TRIMMED_WS; } aDeltaWidth = dw; return NS_OK; } nscoord nsTextFrame::ComputeTotalWordWidth(nsIPresContext* aPresContext, nsLineLayout& aLineLayout, const nsHTMLReflowState& aReflowState, nsIFrame* aNextFrame, nscoord aBaseWidth) { nscoord addedWidth = 0; while (nsnull != aNextFrame) { nsIContent* content = nsnull; if ((NS_OK == aNextFrame->GetContent(&content)) && (nsnull != content)) { #ifdef DEBUG_WORD_WRAPPING printf(" next textRun="); nsFrame::ListTag(stdout, aNextFrame); printf("\n"); #endif nsITextContent* tc; if (NS_OK == content->QueryInterface(kITextContentIID, (void**)&tc)) { PRBool stop; nscoord moreWidth = ComputeWordFragmentWidth(aPresContext, aLineLayout, aReflowState, aNextFrame, 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; } } // 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; } #if 0 nsIStyleContext* nsTextFrame::GetCorrectStyleContext(nsIPresContext* aPresContext, nsLineLayout& aLineLayout, const nsHTMLReflowState& aReflowState, nsIFrame* aTextFrame) { } #endif nscoord nsTextFrame::ComputeWordFragmentWidth(nsIPresContext* aPresContext, nsLineLayout& aLineLayout, const nsHTMLReflowState& aReflowState, nsIFrame* aTextFrame, nsITextContent* aText, PRBool* aStop) { PRUnichar buf[TEXT_BUF_SIZE]; nsIDocument* doc; mContent->GetDocument(doc); if (!doc) { // Not all content objects have a document pointer. Anonymous content // objects, e.g. generated content, do not live in the document model nsIPresShell* shell; aPresContext->GetShell(&shell); shell->GetDocument(&doc); NS_RELEASE(shell); } nsCOMPtr lb; doc->GetLineBreaker(getter_AddRefs(lb)); nsCOMPtr wb; doc->GetWordBreaker(getter_AddRefs(wb)); NS_IF_RELEASE(doc); nsTextTransformer tx(buf, TEXT_BUF_SIZE,lb,wb); // 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(&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_IF_RELEASE(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 nsTextFrame::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 nsTextFrame::GetFrameType(nsIAtom** aType) const { NS_PRECONDITION(nsnull != aType, "null OUT parameter pointer"); *aType = nsLayoutAtoms::textFrame; NS_ADDREF(*aType); return NS_OK; } NS_IMETHODIMP nsTextFrame::GetFrameName(nsString& aResult) const { return MakeFrameName("Text", aResult); } NS_IMETHODIMP nsTextFrame::List(FILE* out, PRInt32 aIndent) const { // Output the tag IndentBy(out, aIndent); 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][%x] ", mContentOffset, mContentOffset+mContentLength-1, isComplete ? 'T':'F', mFlags); if (nsnull != mNextSibling) { fprintf(out, " next=%p", mNextSibling); } 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 fprintf(out, " {%d,%d,%d,%d}", mRect.x, mRect.y, mRect.width, mRect.height); if (0 != mState) { fprintf(out, " [state=%08x]", mState); } fprintf(out, " sc=%p<\n", mStyleContext); // Output the text aIndent++; IndentBy(out, aIndent); fputs("\"", out); fputs(tmp, out); fputs("\"\n", out); aIndent--; IndentBy(out, aIndent); fputs(">\n", out); return NS_OK; } //STORAGE FOR LATER MAYBE #if 0 nsTextFrame *frame = this; while(frame = (nsTextFrame *)frame->GetPrevInFlow()){ result = NS_OK; nsPoint futurecoord; frame->GetOffsetFromView(futurecoord, &view); if (view == nsnull) return NS_ERROR_UNEXPECTED; nscoord x,y; do { view->GetPosition(&x, &y); futurecoord.x += x; futurecoord.y += y; view->GetParent(view); } while (view); if (coord.y > futurecoord.y) { if (coord.x < futurecoord.x) {//keep going back until y is up again or coord.x is greater than future coord.x nsTextFrame *lookahead = nsnull; while(lookahead = (nsTextFrame *)frame->GetPrevInFlow()){ result = NS_OK; nsPoint futurecoord2; lookahead->GetOffsetFromView(futurecoord2, &view); if (view == nsnull) return NS_ERROR_UNEXPECTED; do { view->GetPosition(&x, &y); futurecoord2.x += x; futurecoord2.y += y; view->GetParent(view); } while (view); if (futurecoord.y > futurecoord2.y)//gone too far break; frame = lookahead; futurecoord = futurecoord2; if (coord.x >=futurecoord2.x) break; } } //definately this one then nscoord newcoord; newcoord = coord.x ;//- futurecoord.x; #endif