/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is mozilla.org code. * * The Initial Developer of the Original Code is * Netscape Communications Corporation. * Portions created by the Initial Developer are Copyright (C) 1998 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Robert O'Callahan * Roger B. Sidje * Pierre Phaneuf * Prabhat Hegde * Tomi Leppikangas * Roland Mainz * Daniel Glazman * Neil Deakin * Masayuki Nakano * Mats Palmgren * Uri Bernstein * * Alternatively, the contents of this file may be used under the terms of * either of the GNU General Public License Version 2 or later (the "GPL"), * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ /* rendering object for textual content of elements */ #include "nsCOMPtr.h" #include "nsHTMLParts.h" #include "nsCRT.h" #include "nsSplittableFrame.h" #include "nsLineLayout.h" #include "nsString.h" #include "nsUnicharUtils.h" #include "nsPresContext.h" #include "nsIContent.h" #include "nsStyleConsts.h" #include "nsStyleContext.h" #include "nsCoord.h" #include "nsIFontMetrics.h" #include "nsIRenderingContext.h" #include "nsIPresShell.h" #include "nsITimer.h" #include "prtime.h" #include "nsVoidArray.h" #include "prprf.h" #include "nsIDOMText.h" #include "nsIDocument.h" #include "nsIDeviceContext.h" #include "nsICaret.h" #include "nsCSSPseudoElements.h" #include "nsILineBreaker.h" #include "nsCompatibility.h" #include "nsCSSColorUtils.h" #include "nsLayoutUtils.h" #include "nsDisplayList.h" #include "nsFrame.h" #include "nsTextTransformer.h" #include "nsTextFragment.h" #include "nsGkAtoms.h" #include "nsFrameSelection.h" #include "nsISelection.h" #include "nsIDOMRange.h" #include "nsILookAndFeel.h" #include "nsCSSRendering.h" #include "nsContentUtils.h" #include "nsILineIterator.h" #include "nsCompressedCharMap.h" #include "nsIServiceManager.h" #ifdef ACCESSIBILITY #include "nsIAccessible.h" #include "nsIAccessibilityService.h" #endif #include "nsGUIEvent.h" #include "nsAutoPtr.h" #include "nsStyleSet.h" #include "nsBidiFrames.h" #include "nsBidiPresUtils.h" #include "nsBidiUtils.h" #ifdef SUNCTL #include "nsILE.h" static NS_DEFINE_CID(kLECID, NS_ULE_CID); #endif /* SUNCTL */ #ifdef NS_DEBUG #undef NOISY_BLINK #undef DEBUG_WORD_WRAPPING #undef NOISY_REFLOW #undef NOISY_TRIM #else #undef NOISY_BLINK #undef DEBUG_WORD_WRAPPING #undef NOISY_REFLOW #undef NOISY_TRIM #endif // #define DEBUGWORDJUMP #define kSZLIG 0x00DF //---------------------------------------------------------------------- #define TEXT_BUF_SIZE 100 //---------------------------------------- struct nsAutoIndexBuffer; struct nsAutoPRUint8Buffer; class nsTextStyle { public: const nsStyleFont* mFont; const nsStyleText* mText; nsIFontMetrics* mNormalFont; nsIFontMetrics* mSmallFont; nsIFontMetrics* mLastFont; PRBool mSmallCaps; nscoord mWordSpacing; nscoord mLetterSpacing; nscoord mSpaceWidth; nscoord mAveCharWidth; PRBool mJustifying; PRBool mPreformatted; PRInt32 mNumJustifiableCharacterToRender; PRInt32 mNumJustifiableCharacterToMeasure; nscoord mExtraSpacePerJustifiableCharacter; PRInt32 mNumJustifiableCharacterReceivingExtraJot; nsTextStyle(nsPresContext* aPresContext, nsIRenderingContext& aRenderingContext, nsStyleContext* sc); ~nsTextStyle(); }; // Contains extra style data needed only for painting (not reflowing) class nsTextPaintStyle : public nsTextStyle { public: enum{ eNormalSelection = nsISelectionController::SELECTION_NORMAL, eIMESelections = nsISelectionController::SELECTION_IME_RAWINPUT | nsISelectionController::SELECTION_IME_SELECTEDRAWTEXT | nsISelectionController::SELECTION_IME_CONVERTEDTEXT | nsISelectionController::SELECTION_IME_SELECTEDCONVERTEDTEXT, eAllSelections = eNormalSelection | eIMESelections }; const nsStyleColor* mColor; nsTextPaintStyle(nsPresContext* aPresContext, nsIRenderingContext& aRenderingContext, nsStyleContext* aStyleContext, nsIContent* aContent, PRInt16 aSelectionStatus); ~nsTextPaintStyle(); nscolor GetTextColor(); void GetSelectionColors(nscolor* aForeColor, nscolor* aBackColor, PRBool* aBackIsTransparent); void GetIMESelectionColors(SelectionType aSelectionType, nscolor* aForeColor, nscolor* aBackColor, PRBool* aBackIsTransparent); // if this returns PR_FALSE, we don't need to draw underline. PRBool GetIMEUnderline(SelectionType aSelectionType, nscolor* aLineColor, float* aRelativeSize); protected: nsPresContext* mPresContext; nsStyleContext* mStyleContext; nsIContent* mContent; PRInt16 mSelectionStatus; // see nsIDocument.h SetDisplaySelection() // Common colors PRBool mInitCommonColors; PRInt32 mSufficientContrast; nscolor mFrameBackgroundColor; // Selection colors PRBool mInitSelectionColors; nscolor mSelectionTextColor; nscolor mSelectionBGColor; PRBool mSelectionBGIsTransparent; // IME selection colors and underline info struct nsIMEColor { PRBool mInit; nscolor mTextColor; nscolor mBGColor; nscolor mBGIsTransparent; nscolor mUnderlineColor; }; nsIMEColor mIMEColor[4]; // indexs enum { eIndexRawInput = 0, eIndexSelRawText, eIndexConvText, eIndexSelConvText }; float mIMEUnderlineRelativeSize; // Color initializations PRBool InitCommonColors(); PRBool InitSelectionColors(); nsIMEColor* GetIMEColor(SelectionType aSelectionType); PRBool InitIMEColors(SelectionType aSelectionType, nsIMEColor*); PRBool EnsureSufficientContrast(nscolor *aForeColor, nscolor *aBackColor); nscolor GetResolvedForeColor(nscolor aColor, nscolor aDefaultForeColor, nscolor aBackColor); }; class nsTextFrame : public nsFrame { public: nsTextFrame(nsStyleContext* aContext) : nsFrame(aContext) { NS_ASSERTION(mContentOffset == 0, "Bogus content offset"); NS_ASSERTION(mContentLength == 0, "Bogus content length"); } // nsIFrame NS_IMETHOD BuildDisplayList(nsDisplayListBuilder* aBuilder, const nsRect& aDirtyRect, const nsDisplayListSet& aLists); void PaintText(nsIRenderingContext& aRenderingContext, nsPoint aPt); NS_IMETHOD Init(nsIContent* aContent, nsIFrame* aParent, nsIFrame* aPrevInFlow); virtual void Destroy(); NS_IMETHOD GetCursor(const nsPoint& aPoint, nsIFrame::Cursor& aCursor); NS_IMETHOD CharacterDataChanged(nsPresContext* aPresContext, nsIContent* aChild, PRBool aAppend); virtual nsIFrame* GetNextContinuation() const { return mNextContinuation; } NS_IMETHOD SetNextContinuation(nsIFrame* aNextContinuation) { NS_ASSERTION (!aNextContinuation || GetType() == aNextContinuation->GetType(), "setting a next continuation with incorrect type!"); NS_ASSERTION (!nsSplittableFrame::IsInNextContinuationChain(aNextContinuation, this), "creating a loop in continuation chain!"); mNextContinuation = aNextContinuation; if (aNextContinuation) aNextContinuation->RemoveStateBits(NS_FRAME_IS_FLUID_CONTINUATION); return NS_OK; } virtual nsIFrame* GetNextInFlowVirtual() const { return GetNextInFlow(); } nsIFrame* GetNextInFlow() const { return mNextContinuation && (mNextContinuation->GetStateBits() & NS_FRAME_IS_FLUID_CONTINUATION) ? mNextContinuation : nsnull; } NS_IMETHOD SetNextInFlow(nsIFrame* aNextInFlow) { NS_ASSERTION (!aNextInFlow || GetType() == aNextInFlow->GetType(), "setting a next in flow with incorrect type!"); NS_ASSERTION (!nsSplittableFrame::IsInNextContinuationChain(aNextInFlow, this), "creating a loop in continuation chain!"); mNextContinuation = aNextInFlow; if (aNextInFlow) aNextInFlow->AddStateBits(NS_FRAME_IS_FLUID_CONTINUATION); return NS_OK; } virtual nsIFrame* GetLastInFlow() const; virtual nsIFrame* GetLastContinuation() const; virtual nsSplittableType GetSplittableType() const { return NS_FRAME_SPLITTABLE; } /** * Get the "type" of the frame * * @see nsGkAtoms::textFrame */ virtual nsIAtom* GetType() const; virtual PRBool IsFrameOfType(PRUint32 aFlags) const { // Set the frame state bit for text frames to mark them as replaced. // XXX kipp: temporary return nsFrame::IsFrameOfType(aFlags & ~(nsIFrame::eReplaced)); } #ifdef DEBUG NS_IMETHOD List(FILE* out, PRInt32 aIndent) const; NS_IMETHOD GetFrameName(nsAString& aResult) const; NS_IMETHOD_(nsFrameState) GetDebugStateBits() const ; #endif NS_IMETHOD GetPositionHelper(const nsPoint& aPoint, nsIContent ** aNewContent, PRInt32& aContentOffset, PRInt32& aContentOffsetEnd); virtual ContentOffsets CalcContentOffsetsFromFramePoint(nsPoint aPoint); NS_IMETHOD GetPositionSlowly(nsIRenderingContext * aRendContext, const nsPoint& aPoint, nsIContent ** aNewContent, PRInt32& aOffset); NS_IMETHOD SetSelected(nsPresContext* aPresContext, nsIDOMRange *aRange, PRBool aSelected, nsSpread aSpread); virtual PRBool PeekOffsetNoAmount(PRBool aForward, PRInt32* aOffset); virtual PRBool PeekOffsetCharacter(PRBool aForward, PRInt32* aOffset); virtual PRBool PeekOffsetWord(PRBool aForward, PRBool aWordSelectEatSpace, PRBool aIsKeyboardSelect, PRInt32* aOffset, PRBool* aSawBeforeType); NS_IMETHOD CheckVisibility(nsPresContext* aContext, PRInt32 aStartIndex, PRInt32 aEndIndex, PRBool aRecurse, PRBool *aFinished, PRBool *_retval); NS_IMETHOD GetOffsets(PRInt32 &start, PRInt32 &end)const; virtual void AdjustOffsetsForBidi(PRInt32 start, PRInt32 end); NS_IMETHOD GetPointFromOffset(nsPresContext* inPresContext, nsIRenderingContext* inRendContext, PRInt32 inOffset, nsPoint* outPoint); NS_IMETHOD GetChildFrameContainingOffset(PRInt32 inContentOffset, PRBool inHint, PRInt32* outFrameContentOffset, nsIFrame* *outChildFrame); virtual PRBool IsVisibleInSelection(nsISelection* aSelection); virtual PRBool IsEmpty(); virtual PRBool IsSelfEmpty() { return IsEmpty(); } /** * @return PR_TRUE if this text frame ends with a newline character. It * should return PR_FALSE if this is not a text frame. */ virtual PRBool HasTerminalNewline() const; #ifdef ACCESSIBILITY NS_IMETHOD GetAccessible(nsIAccessible** aAccessible); #endif // nsIHTMLReflow virtual void MarkIntrinsicWidthsDirty(); virtual nscoord GetMinWidth(nsIRenderingContext *aRenderingContext); virtual nscoord GetPrefWidth(nsIRenderingContext *aRenderingContext); virtual void AddInlineMinWidth(nsIRenderingContext *aRenderingContext, InlineMinWidthData *aData); virtual void AddInlinePrefWidth(nsIRenderingContext *aRenderingContext, InlinePrefWidthData *aData); virtual nsSize ComputeSize(nsIRenderingContext *aRenderingContext, nsSize aCBSize, nscoord aAvailableWidth, nsSize aMargin, nsSize aBorder, nsSize aPadding, PRBool aShrinkWrap); NS_IMETHOD Reflow(nsPresContext* aPresContext, nsHTMLReflowMetrics& aMetrics, const nsHTMLReflowState& aReflowState, nsReflowStatus& aStatus); virtual PRBool CanContinueTextRun() const; NS_IMETHOD TrimTrailingWhiteSpace(nsPresContext* aPresContext, nsIRenderingContext& aRC, nscoord& aDeltaWidth, PRBool& aLastCharIsJustifiable); struct TextReflowData { PRInt32 mX; // OUT PRInt32 mOffset; // IN/OUT How far along we are in the content nscoord mAscent; // OUT nscoord mDescent; // OUT PRPackedBool mWrapping; // IN PRPackedBool mSkipWhitespace; // IN PRPackedBool mMeasureText; // IN PRPackedBool mInWord; // IN PRPackedBool mFirstLetterOK; // IN PRPackedBool mCanBreakBefore; // IN PRPackedBool mTrailingSpaceTrimmed; // IN/OUT TextReflowData(PRInt32 aStartingOffset, PRBool aWrapping, PRBool aSkipWhitespace, PRBool aMeasureText, PRBool aInWord, PRBool aFirstLetterOK, PRBool aCanBreakBefore, PRBool aTrailingSpaceTrimmed) : mX(0), mOffset(aStartingOffset), mAscent(0), mDescent(0), mWrapping(aWrapping), mSkipWhitespace(aSkipWhitespace), mMeasureText(aMeasureText), mInWord(aInWord), mFirstLetterOK(aFirstLetterOK), mCanBreakBefore(aCanBreakBefore), mTrailingSpaceTrimmed(aTrailingSpaceTrimmed) {} }; nsIDocument* GetDocument(nsPresContext* aPresContext); void PrepareUnicodeText(nsTextTransformer& aTransformer, nsAutoIndexBuffer* aIndexBuffer, nsAutoTextBuffer* aTextBuffer, PRInt32* aTextLen, PRBool aForceArabicShaping = PR_FALSE, PRIntn* aJustifiableCharCount = nsnull, PRBool aRemoveMultipleTrimmedWS = PR_FALSE); void ComputeExtraJustificationSpacing(nsIRenderingContext& aRenderingContext, nsTextStyle& aTextStyle, PRUnichar* aBuffer, PRInt32 aLength, PRInt32 aNumJustifiableCharacter); void SetupTextRunDirection(nsPresContext* aPresContext, nsIRenderingContext* aRenderingContext); /** * @param aRightToLeftText whether the rendering context is reversing text * using its native right-to-left capability */ void PaintTextDecorations(nsIRenderingContext& aRenderingContext, nsStyleContext* aStyleContext, nsPresContext* aPresContext, nsTextPaintStyle& aStyle, nscoord aX, nscoord aY, nscoord aWidth, PRBool aRightToLeftText, PRUnichar* aText = nsnull, SelectionDetails *aDetails = nsnull, PRUint32 aIndex = 0, PRUint32 aLength = 0, const nscoord* aSpacing = nsnull); void PaintTextSlowly(nsPresContext* aPresContext, nsIRenderingContext& aRenderingContext, nsStyleContext* aStyleContext, nsTextPaintStyle& aStyle, nscoord aX, nscoord aY); // The passed-in rendering context must have its color set to the color the // text should be rendered in. /** * @param aRightToLeftText whether the rendering context is reversing text * using its native right-to-left capability */ void RenderString(nsIRenderingContext& aRenderingContext, nsStyleContext* aStyleContext, nsPresContext* aPresContext, nsTextPaintStyle& aStyle, PRBool aRightToLeftText, PRUnichar* aBuffer, PRInt32 aLength, PRBool aIsEndOfFrame, nscoord aX, nscoord aY, nscoord aWidth, SelectionDetails *aDetails = nsnull); void MeasureSmallCapsText(nsIRenderingContext* aRenderingContext, nsTextStyle& aStyle, PRUnichar* aWord, PRInt32 aWordLength, PRBool aIsEndOfFrame, nsTextDimensions* aDimensionsResult); PRUint32 EstimateNumChars(PRUint32 aAvailableWidth, PRUint32 aAverageCharWidth); nsReflowStatus MeasureText(nsPresContext* aPresContext, const nsHTMLReflowState& aReflowState, nsTextTransformer& aTx, nsTextStyle& aTs, TextReflowData& aTextData); void GetTextDimensions(nsIRenderingContext& aRenderingContext, nsTextStyle& aStyle, PRUnichar* aBuffer, PRInt32 aLength, PRBool aIsEndOfFrame, nsTextDimensions* aDimensionsResult); //this returns the index into the PAINTBUFFER of the x coord aWidth(based on 0 as far left) //also note: this is NOT added to mContentOffset since that would imply that this return is //meaningful to content yet. use index buffer from prepareunicodestring to find the content offset. PRInt32 GetLengthSlowly(nsIRenderingContext& aRenderingContext, nsTextStyle& aStyle, PRUnichar* aBuffer, PRInt32 aLength, PRBool aIsEndOfFrame, nscoord aWidth); // REVIEW: There is absolute no reason why IsTextInSelection should depend // on a rendering context, and I've refactored the code so it doesn't. PRBool IsTextInSelection(); nsresult GetTextInfoForPainting(nsPresContext* aPresContext, nsIPresShell** aPresShell, nsISelectionController** aSelectionController, PRBool& aDisplayingSelection, PRBool& aIsPaginated, PRBool& aIsSelected, PRBool& aHideStandardSelection, PRInt16& aSelectionValue); nsresult GetSelectionStatus(nsPresContext* aPresContext, PRInt16& aSelectionValue); void PaintUnicodeText(nsPresContext* aPresContext, nsIRenderingContext& aRenderingContext, nsStyleContext* aStyleContext, nsTextPaintStyle& aStyle, nscoord dx, nscoord dy); void PaintAsciiText(nsPresContext* aPresContext, nsIRenderingContext& aRenderingContext, nsStyleContext* aStyleContext, nsTextPaintStyle& aStyle, nscoord dx, nscoord dy); #ifdef DEBUG void ToCString(nsString& aBuf, PRInt32* aTotalContentLength) const; #endif PRInt32 GetContentOffset() { return mContentOffset; } PRInt32 GetContentLength() { return mContentLength; } protected: virtual ~nsTextFrame(); nsIFrame* mNextContinuation; PRInt32 mContentOffset; PRInt32 mContentLength; PRInt32 mColumn; nscoord mAscent; //factored out method for GetTextDimensions and getlengthslowly. if aGetTextDimensions is non-zero number then measure to the width field and return the length. else shove total dimensions into result PRInt32 GetTextDimensionsOrLength(nsIRenderingContext& aRenderingContext, nsTextStyle& aStyle, PRUnichar* aBuffer, PRInt32 aLength, PRBool aIsEndOfFrame, nsTextDimensions* aDimensionsResult, PRBool aGetTextDimensions/* true=get dimensions false = return length up to aDimensionsResult->width size*/); nsresult GetContentAndOffsetsForSelection(nsPresContext* aPresContext,nsIContent **aContent, PRInt32 *aOffset, PRInt32 *aLength); void AdjustSelectionPointsForBidi(SelectionDetails *sdptr, PRInt32 textLength, PRBool isRTLChars, PRBool isOddLevel, PRBool isBidiSystem); void SetOffsets(PRInt32 start, PRInt32 end); PRBool IsChineseJapaneseLangGroup(); PRBool IsJustifiableCharacter(PRUnichar aChar, PRBool aLangIsCJ); nsresult FillClusterBuffer(nsPresContext *aPresContext, const PRUnichar *aText, PRUint32 aLength, nsAutoPRUint8Buffer& aClusterBuffer); }; //---------------------------------------- // checks to see if the text can be lightened.. // text is darkend inline PRBool CanDarken(nsPresContext* aPresContext) { PRBool darken; if (aPresContext->GetBackgroundColorDraw()) { darken = PR_FALSE; } else { if (aPresContext->GetBackgroundImageDraw()) { darken = PR_FALSE; } else { darken = PR_TRUE; } } return darken; } struct nsAutoIndexBuffer { nsAutoIndexBuffer(); ~nsAutoIndexBuffer(); nsresult GrowTo(PRInt32 aAtLeast); PRInt32* mBuffer; PRInt32 mBufferLen; PRInt32 mAutoBuffer[TEXT_BUF_SIZE]; }; nsAutoIndexBuffer::nsAutoIndexBuffer() : mBuffer(mAutoBuffer), mBufferLen(TEXT_BUF_SIZE) { #ifdef DEBUG memset(mAutoBuffer, 0xdd, sizeof(mAutoBuffer)); #endif } nsAutoIndexBuffer::~nsAutoIndexBuffer() { if (mBuffer && (mBuffer != mAutoBuffer)) { delete [] mBuffer; } } nsresult nsAutoIndexBuffer::GrowTo(PRInt32 aAtLeast) { if (aAtLeast > mBufferLen) { PRInt32 newSize = mBufferLen * 2; if (newSize < mBufferLen + aAtLeast) { newSize = mBufferLen * 2 + aAtLeast; } PRInt32* newBuffer = new PRInt32[newSize]; if (!newBuffer) { return NS_ERROR_OUT_OF_MEMORY; } #ifdef DEBUG memset(newBuffer, 0xdd, sizeof(PRInt32) * newSize); #endif memcpy(newBuffer, mBuffer, sizeof(PRInt32) * mBufferLen); if (mBuffer != mAutoBuffer) { delete [] mBuffer; } mBuffer = newBuffer; mBufferLen = newSize; } return NS_OK; } struct nsAutoPRUint8Buffer { nsAutoPRUint8Buffer(); ~nsAutoPRUint8Buffer(); nsresult GrowTo(PRInt32 aAtLeast); PRUint8* mBuffer; PRInt32 mBufferLen; PRUint8 mAutoBuffer[TEXT_BUF_SIZE]; }; nsAutoPRUint8Buffer::nsAutoPRUint8Buffer() : mBuffer(mAutoBuffer), mBufferLen(TEXT_BUF_SIZE) { #ifdef DEBUG memset(mAutoBuffer, 0xdd, sizeof(mAutoBuffer)); #endif } nsAutoPRUint8Buffer::~nsAutoPRUint8Buffer() { if (mBuffer && (mBuffer != mAutoBuffer)) { delete [] mBuffer; } } nsresult nsAutoPRUint8Buffer::GrowTo(PRInt32 aAtLeast) { if (aAtLeast > mBufferLen) { PRInt32 newSize = mBufferLen * 2; if (newSize < mBufferLen + aAtLeast) { newSize = mBufferLen * 2 + aAtLeast; } PRUint8* newBuffer = new PRUint8[newSize]; if (!newBuffer) { return NS_ERROR_OUT_OF_MEMORY; } #ifdef DEBUG memset(newBuffer, 0xdd, sizeof(PRUint8) * newSize); #endif memcpy(newBuffer, mBuffer, sizeof(PRUint8) * mBufferLen); if (mBuffer != mAutoBuffer) { delete [] mBuffer; } mBuffer = newBuffer; mBufferLen = newSize; } return NS_OK; } //---------------------------------------------------------------------- // 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(); NS_DECL_NSITIMERCALLBACK static nsresult AddBlinkFrame(nsPresContext* aPresContext, nsIFrame* aFrame); static nsresult RemoveBlinkFrame(nsIFrame* aFrame); static PRBool GetBlinkIsOff() { return sState == 3; } protected: nsCOMPtr mTimer; nsVoidArray mFrames; protected: static nsBlinkTimer* sTextBlinker; static PRUint32 sState; // 0-2 == on; 3 == off }; nsBlinkTimer* nsBlinkTimer::sTextBlinker = nsnull; PRUint32 nsBlinkTimer::sState = 0; #ifdef NOISY_BLINK static PRTime gLastTick; #endif nsBlinkTimer::nsBlinkTimer() { } nsBlinkTimer::~nsBlinkTimer() { Stop(); sTextBlinker = nsnull; } void nsBlinkTimer::Start() { nsresult rv; mTimer = do_CreateInstance("@mozilla.org/timer;1", &rv); if (NS_OK == rv) { mTimer->InitWithCallback(this, 250, nsITimer::TYPE_REPEATING_PRECISE); } } void nsBlinkTimer::Stop() { if (nsnull != mTimer) { mTimer->Cancel(); } } NS_IMPL_ISUPPORTS1(nsBlinkTimer, nsITimerCallback) 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(); } NS_IMETHODIMP 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. sState = (sState + 1) % 4; if (sState == 1 || sState == 2) // States 0, 1, and 2 are all the same. return NS_OK; #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* frame = (nsIFrame*) mFrames.ElementAt(i); // Determine damaged area and tell view manager to redraw it // blink doesn't blink outline ... I hope nsRect bounds(nsPoint(0, 0), frame->GetSize()); frame->Invalidate(bounds, PR_FALSE); } return NS_OK; } // static nsresult nsBlinkTimer::AddBlinkFrame(nsPresContext* aPresContext, nsIFrame* aFrame) { if (!sTextBlinker) { sTextBlinker = new nsBlinkTimer; if (!sTextBlinker) return NS_ERROR_OUT_OF_MEMORY; } NS_ADDREF(sTextBlinker); sTextBlinker->AddFrame(aFrame); return NS_OK; } // static nsresult nsBlinkTimer::RemoveBlinkFrame(nsIFrame* aFrame) { NS_ASSERTION(sTextBlinker, "Should have blink timer here"); nsBlinkTimer* blinkTimer = sTextBlinker; // copy so we can call NS_RELEASE on it if (!blinkTimer) return NS_OK; blinkTimer->RemoveFrame(aFrame); NS_RELEASE(blinkTimer); return NS_OK; } //---------------------------------------------------------------------- nsTextStyle::nsTextStyle(nsPresContext* aPresContext, nsIRenderingContext& aRenderingContext, nsStyleContext* sc) { // Get style data mFont = sc->GetStyleFont(); mText = sc->GetStyleText(); // 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. NS_ASSERTION(plainFont, "null plainFont: font problems in nsTextStyle::nsTextStyle"); PRUint8 originalDecorations = plainFont->decorations; plainFont->decorations = NS_FONT_DECORATION_NONE; mAveCharWidth = 0; // Set the font: some users of the struct expect this state nsLayoutUtils::SetFontFromStyle(&aRenderingContext, sc); aRenderingContext.GetFontMetrics(mNormalFont); mNormalFont->GetSpaceWidth(mSpaceWidth); mNormalFont->GetAveCharWidth(mAveCharWidth); 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.8 * plainFont->size); mSmallFont = aPresContext->GetMetricsFor(*plainFont).get(); // addrefs // Reset to the size value saved earlier. plainFont->size = originalSize; } else { mSmallFont = nsnull; } // Reset to the decoration saved earlier plainFont->decorations = originalDecorations; // Get the word and letter spacing PRIntn unit = mText->mWordSpacing.GetUnit(); if (eStyleUnit_Coord == unit) { mWordSpacing = mText->mWordSpacing.GetCoordValue(); } else { mWordSpacing = 0; } unit = mText->mLetterSpacing.GetUnit(); if (eStyleUnit_Coord == unit) { mLetterSpacing = mText->mLetterSpacing.GetCoordValue(); } else { mLetterSpacing = 0; } mNumJustifiableCharacterToRender = 0; mNumJustifiableCharacterToMeasure = 0; mNumJustifiableCharacterReceivingExtraJot = 0; mExtraSpacePerJustifiableCharacter = 0; mPreformatted = (NS_STYLE_WHITESPACE_PRE == mText->mWhiteSpace) || (NS_STYLE_WHITESPACE_MOZ_PRE_WRAP == mText->mWhiteSpace); mJustifying = (NS_STYLE_TEXT_ALIGN_JUSTIFY == mText->mTextAlign) && !mPreformatted; } nsTextStyle::~nsTextStyle() { NS_IF_RELEASE(mNormalFont); NS_IF_RELEASE(mSmallFont); } //---------------------------------------------------------------------- inline nscolor EnsureDifferentColors(nscolor colorA, nscolor colorB) { if (colorA == colorB) { nscolor res; res = NS_RGB(NS_GET_R(colorA) ^ 0xff, NS_GET_G(colorA) ^ 0xff, NS_GET_B(colorA) ^ 0xff); return res; } return colorA; } //----------------------------------------------------------------------------- nsTextPaintStyle::nsTextPaintStyle(nsPresContext* aPresContext, nsIRenderingContext& aRenderingContext, nsStyleContext* aStyleContext, nsIContent* aContent, PRInt16 aSelectionStatus) : nsTextStyle(aPresContext, aRenderingContext, aStyleContext), mPresContext(nsnull), mStyleContext(nsnull), mContent(nsnull), mInitCommonColors(PR_FALSE), mInitSelectionColors(PR_FALSE) { mPresContext = aPresContext; mStyleContext = aStyleContext; mContent = aContent; mSelectionStatus = aSelectionStatus; mColor = mStyleContext->GetStyleColor(); for (int i = 0; i < 4; i++) mIMEColor[i].mInit = PR_FALSE; mIMEUnderlineRelativeSize = -1.0f; } nsTextPaintStyle::~nsTextPaintStyle() { mColor = nsnull; } PRBool nsTextPaintStyle::EnsureSufficientContrast(nscolor *aForeColor, nscolor *aBackColor) { if (!aForeColor || !aBackColor) return PR_FALSE; // If common colors are not initialized, mFrameBackgroundColor and // mSufficientContrast are not initialized. if (!mInitCommonColors && !InitCommonColors()) return PR_FALSE; // If the combination of selection background color and frame background color // is sufficient contrast, don't exchange the selection colors. PRInt32 backLuminosityDifference = NS_LUMINOSITY_DIFFERENCE(*aBackColor, mFrameBackgroundColor); if (backLuminosityDifference >= mSufficientContrast) return PR_FALSE; // Otherwise, we should use the higher-contrast color for the selection // background color. PRInt32 foreLuminosityDifference = NS_LUMINOSITY_DIFFERENCE(*aForeColor, mFrameBackgroundColor); if (backLuminosityDifference < foreLuminosityDifference) { nscolor tmpColor = *aForeColor; *aForeColor = *aBackColor; *aBackColor = tmpColor; return PR_TRUE; } return PR_FALSE; } nscolor nsTextPaintStyle::GetTextColor() { return mColor->mColor; } void nsTextPaintStyle::GetSelectionColors(nscolor* aForeColor, nscolor* aBackColor, PRBool* aBackIsTransparent) { NS_ASSERTION(aForeColor, "aForeColor is null"); NS_ASSERTION(aBackColor, "aBackColor is null"); NS_ASSERTION(aBackIsTransparent, "aBackIsTransparent is null"); if (!mInitSelectionColors && !InitSelectionColors()) { NS_ERROR("Fail to initialize selection colors"); return; } *aForeColor = mSelectionTextColor; *aBackColor = mSelectionBGColor; *aBackIsTransparent = mSelectionBGIsTransparent; } void nsTextPaintStyle::GetIMESelectionColors(SelectionType aSelectionType, nscolor* aForeColor, nscolor* aBackColor, PRBool* aBackIsTransparent) { NS_ASSERTION(aForeColor, "aForeColor is null"); NS_ASSERTION(aBackColor, "aBackColor is null"); NS_ASSERTION(aBackIsTransparent, "aBackIsTransparent is null"); nsIMEColor* IMEColor = GetIMEColor(aSelectionType); if (!IMEColor) { NS_ERROR("aSelectionType is invalid"); return; } if (!IMEColor->mInit) return; *aForeColor = IMEColor->mTextColor; *aBackColor = IMEColor->mBGColor; *aBackIsTransparent = IMEColor->mBGIsTransparent; } PRBool nsTextPaintStyle::GetIMEUnderline(SelectionType aSelectionType, nscolor* aLineColor, float* aRelativeSize) { NS_ASSERTION(aLineColor, "aLineColor is null"); NS_ASSERTION(aRelativeSize, "aRelativeSize is null"); nsIMEColor* IMEColor = GetIMEColor(aSelectionType); if (!IMEColor) { NS_ERROR("aSelectionType is invalid"); return PR_FALSE; } if (!IMEColor->mInit) return PR_FALSE; if (IMEColor->mUnderlineColor == NS_TRANSPARENT || mIMEUnderlineRelativeSize <= 0.0f) return PR_FALSE; *aLineColor = IMEColor->mUnderlineColor; *aRelativeSize = mIMEUnderlineRelativeSize; return PR_TRUE; } PRBool nsTextPaintStyle::InitCommonColors() { if (!mPresContext || !mStyleContext) return PR_FALSE; if (mInitCommonColors) return PR_TRUE; const nsStyleBackground* bg = nsCSSRendering::FindNonTransparentBackground(mStyleContext); NS_ASSERTION(bg, "Cannot find NonTransparentBackground."); mFrameBackgroundColor = bg->mBackgroundColor; nsILookAndFeel* look = mPresContext->LookAndFeel(); if (!look) return PR_FALSE; nscolor defaultWindowBackgroundColor, selectionTextColor, selectionBGColor; look->GetColor(nsILookAndFeel::eColor_TextSelectBackground, selectionBGColor); look->GetColor(nsILookAndFeel::eColor_TextSelectForeground, selectionTextColor); look->GetColor(nsILookAndFeel::eColor_WindowBackground, defaultWindowBackgroundColor); mSufficientContrast = PR_MIN(PR_MIN(NS_SUFFICIENT_LUMINOSITY_DIFFERENCE, NS_LUMINOSITY_DIFFERENCE(selectionTextColor, selectionBGColor)), NS_LUMINOSITY_DIFFERENCE(defaultWindowBackgroundColor, selectionBGColor)); mInitCommonColors = PR_TRUE; return PR_TRUE; } PRBool nsTextPaintStyle::InitSelectionColors() { if (!mPresContext || !mStyleContext) return PR_FALSE; if (mInitSelectionColors) return PR_TRUE; mSelectionBGIsTransparent = PR_FALSE; if (mContent && mSelectionStatus == nsISelectionController::SELECTION_ON) { nsRefPtr sc = nsnull; sc = mPresContext->StyleSet()-> ProbePseudoStyleFor(mContent->GetParent(), nsCSSPseudoElements::mozSelection, mStyleContext); // Use -moz-selection pseudo class. if (sc) { const nsStyleBackground* bg = sc->GetStyleBackground(); mSelectionBGIsTransparent = PRBool(bg->mBackgroundFlags & NS_STYLE_BG_COLOR_TRANSPARENT); if (!mSelectionBGIsTransparent) mSelectionBGColor = bg->mBackgroundColor; mSelectionTextColor = sc->GetStyleColor()->mColor; return PR_TRUE; } } nsILookAndFeel* look = mPresContext->LookAndFeel(); if (!look) return PR_FALSE; nscolor selectionBGColor; look->GetColor(nsILookAndFeel::eColor_TextSelectBackground, selectionBGColor); if (mSelectionStatus == nsISelectionController::SELECTION_ATTENTION) { look->GetColor(nsILookAndFeel::eColor_TextSelectBackgroundAttention, mSelectionBGColor); mSelectionBGColor = EnsureDifferentColors(mSelectionBGColor, selectionBGColor); } else if (mSelectionStatus != nsISelectionController::SELECTION_ON) { look->GetColor(nsILookAndFeel::eColor_TextSelectBackgroundDisabled, mSelectionBGColor); mSelectionBGColor = EnsureDifferentColors(mSelectionBGColor, selectionBGColor); } else { mSelectionBGColor = selectionBGColor; } look->GetColor(nsILookAndFeel::eColor_TextSelectForeground, mSelectionTextColor); // On MacOS X, we don't exchange text color and BG color. if (mSelectionTextColor == NS_DONT_CHANGE_COLOR) { mSelectionTextColor = EnsureDifferentColors(mColor->mColor, mSelectionBGColor); return PR_TRUE; } EnsureSufficientContrast(&mSelectionTextColor, &mSelectionBGColor); mInitSelectionColors = PR_TRUE; return PR_TRUE; } nsTextPaintStyle::nsIMEColor* nsTextPaintStyle::GetIMEColor(SelectionType aSelectionType) { PRInt32 index; switch (aSelectionType) { case nsISelectionController::SELECTION_IME_RAWINPUT: index = eIndexRawInput; break; case nsISelectionController::SELECTION_IME_SELECTEDRAWTEXT: index = eIndexSelRawText; break; case nsISelectionController::SELECTION_IME_CONVERTEDTEXT: index = eIndexConvText; break; case nsISelectionController::SELECTION_IME_SELECTEDCONVERTEDTEXT: index = eIndexSelConvText; break; default: NS_ERROR("aSelectionType is Invalid"); return nsnull; } nsIMEColor* IMEColor = &mIMEColor[index]; if (!IMEColor->mInit && !InitIMEColors(aSelectionType, IMEColor)) NS_ERROR("Fail to initialize IME color"); return IMEColor; } PRBool nsTextPaintStyle::InitIMEColors(SelectionType aSelectionType, nsIMEColor* aIMEColor) { if (!mPresContext || !aIMEColor) return PR_FALSE; NS_ASSERTION(!aIMEColor->mInit, "this is already initialized"); nsILookAndFeel::nsColorID foreColorID, backColorID, lineColorID; switch (aSelectionType) { case nsISelectionController::SELECTION_IME_RAWINPUT: foreColorID = nsILookAndFeel::eColor_IMERawInputForeground; backColorID = nsILookAndFeel::eColor_IMERawInputBackground; lineColorID = nsILookAndFeel::eColor_IMERawInputUnderline; break; case nsISelectionController::SELECTION_IME_SELECTEDRAWTEXT: foreColorID = nsILookAndFeel::eColor_IMESelectedRawTextForeground; backColorID = nsILookAndFeel::eColor_IMESelectedRawTextBackground; lineColorID = nsILookAndFeel::eColor_IMESelectedRawTextUnderline; break; case nsISelectionController::SELECTION_IME_CONVERTEDTEXT: foreColorID = nsILookAndFeel::eColor_IMEConvertedTextForeground; backColorID = nsILookAndFeel::eColor_IMEConvertedTextBackground; lineColorID = nsILookAndFeel::eColor_IMEConvertedTextUnderline; break; case nsISelectionController::SELECTION_IME_SELECTEDCONVERTEDTEXT: foreColorID = nsILookAndFeel::eColor_IMESelectedConvertedTextForeground; backColorID = nsILookAndFeel::eColor_IMESelectedConvertedTextBackground; lineColorID = nsILookAndFeel::eColor_IMESelectedConvertedTextUnderline; break; default: NS_ERROR("aSelectionType is Invalid"); return PR_FALSE; } nsILookAndFeel* look = mPresContext->LookAndFeel(); if (!look) return PR_FALSE; nscolor foreColor, backColor, lineColor; look->GetColor(foreColorID, foreColor); look->GetColor(backColorID, backColor); look->GetColor(lineColorID, lineColor); // Convert special color to actual color NS_ASSERTION(foreColor != NS_TRANSPARENT, "foreColor cannot be NS_TRANSPARENT"); NS_ASSERTION(backColor != NS_SAME_AS_FOREGROUND_COLOR, "backColor cannot be NS_SAME_AS_FOREGROUND_COLOR"); NS_ASSERTION(backColor != NS_40PERCENT_FOREGROUND_COLOR, "backColor cannot be NS_40PERCENT_FOREGROUND_COLOR"); PRBool backIsTransparent = PR_FALSE; if (backColor == NS_TRANSPARENT) backIsTransparent = PR_TRUE; foreColor = GetResolvedForeColor(foreColor, GetTextColor(), backColor); if (!backIsTransparent) EnsureSufficientContrast(&foreColor, &backColor); lineColor = GetResolvedForeColor(lineColor, foreColor, backColor); aIMEColor->mTextColor = foreColor; aIMEColor->mBGColor = backColor; aIMEColor->mBGIsTransparent = backIsTransparent; aIMEColor->mUnderlineColor = lineColor; aIMEColor->mInit = PR_TRUE; if (mIMEUnderlineRelativeSize == -1.0f) { look->GetMetric(nsILookAndFeel::eMetricFloat_IMEUnderlineRelativeSize, mIMEUnderlineRelativeSize); NS_ASSERTION(mIMEUnderlineRelativeSize >= 0.0f, "underline size must be larger than 0"); } return PR_TRUE; } inline nscolor Get40PercentColor(nscolor aForeColor, nscolor aBackColor) { nscolor foreColor = NS_RGBA(NS_GET_R(aForeColor), NS_GET_G(aForeColor), NS_GET_B(aForeColor), (PRUint8)(255 * 0.4f)); return NS_ComposeColors(aBackColor, foreColor); } nscolor nsTextPaintStyle::GetResolvedForeColor(nscolor aColor, nscolor aDefaultForeColor, nscolor aBackColor) { if (aColor == NS_SAME_AS_FOREGROUND_COLOR) return aDefaultForeColor; if (aColor != NS_40PERCENT_FOREGROUND_COLOR) return aColor; // Get actual background color nscolor actualBGColor = aBackColor; if (actualBGColor == NS_TRANSPARENT) { if (!mInitCommonColors && !InitCommonColors()) return aDefaultForeColor; actualBGColor = mFrameBackgroundColor; } return Get40PercentColor(aDefaultForeColor, actualBGColor); } //----------------------------------------------------------------------------- #ifdef ACCESSIBILITY NS_IMETHODIMP nsTextFrame::GetAccessible(nsIAccessible** aAccessible) { if (!IsEmpty() || GetNextInFlow()) { nsCOMPtr accService = do_GetService("@mozilla.org/accessibilityService;1"); if (accService) { return accService->CreateHTMLTextAccessible(NS_STATIC_CAST(nsIFrame*, this), aAccessible); } } return NS_ERROR_FAILURE; } #endif //----------------------------------------------------------------------------- NS_IMETHODIMP nsTextFrame::Init(nsIContent* aContent, nsIFrame* aParent, nsIFrame* aPrevInFlow) { NS_PRECONDITION(aContent->IsNodeOfType(nsINode::eTEXT), "Bogus content!"); nsresult rv = nsFrame::Init(aContent, aParent, aPrevInFlow); if (NS_SUCCEEDED(rv) && !aPrevInFlow && GetStyleText()->WhiteSpaceIsSignificant()) { // We care about our actual length in this case, so we can report the right // thing from HasTerminalNewline(). Since we're not a continuing frame, we // should map the whole content node. // Note that if we're created due to bidi splitting the bidi code // will override what we compute here, so it's ok. mContentLength = mContent->TextLength(); } return rv; } void nsTextFrame::Destroy() { if (mNextContinuation) { mNextContinuation->SetPrevInFlow(nsnull); } // Let the base class destroy the frame nsFrame::Destroy(); } class nsContinuingTextFrame : public nsTextFrame { public: friend nsIFrame* NS_NewContinuingTextFrame(nsIPresShell* aPresShell, nsStyleContext* aContext); NS_IMETHOD Init(nsIContent* aContent, nsIFrame* aParent, nsIFrame* aPrevInFlow); virtual void Destroy(); virtual nsIFrame* GetPrevContinuation() const { return mPrevContinuation; } NS_IMETHOD SetPrevContinuation(nsIFrame* aPrevContinuation) { NS_ASSERTION (!aPrevContinuation || GetType() == aPrevContinuation->GetType(), "setting a prev continuation with incorrect type!"); NS_ASSERTION (!nsSplittableFrame::IsInPrevContinuationChain(aPrevContinuation, this), "creating a loop in continuation chain!"); mPrevContinuation = aPrevContinuation; RemoveStateBits(NS_FRAME_IS_FLUID_CONTINUATION); return NS_OK; } virtual nsIFrame* GetPrevInFlowVirtual() const { return GetPrevInFlow(); } nsIFrame* GetPrevInFlow() const { return (GetStateBits() & NS_FRAME_IS_FLUID_CONTINUATION) ? mPrevContinuation : nsnull; } NS_IMETHOD SetPrevInFlow(nsIFrame* aPrevInFlow) { NS_ASSERTION (!aPrevInFlow || GetType() == aPrevInFlow->GetType(), "setting a prev in flow with incorrect type!"); NS_ASSERTION (!nsSplittableFrame::IsInPrevContinuationChain(aPrevInFlow, this), "creating a loop in continuation chain!"); mPrevContinuation = aPrevInFlow; AddStateBits(NS_FRAME_IS_FLUID_CONTINUATION); return NS_OK; } virtual nsIFrame* GetFirstInFlow() const; virtual nsIFrame* GetFirstContinuation() const; virtual void AddInlineMinWidth(nsIRenderingContext *aRenderingContext, InlineMinWidthData *aData); virtual void AddInlinePrefWidth(nsIRenderingContext *aRenderingContext, InlinePrefWidthData *aData); protected: nsContinuingTextFrame(nsStyleContext* aContext) : nsTextFrame(aContext) {} nsIFrame* mPrevContinuation; }; NS_IMETHODIMP nsContinuingTextFrame::Init(nsIContent* aContent, nsIFrame* aParent, nsIFrame* aPrevInFlow) { nsresult rv = nsTextFrame::Init(aContent, aParent, aPrevInFlow); if (aPrevInFlow) { nsIFrame* nextContinuation = aPrevInFlow->GetNextContinuation(); // Hook the frame into the flow SetPrevInFlow(aPrevInFlow); aPrevInFlow->SetNextInFlow(this); #ifdef IBMBIDI if (aPrevInFlow->GetStateBits() & NS_FRAME_IS_BIDI) { PRInt32 start, end; aPrevInFlow->GetOffsets(start, mContentOffset); nsPropertyTable *propTable = PresContext()->PropertyTable(); propTable->SetProperty(this, nsGkAtoms::embeddingLevel, propTable->GetProperty(aPrevInFlow, nsGkAtoms::embeddingLevel), nsnull, nsnull); propTable->SetProperty(this, nsGkAtoms::baseLevel, propTable->GetProperty(aPrevInFlow, nsGkAtoms::baseLevel), nsnull, nsnull); propTable->SetProperty(this, nsGkAtoms::charType, propTable->GetProperty(aPrevInFlow, nsGkAtoms::charType), nsnull, nsnull); if (nextContinuation) { SetNextContinuation(nextContinuation); nextContinuation->SetPrevContinuation(this); nextContinuation->GetOffsets(start, end); mContentLength = PR_MAX(1, start - mContentOffset); } mState |= NS_FRAME_IS_BIDI; } // prev frame is bidi #endif // IBMBIDI } return rv; } void nsContinuingTextFrame::Destroy() { if (mPrevContinuation || mNextContinuation) { nsSplittableFrame::RemoveFromFlow(this); } // Let the base class destroy the frame nsFrame::Destroy(); } nsIFrame* nsContinuingTextFrame::GetFirstInFlow() const { // Can't cast to |nsContinuingTextFrame*| because the first one isn't. nsIFrame *firstInFlow, *previous = NS_CONST_CAST(nsIFrame*, NS_STATIC_CAST(const nsIFrame*, this)); do { firstInFlow = previous; previous = firstInFlow->GetPrevInFlow(); } while (previous); return firstInFlow; } nsIFrame* nsContinuingTextFrame::GetFirstContinuation() const { // Can't cast to |nsContinuingTextFrame*| because the first one isn't. nsIFrame *firstContinuation, *previous = NS_CONST_CAST(nsIFrame*, NS_STATIC_CAST(const nsIFrame*, mPrevContinuation)); do { firstContinuation = previous; previous = firstContinuation->GetPrevContinuation(); } while (previous); return firstContinuation; } // XXX Do we want to do all the work for the first-in-flow or do the // work for each part? (Be careful of first-letter / first-line, though, // especially first-line!) Doing all the work on the first-in-flow has // the advantage of avoiding the potential for incremental reflow bugs, // but depends on our maintining the frame tree in reasonable ways even // for edge cases (block-within-inline splits, nextBidi, etc.) // XXX We really need to make :first-letter happen during frame // construction. // Needed for text frames in XUL. /* virtual */ nscoord nsTextFrame::GetMinWidth(nsIRenderingContext *aRenderingContext) { return nsLayoutUtils::MinWidthFromInline(this, aRenderingContext); } // Needed for text frames in XUL. /* virtual */ nscoord nsTextFrame::GetPrefWidth(nsIRenderingContext *aRenderingContext) { return nsLayoutUtils::PrefWidthFromInline(this, aRenderingContext); } /* virtual */ void nsContinuingTextFrame::AddInlineMinWidth(nsIRenderingContext *aRenderingContext, InlineMinWidthData *aData) { // Do nothing, since the first-in-flow accounts for everything. return; } /* virtual */ void nsContinuingTextFrame::AddInlinePrefWidth(nsIRenderingContext *aRenderingContext, InlinePrefWidthData *aData) { // Do nothing, since the first-in-flow accounts for everything. return; } //DRAW SELECTION ITERATOR USED FOR TEXTFRAMES ONLY //helper class for drawing multiply selected text class DrawSelectionIterator { public: DrawSelectionIterator(const SelectionDetails *aSelDetails, PRUnichar *aText, PRUint32 aTextLength, nsTextPaintStyle *aTextStyle, SelectionType aCareSelections); ~DrawSelectionIterator(); PRBool First(); PRBool Next(); PRBool IsDone(); PRBool IsLast(); PRUnichar * CurrentTextUnicharPtr(); char * CurrentTextCStrPtr(); PRUint32 CurrentLength(); PRBool IsBeforeOrAfter(); /** * Get foreground color, background color, whether the background is transparent, * and whether the current range is the normal selection. * * @param aForeColor [out] returns the foreground color of the current range. * @param aBackColor [out] returns the background color of the current range. * Note that this value is undefined if aBackIsTransparent * is true or if @return is false. * @param aBackIsTransparent [out] returns whether the background is transparent. * If true, the background is transparent. * Otherwise, it isn't so. * @return whether the current range is a normal selection. */ PRBool GetSelectionColors(nscolor *aForeColor, nscolor *aBackColor, PRBool *aBackIsTransparent); private: union { PRUnichar *mUniStr; char *mCStr; }; PRUint32 mLength; PRUint32 mCurrentIdx; PRUint32 mCurrentLength; nsTextPaintStyle* mOldStyle;//base new styles on this one??? const SelectionDetails *mDetails; PRBool mDone; PRUint8 * mTypes; PRBool mInit; //private methods void FillCurrentData(); }; DrawSelectionIterator::DrawSelectionIterator(const SelectionDetails *aSelDetails, PRUnichar *aText, PRUint32 aTextLength, nsTextPaintStyle* aTextStyle, SelectionType aCareSelections) :mOldStyle(aTextStyle) { NS_ASSERTION(aCareSelections, "aCareSelection value must not be zero!"); mDetails = aSelDetails; mCurrentIdx = 0; mUniStr = aText; mLength = aTextLength; mTypes = nsnull; mInit = PR_FALSE; if (!aSelDetails) { mDone = PR_TRUE; return; } mDone = (PRBool)(mCurrentIdx>=mLength); if (mDone) return; //special case for 1 selection. later const SelectionDetails *details = aSelDetails; if (details->mNext) { // go to next } else if (details->mStart == details->mEnd) { // no collapsed selections here! mDone = PR_TRUE; return; } else if (!(details->mType & aCareSelections)) { //if all we have is selection we DONT care about, do nothing mDone = PR_TRUE; return; } mTypes = new PRUint8[mLength]; if (!mTypes) return; memset(mTypes, 0, mLength); while (details) { if ((details->mType & aCareSelections) && (details->mStart != details->mEnd)) { mInit = PR_TRUE; // WE FOUND SOMETHING WE CARE ABOUT for (int i = details->mStart; i < details->mEnd; i++) { if ((PRUint32)i >= mLength) { NS_ASSERTION(0, "Selection Details out of range?"); return; } mTypes[i] |= details->mType; } } details= details->mNext; } if (!mInit) { // we have details but none that we care about. delete [] mTypes; mTypes = nsnull; mDone = PR_TRUE; // we are finished mInit = PR_TRUE; } } DrawSelectionIterator::~DrawSelectionIterator() { if (mTypes) delete [] mTypes; } void DrawSelectionIterator::FillCurrentData() { if (mDone) return; mCurrentIdx += mCurrentLength; // advance to this chunk mCurrentLength = 0; if (mCurrentIdx >= mLength) { mDone = PR_TRUE; return; } if (!mTypes) { if (mCurrentIdx < (PRUint32)mDetails->mStart) { mCurrentLength = mDetails->mStart; } else if (mCurrentIdx == (PRUint32)mDetails->mStart) {//start mCurrentLength = mDetails->mEnd-mCurrentIdx; } else if (mCurrentIdx > (PRUint32)mDetails->mStart)//last unselected part { mCurrentLength = mLength - mDetails->mEnd; } } else { uint8 typevalue = mTypes[mCurrentIdx]; while (mCurrentIdx+mCurrentLength < mLength && typevalue == mTypes[mCurrentIdx+mCurrentLength]) { mCurrentLength++; } } // never overrun past mLength if (mCurrentIdx+mCurrentLength > mLength) { mCurrentLength = mLength - mCurrentIdx; } } PRBool DrawSelectionIterator::First() { if (!mInit) return PR_FALSE; mCurrentIdx = 0; mCurrentLength = 0; if (!mTypes && mDetails->mStart == mDetails->mEnd)//no collapsed selections here! mDone = PR_TRUE; mDone = (mCurrentIdx+mCurrentLength) >= mLength; FillCurrentData(); return PR_TRUE; } PRBool DrawSelectionIterator::Next() { if (mDone || !mInit) return PR_FALSE; FillCurrentData();//advances to next chunk return PR_TRUE; } PRBool DrawSelectionIterator::IsLast() { return mDone || !mInit || mCurrentIdx + mCurrentLength >= mLength; } PRBool DrawSelectionIterator::IsDone() { return mDone || !mInit; } PRUnichar * DrawSelectionIterator::CurrentTextUnicharPtr() { return mUniStr+mCurrentIdx; } char * DrawSelectionIterator::CurrentTextCStrPtr() { return mCStr+mCurrentIdx; } PRUint32 DrawSelectionIterator::CurrentLength() { return mCurrentLength; } PRBool DrawSelectionIterator::GetSelectionColors(nscolor *aForeColor, nscolor *aBackColor, PRBool *aBackIsTransparent) { if (mTypes) { // Normal selection if (mTypes[mCurrentIdx] & nsTextPaintStyle::eNormalSelection) { mOldStyle->GetSelectionColors(aForeColor, aBackColor, aBackIsTransparent); return PR_TRUE; } // IME selections if (mTypes[mCurrentIdx] & nsTextPaintStyle::eIMESelections) { mOldStyle->GetIMESelectionColors(mTypes[mCurrentIdx], aForeColor, aBackColor, aBackIsTransparent); return PR_TRUE; } } // Non-supported Selection or Non-selection text *aBackIsTransparent = PR_FALSE; *aForeColor = mOldStyle->GetTextColor(); return PR_FALSE; } PRBool DrawSelectionIterator::IsBeforeOrAfter() { return mCurrentIdx != (PRUint32)mDetails->mStart; } //END DRAWSELECTIONITERATOR!! // Flag information used by rendering code. This information is // computed by the ResizeReflow code. The flags are stored in the // mState variable in the frame class private section. // Flag indicating that whitespace was skipped #define TEXT_SKIP_LEADING_WS 0x01000000 #define TEXT_HAS_MULTIBYTE 0x02000000 #define TEXT_IN_WORD 0x04000000 // This bit is set on the first frame in a continuation indicating // that it was chopped short because of :first-letter style. #define TEXT_FIRST_LETTER 0x08000000 #define TEXT_WAS_TRANSFORMED 0x10000000 // Bits in mState used for reflow flags #define TEXT_REFLOW_FLAGS 0x1F000000 #define TEXT_TRIMMED_WS 0x20000000 #define TEXT_OPTIMIZE_RESIZE 0x40000000 #define TEXT_BLINK_ON 0x80000000 #define TEXT_IS_ONLY_WHITESPACE 0x00100000 #define TEXT_ISNOT_ONLY_WHITESPACE 0x00200000 #define TEXT_WHITESPACE_FLAGS 0x00300000 #define TEXT_IS_END_OF_LINE 0x00400000 //---------------------------------------------------------------------- #if defined(DEBUG_rbs) || defined(DEBUG_bzbarsky) static void VerifyNotDirty(nsFrameState state) { PRBool isZero = state & NS_FRAME_FIRST_REFLOW; PRBool isDirty = state & NS_FRAME_IS_DIRTY; if (!isZero && isDirty) NS_WARNING("internal offsets may be out-of-sync"); } #define DEBUG_VERIFY_NOT_DIRTY(state) \ VerifyNotDirty(state) #else #define DEBUG_VERIFY_NOT_DIRTY(state) #endif nsIFrame* NS_NewTextFrame(nsIPresShell* aPresShell, nsStyleContext* aContext) { return new (aPresShell) nsTextFrame(aContext); } nsIFrame* NS_NewContinuingTextFrame(nsIPresShell* aPresShell, nsStyleContext* aContext) { return new (aPresShell) nsContinuingTextFrame(aContext); } nsTextFrame::~nsTextFrame() { if (0 != (mState & TEXT_BLINK_ON)) { nsBlinkTimer::RemoveBlinkFrame(this); } } nsIDocument* nsTextFrame::GetDocument(nsPresContext* aPresContext) { nsIDocument *result = nsnull; if (mContent) { result = mContent->GetDocument(); } if (!result && aPresContext) { result = aPresContext->PresShell()->GetDocument(); } return result; } NS_IMETHODIMP nsTextFrame::GetCursor(const nsPoint& aPoint, nsIFrame::Cursor& aCursor) { FillCursorInformationFromStyle(GetStyleUserInterface(), aCursor); if (NS_STYLE_CURSOR_AUTO == aCursor.mCursor) { aCursor.mCursor = NS_STYLE_CURSOR_TEXT; // If tabindex >= 0, use default cursor to indicate it's not selectable nsIFrame *ancestorFrame = this; while ((ancestorFrame = ancestorFrame->GetParent()) != nsnull) { nsIContent *ancestorContent = ancestorFrame->GetContent(); if (ancestorContent && ancestorContent->HasAttr(kNameSpaceID_None, nsGkAtoms::tabindex)) { nsAutoString tabIndexStr; ancestorContent->GetAttr(kNameSpaceID_None, nsGkAtoms::tabindex, tabIndexStr); if (!tabIndexStr.IsEmpty()) { PRInt32 rv, tabIndexVal = tabIndexStr.ToInteger(&rv); if (NS_SUCCEEDED(rv) && tabIndexVal >= 0) { aCursor.mCursor = NS_STYLE_CURSOR_DEFAULT; break; } } } } } return NS_OK; } nsIFrame* nsTextFrame::GetLastInFlow() const { nsTextFrame* lastInFlow = NS_CONST_CAST(nsTextFrame*, this); while (lastInFlow->GetNextInFlow()) { lastInFlow = NS_STATIC_CAST(nsTextFrame*, lastInFlow->GetNextInFlow()); } NS_POSTCONDITION(lastInFlow, "illegal state in flow chain."); return lastInFlow; } nsIFrame* nsTextFrame::GetLastContinuation() const { nsTextFrame* lastInFlow = NS_CONST_CAST(nsTextFrame*, this); while (lastInFlow->mNextContinuation) { lastInFlow = NS_STATIC_CAST(nsTextFrame*, lastInFlow->mNextContinuation); } NS_POSTCONDITION(lastInFlow, "illegal state in continuation chain."); return lastInFlow; } NS_IMETHODIMP nsTextFrame::CharacterDataChanged(nsPresContext* aPresContext, nsIContent* aChild, PRBool aAppend) { nsIFrame* targetTextFrame = this; PRBool markAllDirty = PR_TRUE; if (aAppend) { markAllDirty = PR_FALSE; nsTextFrame* frame = NS_STATIC_CAST(nsTextFrame*, GetLastInFlow()); frame->mState &= ~TEXT_WHITESPACE_FLAGS; frame->mState |= NS_FRAME_IS_DIRTY; targetTextFrame = frame; } if (markAllDirty) { // Mark this frame and all the next-in-flow frames as dirty and reset all // the content offsets and lengths to 0, since they no longer know what // content is ok to access. nsTextFrame* textFrame = this; while (textFrame) { textFrame->mState &= ~TEXT_WHITESPACE_FLAGS; textFrame->mState |= NS_FRAME_IS_DIRTY; textFrame->mContentOffset = 0; textFrame->mContentLength = 0; textFrame = NS_STATIC_CAST(nsTextFrame*, textFrame->GetNextContinuation()); } } // Ask the parent frame to reflow me. aPresContext->GetPresShell()->FrameNeedsReflow(targetTextFrame, nsIPresShell::eStyleChange); return NS_OK; } // When we fix nsTextFrame to handle bearing (character glyphs that // extend outside the frame) by giving it overflow area, we'll need to fix // this to use the overflow area as its bounds. class nsDisplayText : public nsDisplayItem { public: nsDisplayText(nsTextFrame* aFrame) : nsDisplayItem(aFrame) { MOZ_COUNT_CTOR(nsDisplayText); } #ifdef NS_BUILD_REFCNT_LOGGING virtual ~nsDisplayText() { MOZ_COUNT_DTOR(nsDisplayText); } #endif virtual nsIFrame* HitTest(nsDisplayListBuilder* aBuilder, nsPoint aPt) { return mFrame; } virtual void Paint(nsDisplayListBuilder* aBuilder, nsIRenderingContext* aCtx, const nsRect& aDirtyRect); NS_DISPLAY_DECL_NAME("Text") }; void nsDisplayText::Paint(nsDisplayListBuilder* aBuilder, nsIRenderingContext* aCtx, const nsRect& aDirtyRect) { NS_STATIC_CAST(nsTextFrame*, mFrame)-> PaintText(*aCtx, aBuilder->ToReferenceFrame(mFrame)); } NS_IMETHODIMP nsTextFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder, const nsRect& aDirtyRect, const nsDisplayListSet& aLists) { if (!IsVisibleForPainting(aBuilder)) return NS_OK; DO_GLOBAL_REFLOW_COUNT_DSP("nsTextFrame"); if ((0 != (mState & TEXT_BLINK_ON)) && nsBlinkTimer::GetBlinkIsOff()) return NS_OK; return aLists.Content()->AppendNewToTop(new (aBuilder) nsDisplayText(this)); } void nsTextFrame::PaintText(nsIRenderingContext& aRenderingContext, nsPoint aPt) { nsStyleContext* sc = mStyleContext; nsPresContext* presContext = PresContext(); nsCOMPtr content; PRInt32 offset, length; GetContentAndOffsetsForSelection(presContext, getter_AddRefs(content), &offset, &length); PRInt16 selectionValue; if (NS_FAILED(GetSelectionStatus(presContext, selectionValue))) selectionValue = nsISelectionController::SELECTION_NORMAL; nsTextPaintStyle ts(presContext, aRenderingContext, mStyleContext, content, selectionValue); SetupTextRunDirection(presContext, &aRenderingContext); if (ts.mSmallCaps || (0 != ts.mWordSpacing) || (0 != ts.mLetterSpacing) || ts.mJustifying) { PaintTextSlowly(presContext, aRenderingContext, sc, ts, aPt.x, aPt.y); } else { // Get the text fragment const nsTextFragment* frag = mContent->GetText(); if (!frag) { return; } // Choose rendering pathway based on rendering context performance // hint, whether it needs to be transformed, and whether it's // multi-byte PRBool hasMultiByteChars = (0 != (mState & TEXT_HAS_MULTIBYTE)); PRUint32 hints = 0; aRenderingContext.GetHints(hints); #ifdef IBMBIDI PRBool bidiEnabled = presContext->BidiEnabled(); #else const PRBool bidiEnabled = PR_FALSE; #endif // IBMBIDI // * BiDi text or text with multi-byte characters must always be // rendered as Unicode. // * Non-transformed, 1-byte text should always be rendered as // ASCII. // * Other transformed or 2-byte text should be rendered according // to the preference of the hint from the rendering context. if (bidiEnabled || hasMultiByteChars || ((0 == (hints & NS_RENDERING_HINT_FAST_8BIT_TEXT)) && (frag->Is2b() || (0 != (mState & TEXT_WAS_TRANSFORMED))))) { PaintUnicodeText(presContext, aRenderingContext, sc, ts, aPt.x, aPt.y); } else { PaintAsciiText(presContext, aRenderingContext, sc, ts, aPt.x, aPt.y); } } } PRBool nsTextFrame::IsChineseJapaneseLangGroup() { const nsStyleVisibility* visibility = mStyleContext->GetStyleVisibility(); if (visibility->mLangGroup == nsGkAtoms::Japanese || visibility->mLangGroup == nsGkAtoms::Chinese || visibility->mLangGroup == nsGkAtoms::Taiwanese || visibility->mLangGroup == nsGkAtoms::HongKongChinese) return PR_TRUE; return PR_FALSE; } /* * Currently only Unicode characters below 0x10000 have their spacing modified * by justification. If characters above 0x10000 turn out to need * justification spacing, that will require extra work. Currently, * this function must not include 0xd800 to 0xdbff because these characters * are surrogates. */ PRBool nsTextFrame::IsJustifiableCharacter(PRUnichar aChar, PRBool aLangIsCJ) { if (0x20u == aChar || 0xa0u == aChar) return PR_TRUE; if (aChar < 0x2150u) return PR_FALSE; if (aLangIsCJ && ( (0x2150u <= aChar && aChar <= 0x22ffu) || // Number Forms, Arrows, Mathematical Operators (0x2460u <= aChar && aChar <= 0x24ffu) || // Enclosed Alphanumerics (0x2580u <= aChar && aChar <= 0x27bfu) || // Block Elements, Geometric Shapes, Miscellaneous Symbols, Dingbats (0x27f0u <= aChar && aChar <= 0x2bffu) || // Supplemental Arrows-A, Braille Patterns, Supplemental Arrows-B, // Miscellaneous Mathematical Symbols-B, Supplemental Mathematical Operators, // Miscellaneous Symbols and Arrows (0x2e80u <= aChar && aChar <= 0x312fu) || // CJK Radicals Supplement, CJK Radicals Supplement, // Ideographic Description Characters, CJK Symbols and Punctuation, // Hiragana, Katakana, Bopomofo (0x3190u <= aChar && aChar <= 0xabffu) || // Kanbun, Bopomofo Extended, Katakana Phonetic Extensions, // Enclosed CJK Letters and Months, CJK Compatibility, // CJK Unified Ideographs Extension A, Yijing Hexagram Symbols, // CJK Unified Ideographs, Yi Syllables, Yi Radicals (0xf900u <= aChar && aChar <= 0xfaffu) || // CJK Compatibility Ideographs (0xff5eu <= aChar && aChar <= 0xff9fu) // Halfwidth and Fullwidth Forms(a part) )) return PR_TRUE; return PR_FALSE; } nsresult nsTextFrame::FillClusterBuffer(nsPresContext *aPresContext, const PRUnichar *aText, PRUint32 aLength, nsAutoPRUint8Buffer& aClusterBuffer) { nsresult rv = aClusterBuffer.GrowTo(aLength); NS_ENSURE_SUCCESS(rv, rv); // Fill in the cluster hint information, if it's available. nsCOMPtr acx; PRUint32 clusterHint = 0; nsIPresShell *shell = aPresContext->GetPresShell(); if (shell) { rv = shell->CreateRenderingContext(this, getter_AddRefs(acx)); NS_ENSURE_SUCCESS(rv, rv); // Find the font metrics for this text nsLayoutUtils::SetFontFromStyle(acx, mStyleContext); acx->GetHints(clusterHint); clusterHint &= NS_RENDERING_HINT_TEXT_CLUSTERS; } if (clusterHint) { rv = acx->GetClusterInfo(aText, aLength, aClusterBuffer.mBuffer); } else { memset(aClusterBuffer.mBuffer, 1, sizeof(PRInt8) * aLength); } return rv; } inline PRBool IsEndOfLine(nsFrameState aState) { return (aState & TEXT_IS_END_OF_LINE) ? PR_TRUE : PR_FALSE; } void nsTextFrame::SetupTextRunDirection(nsPresContext* aPresContext, nsIRenderingContext* aContext) { PRBool isRTL = aPresContext->BidiEnabled() && (NS_GET_EMBEDDING_LEVEL(this) & 1); aContext->SetTextRunRTL(isRTL); } /** * 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. */ void nsTextFrame::PrepareUnicodeText(nsTextTransformer& aTX, nsAutoIndexBuffer* aIndexBuffer, nsAutoTextBuffer* aTextBuffer, PRInt32* aTextLen, PRBool aForceArabicShaping, PRIntn* aJustifiableCharCount, PRBool aRemoveMultipleTrimmedWS) { // Setup transform to operate starting in the content at our content // offset aTX.Init(this, mContent, mContentOffset, aForceArabicShaping); PRInt32 strInx = mContentOffset; PRInt32* indexp = aIndexBuffer ? aIndexBuffer->mBuffer : nsnull; // Skip over the leading whitespace PRInt32 n = mContentLength; if (0 != (mState & TEXT_SKIP_LEADING_WS)) { PRBool isWhitespace, wasTransformed; PRInt32 wordLen, contentLen; // Set maximum word length. This is an ABUSE of the variable // because on entry, this is DOM content length, but on exit, // GetNextWord returns a transformed-string length in here! wordLen = mContentOffset + mContentLength; aTX.GetNextWord(PR_FALSE, &wordLen, &contentLen, &isWhitespace, &wasTransformed); // we trip this assertion in bug 31053, but I think it's unnecessary //NS_ASSERTION(isWhitespace, "mState and content are out of sync"); if (isWhitespace) { if (nsnull != indexp) { // 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) { *indexp++ = strInx; } } n -= contentLen; if(n<0) NS_WARNING("mContentLength is < FragmentLength"); } } // Rescan the content and transform it. Stop when we have consumed // mContentLength characters. PRUint8 textTransform = GetStyleText()->mTextTransform; PRBool inWord = (TEXT_IN_WORD & mState) ? PR_TRUE : PR_FALSE; PRInt32 column = mColumn; PRInt32 textLength = 0; PRInt32 dstOffset = 0; nsAutoTextBuffer tmpTextBuffer; nsAutoTextBuffer* textBuffer = aTextBuffer; if (!textBuffer && aJustifiableCharCount) textBuffer = &tmpTextBuffer; while (n > 0) { PRUnichar* bp; PRBool isWhitespace, wasTransformed; PRInt32 wordLen, contentLen; // Set maximum word length. This is an ABUSE of the variable // because on entry, this is DOM content length, but on exit, // GetNextWord returns a transformed-string length in here! wordLen = mContentOffset + mContentLength; // Get the next word bp = aTX.GetNextWord(inWord, &wordLen, &contentLen, &isWhitespace, &wasTransformed); if (nsnull == bp) { if (indexp) { while (--n >= 0) { *indexp++ = strInx; } } break; } inWord = PR_FALSE; if (isWhitespace) { 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 != indexp) { *indexp++ = strInx; strInx += wordLen; } } else if ('\n' == bp[0]) { if (nsnull != indexp) { *indexp++ = strInx; } break; } else if (nsnull != indexp) { if (1 == wordLen) { // 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) { *indexp++ = strInx; } strInx++; } else { // Point mapping indicies at each content index in the word PRInt32 i = contentLen; while (--i >= 0) { *indexp++ = strInx++; } } } } else { PRInt32 i = contentLen; if (nsnull != indexp) { // Point mapping indices at each content index in the word if (!wasTransformed) { while (--i >= 0) { *indexp++ = strInx++; } } else { PRUnichar* tp = bp; PRBool caseChanged = textTransform == NS_STYLE_TEXT_TRANSFORM_UPPERCASE || textTransform == NS_STYLE_TEXT_TRANSFORM_CAPITALIZE; while (--i >= 0) { PRUnichar ch = aTX.GetContentCharAt(mContentOffset + indexp - aIndexBuffer->mBuffer); if (IS_DISCARDED(ch) || ch == '\n') { *indexp++ = strInx; continue; } // Point lam and alef to lamalef in shaped text if (aTX.NeedsArabicShaping()) { if (IS_LAM(ch) && IS_LAMALEF(*tp)) { // No need to check for index > length, since // GetContentCharAt() checks PRUnichar ch1 = aTX.GetContentCharAt(mContentOffset + indexp + 1 - aIndexBuffer->mBuffer); if (IS_ALEF(ch1)) { *indexp++ = strInx; --i; } } } *indexp++ = strInx++; // Point any capitalized German ß to 'SS' if (caseChanged && ch == kSZLIG && *tp == PRUnichar('S')) { ++strInx; ++tp; } ++tp; } } } } // Grow the buffer before we run out of room. if (textBuffer != nsnull && dstOffset + wordLen > textBuffer->mBufferLen) { nsresult rv = textBuffer->GrowBy(wordLen); if (NS_FAILED(rv)) { break; } } column += wordLen; textLength += wordLen; n -= contentLen; if (textBuffer != nsnull) { memcpy(textBuffer->mBuffer + dstOffset, bp, sizeof(PRUnichar)*wordLen); } dstOffset += wordLen; } #ifdef DEBUG if (aIndexBuffer) { NS_ASSERTION(indexp <= aIndexBuffer->mBuffer + aIndexBuffer->mBufferLen, "yikes - we just overwrote memory"); } if (textBuffer) { NS_ASSERTION(dstOffset <= textBuffer->mBufferLen, "yikes - we just overwrote memory"); } #endif // Remove trailing whitespace if it was trimmed after reflow // TEXT_TRIMMED_WS can be set in measureText during reflow, and // nonexitent text buffer may occur in this situation. if (TEXT_TRIMMED_WS & mState && textBuffer) { while (--dstOffset >= 0) { PRUnichar ch = textBuffer->mBuffer[dstOffset]; if (XP_IS_SPACE(ch)) textLength--; else break; if (!aRemoveMultipleTrimmedWS) break; } } if (aIndexBuffer) { PRInt32* ip = aIndexBuffer->mBuffer; // Make sure no indexes point beyond text length for (PRInt32 i = mContentLength - 1; i >= 0; i--) { if (ip[i] > textLength + mContentOffset) ip[i] = textLength + mContentOffset; else break; } ip[mContentLength] = ip[mContentLength-1]; if ((ip[mContentLength] - mContentOffset) < textLength) { // Must set up last one for selection beyond edge if in boundary ip[mContentLength] = textLength + mContentOffset; } } *aTextLen = textLength; if (aJustifiableCharCount && textBuffer) { PRBool isCJ = IsChineseJapaneseLangGroup(); PRIntn numJustifiableCharacter = 0; PRInt32 justifiableRange = textLength; if (IsEndOfLine(mState)) justifiableRange--; for (PRInt32 i = 0; i < justifiableRange; i++) { if (IsJustifiableCharacter(textBuffer->mBuffer[i], isCJ)) numJustifiableCharacter++; } *aJustifiableCharCount = numJustifiableCharacter; } } //#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 void nsTextFrame::PaintTextDecorations(nsIRenderingContext& aRenderingContext, nsStyleContext* aStyleContext, nsPresContext* aPresContext, nsTextPaintStyle& aTextStyle, nscoord aX, nscoord aY, nscoord aWidth, PRBool aRightToLeftText, PRUnichar *aText, /*=nsnull*/ SelectionDetails *aDetails,/*= nsnull*/ PRUint32 aIndex, /*= 0*/ PRUint32 aLength, /*= 0*/ const nscoord* aSpacing /* = nsnull*/ ) { // Quirks mode text decoration are rendered by children; see bug 1777 // In non-quirks mode, nsHTMLContainer::Paint and nsBlockFrame::Paint // does the painting of text decorations. if (eCompatibility_NavQuirks == aPresContext->CompatibilityMode()) { nscolor overColor, underColor, strikeColor; PRBool useOverride = PR_FALSE; nscolor overrideColor; PRUint8 decorations = NS_STYLE_TEXT_DECORATION_NONE; // A mask of all possible decorations. PRUint8 decorMask = NS_STYLE_TEXT_DECORATION_UNDERLINE | NS_STYLE_TEXT_DECORATION_OVERLINE | NS_STYLE_TEXT_DECORATION_LINE_THROUGH; nsStyleContext* context = aStyleContext; PRBool hasDecorations = context->HasTextDecorations(); while (hasDecorations) { const nsStyleTextReset* styleText = context->GetStyleTextReset(); if (!useOverride && (NS_STYLE_TEXT_DECORATION_OVERRIDE_ALL & styleText->mTextDecoration)) { // This handles the La // la la case. The link underline should be green. useOverride = PR_TRUE; overrideColor = context->GetStyleColor()->mColor; } PRUint8 useDecorations = decorMask & styleText->mTextDecoration; if (useDecorations) {// a decoration defined here nscolor color = context->GetStyleColor()->mColor; if (NS_STYLE_TEXT_DECORATION_UNDERLINE & useDecorations) { underColor = useOverride ? overrideColor : color; decorMask &= ~NS_STYLE_TEXT_DECORATION_UNDERLINE; decorations |= NS_STYLE_TEXT_DECORATION_UNDERLINE; } if (NS_STYLE_TEXT_DECORATION_OVERLINE & useDecorations) { overColor = useOverride ? overrideColor : color; decorMask &= ~NS_STYLE_TEXT_DECORATION_OVERLINE; decorations |= NS_STYLE_TEXT_DECORATION_OVERLINE; } if (NS_STYLE_TEXT_DECORATION_LINE_THROUGH & useDecorations) { strikeColor = useOverride ? overrideColor : color; decorMask &= ~NS_STYLE_TEXT_DECORATION_LINE_THROUGH; decorations |= NS_STYLE_TEXT_DECORATION_LINE_THROUGH; } } if (0 == decorMask) break; context = context->GetParent(); if (!context) break; hasDecorations = context->HasTextDecorations(); } nscoord offset; nscoord size; nscoord baseline = mAscent; 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(); while(aDetails){ const nscoord* sp= aSpacing; PRInt32 startOffset = 0; PRInt32 textWidth = 0; 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 (sp) { for (i = 0; i < start;i ++){ startOffset += *sp ++; } } else aRenderingContext.GetWidth(aText, start, startOffset); } if (sp){ for (i = start; i < end;i ++){ textWidth += *sp ++; } } else aRenderingContext.GetWidth(aText + start, PRUint32(end - start), textWidth); } nscolor lineColor; float relativeSize; nscoord offset, size; nscoord baseline = mAscent; switch (aDetails->mType) { case nsISelectionController::SELECTION_NORMAL: break; case nsISelectionController::SELECTION_SPELLCHECK: aTextStyle.mNormalFont->GetUnderline(offset, size); aRenderingContext.SetLineStyle(nsLineStyle_kDotted); aRenderingContext.SetColor(NS_RGB(255,0,0)); /* * If the rendering context is drawing text from right to left, * reverse the coordinates of the underline to match. */ if (aRightToLeftText) { nscoord rightEdge = aX + aWidth; aRenderingContext.DrawLine(rightEdge - textWidth - startOffset, aY + baseline - offset, rightEdge - startOffset, aY + baseline - offset); } else { aRenderingContext.DrawLine(aX + startOffset, aY + baseline - offset, aX + startOffset + textWidth, aY + baseline - offset); } break; case nsISelectionController::SELECTION_IME_RAWINPUT: case nsISelectionController::SELECTION_IME_CONVERTEDTEXT: case nsISelectionController::SELECTION_IME_SELECTEDRAWTEXT: case nsISelectionController::SELECTION_IME_SELECTEDCONVERTEDTEXT: if (aTextStyle.GetIMEUnderline(aDetails->mType, &lineColor, &relativeSize)) { aTextStyle.mNormalFont->GetUnderline(offset, size); aRenderingContext.SetColor(lineColor); /* * If the rendering context is drawing text from right to left, * reverse the coordinates of the underline to match. */ nscoord leftEdge = aRightToLeftText ? aX + aWidth - startOffset - textWidth + size : aX + startOffset + size; aRenderingContext.FillRect(leftEdge, aY + baseline - offset, textWidth - 2 * size, (nscoord)(relativeSize * size)); } break; default: NS_ASSERTION(0,"what type of selection do i not know about?"); break; } } } aDetails = aDetails->mNext; } } } nsresult nsTextFrame::GetContentAndOffsetsForSelection(nsPresContext *aPresContext, nsIContent **aContent, PRInt32 *aOffset, PRInt32 *aLength) { if (!aContent || !aOffset || !aLength) return NS_ERROR_NULL_POINTER; //ARE WE GENERATED?? *aContent = nsnull; *aOffset = mContentOffset; *aLength = mContentLength; nsIFrame *parent = GetParent(); if (parent) { if ((mState & NS_FRAME_GENERATED_CONTENT) != 0)//parent is generated so so are we. { //we COULD check the previous sibling but I dont think that is reliable *aContent = parent->GetContent(); if(!*aContent) return NS_ERROR_FAILURE; NS_ADDREF(*aContent); //ARE WE A BEFORE FRAME? if not then we assume we are an after frame. this may be bad later nsIFrame *grandParent = parent->GetParent(); if (grandParent) { nsIFrame *firstParent = grandParent->GetFirstChild(nsnull); if (firstParent) { *aLength = 0; if (firstParent == parent) //then our parent is the first child of granddad. use BEFORE { *aOffset = 0; } else { *aOffset = (*aContent)->GetChildCount(); } } else return NS_OK; } } } //END GENERATED BLOCK if (!*aContent) { *aContent = mContent; NS_IF_ADDREF(*aContent); } return NS_OK; } //--------------------------------------------------------- nsresult nsTextFrame::GetTextInfoForPainting(nsPresContext* aPresContext, nsIPresShell** aPresShell, nsISelectionController** aSelectionController, PRBool& aDisplayingSelection, PRBool& aIsPaginated, PRBool& aIsSelected, PRBool& aHideStandardSelection, PRInt16& aSelectionValue) { NS_ENSURE_ARG_POINTER(aPresContext); NS_ENSURE_ARG_POINTER(aPresShell); NS_ENSURE_ARG_POINTER(aSelectionController); //get the presshell NS_IF_ADDREF(*aPresShell = aPresContext->GetPresShell()); if (!*aPresShell) return NS_ERROR_FAILURE; //get the selection controller nsresult rv = GetSelectionController(aPresContext, aSelectionController); if (NS_FAILED(rv) || !(*aSelectionController)) return NS_ERROR_FAILURE; (*aSelectionController)->GetDisplaySelection(&aSelectionValue); if (aPresContext->IsRenderingOnlySelection()) { aIsPaginated = PR_TRUE; aDisplayingSelection = PR_TRUE; } else { aIsPaginated = PR_FALSE; aDisplayingSelection = (aSelectionValue > nsISelectionController::SELECTION_HIDDEN); } PRInt16 textSel=0; (*aSelectionController)->GetSelectionFlags(&textSel); if (!(textSel & nsISelectionDisplay::DISPLAY_TEXT)) aDisplayingSelection = PR_FALSE; // the spellcheck selection should be visible all the time aHideStandardSelection = !aDisplayingSelection; if (!aDisplayingSelection){ nsCOMPtr spellcheckSelection; (*aSelectionController)->GetSelection(nsISelectionController::SELECTION_SPELLCHECK, getter_AddRefs(spellcheckSelection)); if (spellcheckSelection){ PRBool iscollapsed = PR_FALSE; spellcheckSelection->GetIsCollapsed(&iscollapsed); if (!iscollapsed) aDisplayingSelection = PR_TRUE; } } // Transform text from content into renderable form // XXX If the text fragment is already Unicode and the text wasn't // transformed when we formatted it, then there's no need to do all // this and we should just render the text fragment directly. See // PaintAsciiText()... nsIDocument *doc = (*aPresShell)->GetDocument(); if (!doc) return NS_ERROR_FAILURE; aIsSelected = (GetStateBits() & NS_FRAME_SELECTED_CONTENT) == NS_FRAME_SELECTED_CONTENT; return NS_OK; } nsresult nsTextFrame::GetSelectionStatus(nsPresContext* aPresContext, PRInt16& aSelectionValue) { NS_ENSURE_ARG_POINTER(aPresContext); // get the selection controller nsCOMPtr selectionController; nsresult rv = GetSelectionController(aPresContext, getter_AddRefs(selectionController)); if (NS_FAILED(rv) || !selectionController) return NS_ERROR_FAILURE; selectionController->GetDisplaySelection(&aSelectionValue); return NS_OK; } PRBool nsTextFrame::IsTextInSelection() { nsCOMPtr selCon; nsCOMPtr shell; PRBool displaySelection; PRBool isPaginated; PRBool isSelected; PRBool hideStandardSelection; PRInt16 selectionValue; nsPresContext* presContext = PresContext(); if (NS_FAILED(GetTextInfoForPainting(presContext, getter_AddRefs(shell), getter_AddRefs(selCon), displaySelection, isPaginated, isSelected, hideStandardSelection, selectionValue))) { return PR_FALSE; } // Make enough space to transform nsAutoTextBuffer paintBuffer; nsAutoIndexBuffer indexBuffer; if (NS_FAILED(indexBuffer.GrowTo(mContentLength + 1))) { return PR_FALSE; } // Transform text from content into renderable form // XXX If the text fragment is already Unicode and the text wasn't // transformed when we formatted it, then there's no need to do all // this and we should just render the text fragment directly. See // PaintAsciiText()... nsTextTransformer tx(presContext); PRInt32 textLength; // no need to worry about justification, that's always on the slow path PrepareUnicodeText(tx, &indexBuffer, &paintBuffer, &textLength); PRInt32* ip = indexBuffer.mBuffer; PRUnichar* text = paintBuffer.mBuffer; isSelected = PR_FALSE; if (0 != textLength) { SelectionDetails *details = nsnull; nsCOMPtr content; PRInt32 offset; PRInt32 length; nsresult rv = GetContentAndOffsetsForSelection(presContext, getter_AddRefs(content), &offset, &length); if (NS_SUCCEEDED(rv) && content) { details = GetFrameSelection()->LookUpSelection(content, mContentOffset, mContentLength, PR_FALSE); } //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; } //while we have substrings... //PRBool drawn = PR_FALSE; DrawSelectionIterator iter(details, text, (PRUint32)textLength, nsnull, nsTextPaintStyle::eNormalSelection); if (!iter.IsDone() && iter.First()) { isSelected = PR_TRUE; } sdptr = details; if (details) { while ((sdptr = details->mNext) != nsnull) { delete details; details = sdptr; } delete details; } } return isSelected; } PRBool nsTextFrame::IsVisibleInSelection(nsISelection* aSelection) { // Check the quick way first PRBool isSelected = (mState & NS_FRAME_SELECTED_CONTENT) == NS_FRAME_SELECTED_CONTENT; if (!isSelected) return PR_FALSE; return IsTextInSelection(); } void nsTextFrame::PaintUnicodeText(nsPresContext* aPresContext, nsIRenderingContext& aRenderingContext, nsStyleContext* aStyleContext, nsTextPaintStyle& aTextStyle, nscoord dx, nscoord dy) { nsCOMPtr selCon; nsCOMPtr shell; PRBool displaySelection,canDarkenColor=PR_FALSE; PRBool isPaginated; PRBool isSelected; PRBool hideStandardSelection; PRInt16 selectionValue; #ifdef IBMBIDI PRBool isOddLevel = PR_FALSE; #endif if (NS_FAILED(GetTextInfoForPainting(aPresContext, getter_AddRefs(shell), getter_AddRefs(selCon), displaySelection, isPaginated, isSelected, hideStandardSelection, selectionValue))) { return; } if(isPaginated){ canDarkenColor = CanDarken(aPresContext); } // Make enough space to transform nsAutoTextBuffer paintBuffer; nsAutoIndexBuffer indexBuffer; if (displaySelection) { if (NS_FAILED(indexBuffer.GrowTo(mContentLength + 1))) { return; } } nscoord width = mRect.width; // Transform text from content into renderable form // XXX If the text fragment is already Unicode and the text wasn't // transformed when we formatted it, then there's no need to do all // this and we should just render the text fragment directly. See // PaintAsciiText()... nsTextTransformer tx(aPresContext); PRInt32 textLength; // In whitespace:-moz-pre-wrap it's possible that we trimmed multiple // spaces from the end of line because they did not fit in the line. // In that case, all those spaces need to be removed before painting. PRBool removeMultipleTrimmedWS = NS_STYLE_WHITESPACE_MOZ_PRE_WRAP == GetStyleText()->mWhiteSpace; // no need to worry about justification, that's always on the slow path PrepareUnicodeText(tx, (displaySelection ? &indexBuffer : nsnull), &paintBuffer, &textLength, PR_FALSE, nsnull, removeMultipleTrimmedWS); PRInt32* ip = indexBuffer.mBuffer; PRUnichar* text = paintBuffer.mBuffer; if (0 != textLength) { #ifdef IBMBIDI PRBool isRightToLeftOnBidiPlatform = PR_FALSE; PRBool isBidiSystem = PR_FALSE; nsCharType charType = eCharType_LeftToRight; if (aPresContext->BidiEnabled()) { isBidiSystem = aPresContext->IsBidiSystem(); isOddLevel = NS_GET_EMBEDDING_LEVEL(this) & 1; charType = (nsCharType)NS_PTR_TO_INT32(aPresContext->PropertyTable()->GetProperty(this, nsGkAtoms::charType)); isRightToLeftOnBidiPlatform = (isBidiSystem && (eCharType_RightToLeft == charType || eCharType_RightToLeftArabic == charType)); if (isRightToLeftOnBidiPlatform) { // indicate that the platform should use its native // capabilities to reorder the text with right-to-left // base direction aRenderingContext.SetRightToLeftText(PR_TRUE); } nsBidiPresUtils* bidiUtils = aPresContext->GetBidiUtils(); if (bidiUtils) { #ifdef DEBUG PRInt32 rememberTextLength = textLength; #endif PRUint32 hints = 0; aRenderingContext.GetHints(hints); bidiUtils->ReorderUnicodeText(text, textLength, charType, isOddLevel, isBidiSystem, (hints & NS_RENDERING_HINT_NEW_TEXT_RUNS) != 0); NS_ASSERTION(rememberTextLength == textLength, "Bidi formatting changed text length"); } } #endif // IBMBIDI if (!displaySelection || !isSelected ) //draw text normally { // When there is no selection showing, use the fastest and // simplest rendering approach aRenderingContext.SetColor(nsCSSRendering::TransformColor(aTextStyle.mColor->mColor,canDarkenColor)); aRenderingContext.DrawString(text, PRUint32(textLength), dx, dy + mAscent); PaintTextDecorations(aRenderingContext, aStyleContext, aPresContext, aTextStyle, dx, dy, width, PR_FALSE); } else { //we draw according to selection rules SelectionDetails *details = nsnull; nsCOMPtr content; PRInt32 offset; PRInt32 length; nsresult rv = GetContentAndOffsetsForSelection(aPresContext, getter_AddRefs(content), &offset, &length); if (NS_SUCCEEDED(rv) && content) { details = GetFrameSelection()->LookUpSelection(content, mContentOffset, mContentLength, PR_FALSE); } //where are the selection points "really" SelectionDetails *sdptr = details; while (sdptr){ sdptr->mStart = ip[sdptr->mStart] - mContentOffset; sdptr->mEnd = ip[sdptr->mEnd] - mContentOffset; #ifdef SUNCTL nsCOMPtr ctlObj; ctlObj = do_CreateInstance(kLECID, &rv); if (NS_FAILED(rv)) { NS_WARNING("Cell based cursor movement will not be supported\n"); ctlObj = nsnull; } else { PRInt32 start, end; PRBool needsCTL = PR_FALSE; ctlObj->NeedsCTLFix(text, sdptr->mStart, sdptr->mEnd, &needsCTL); if (needsCTL && (sdptr->mEnd < textLength)) { ctlObj->GetRangeOfCluster(text, PRInt32(textLength), sdptr->mEnd, &start, &end); if (sdptr->mStart > sdptr->mEnd) /* Left Edge */ sdptr->mEnd = start; else sdptr->mEnd = end; } /* Always start selection from a Right Edge */ if (needsCTL && (sdptr->mStart > 0)) { ctlObj->GetRangeOfCluster(text, PRInt32(textLength), sdptr->mStart, &start, &end); sdptr->mStart = end; } } #endif /* SUNCTL */ #ifdef IBMBIDI AdjustSelectionPointsForBidi(sdptr, textLength, CHARTYPE_IS_RTL(charType), isOddLevel, isBidiSystem); #endif sdptr = sdptr->mNext; } if (!hideStandardSelection || displaySelection) { /* * Text is drawn by drawing the entire string every time, but * using clip regions to control which part of the text is shown * (selected or unselected.) We do this because you can't * assume that the layout of a part of text will be the same * when it's drawn apart from the entire string. This is true * in languages like arabic, where shaping affects entire words. * Simply put: length("abcd") != length("ab") + length("cd") in * some languages. */ // See if this rendering backend supports getting cluster // information. PRUint32 clusterHint = 0; aRenderingContext.GetHints(clusterHint); clusterHint &= NS_RENDERING_HINT_TEXT_CLUSTERS; //while we have substrings... //PRBool drawn = PR_FALSE; DrawSelectionIterator iter(details, text, (PRUint32)textLength, &aTextStyle, nsTextPaintStyle::eAllSelections); if (!iter.IsDone() && iter.First()) { nscoord currentX = dx; nscoord newWidth;//temp #ifdef IBMBIDI // Simon - display substrings RTL in RTL frame nscoord FrameWidth = 0; if (isRightToLeftOnBidiPlatform) if (NS_SUCCEEDED(aRenderingContext.GetWidth(text, textLength, FrameWidth))) currentX = dx + FrameWidth; #endif while (!iter.IsDone()) { PRUnichar *currenttext = iter.CurrentTextUnicharPtr(); PRUint32 currentlength= iter.CurrentLength(); nscolor currentFGColor, currentBKColor; PRBool isCurrentBKColorTransparent; PRBool isSelection = iter.GetSelectionColors(¤tFGColor, ¤tBKColor, &isCurrentBKColorTransparent); if (currentlength > 0) { if (clusterHint) { PRUint32 tmpWidth; rv = aRenderingContext.GetRangeWidth(text, textLength, currenttext - text, (currenttext - text) + currentlength, tmpWidth); newWidth = nscoord(tmpWidth); } else { rv = aRenderingContext.GetWidth(currenttext, currentlength,newWidth); //ADJUST FOR CHAR SPACING } if (NS_SUCCEEDED(rv)) { if (isRightToLeftOnBidiPlatform) currentX -= newWidth; if (isSelection && !isPaginated) {//DRAW RECT HERE!!! if (!isCurrentBKColorTransparent) { aRenderingContext.SetColor(currentBKColor); aRenderingContext.FillRect(currentX, dy, newWidth, mRect.height); } } } else { newWidth = 0; } } else { newWidth = 0; } aRenderingContext.PushState(); nsRect rect(currentX, dy, newWidth, mRect.height); aRenderingContext.SetClipRect(rect, nsClipCombine_kIntersect); if (isPaginated && !iter.IsBeforeOrAfter()) { aRenderingContext.SetColor(nsCSSRendering::TransformColor(aTextStyle.mColor->mColor,canDarkenColor)); aRenderingContext.DrawString(text, PRUint32(textLength), dx, dy + mAscent); } else if (!isPaginated) { aRenderingContext.SetColor(nsCSSRendering::TransformColor(currentFGColor,canDarkenColor)); aRenderingContext.DrawString(text, PRUint32(textLength), dx, dy + mAscent); } aRenderingContext.PopState(); #ifdef IBMBIDI if (!isRightToLeftOnBidiPlatform) #endif currentX += newWidth; // increment twips X start iter.Next(); } } else if (!isPaginated) { aRenderingContext.SetColor(nsCSSRendering::TransformColor(aTextStyle.mColor->mColor,canDarkenColor)); aRenderingContext.DrawString(text, PRUint32(textLength), dx, dy + mAscent); } } PaintTextDecorations(aRenderingContext, aStyleContext, aPresContext, aTextStyle, dx, dy, width, isRightToLeftOnBidiPlatform, text, details, 0, (PRUint32)textLength, nsnull); sdptr = details; if (details){ while ((sdptr = details->mNext) != nsnull) { delete details; details = sdptr; } delete details; } } #ifdef IBMBIDI if (isRightToLeftOnBidiPlatform) { // indicate that future text should not be reordered with // right-to-left base direction aRenderingContext.SetRightToLeftText(PR_FALSE); } #endif // IBMBIDI } } //measure Spaced Textvoid nsresult nsTextFrame::GetPositionSlowly(nsIRenderingContext* aRendContext, const nsPoint& aPoint, nsIContent** aNewContent, PRInt32& aOffset) { // pre-condition tests NS_PRECONDITION(aRendContext && aNewContent, "null arg"); if (!aRendContext || !aNewContent) { return NS_ERROR_NULL_POINTER; } // initialize out param *aNewContent = nsnull; nsPresContext* presContext = PresContext(); nsTextStyle ts(presContext, *aRendContext, mStyleContext); SetupTextRunDirection(presContext, aRendContext); if (!ts.mSmallCaps && !ts.mWordSpacing && !ts.mLetterSpacing && !ts.mJustifying) { return NS_ERROR_INVALID_ARG; } /* This if clause is the cause of much pain. If aNewContent is set, then any * code path that returns an error must set aNewContent to null before returning, * or risk the caller unknowingly decrementing aNewContent inappropriately. * Here's what Robert O'Callahan has to say on the matter: If I'm not mistaken, in GetPositionSlowly, the values of aNewContent and aOffset set in the conditional "if (aPoint.x - origin.x < 0)" are overwritten on all successful return paths. Since they should never be used by the caller if the function fails, that entire "if" statement is --- or should be --- a no-op. Come to think of it, it doesn't make sense either; setting aOffset to zero is nonsense. I recommend you just delete that "if" statement. * * If this clause is removed, then some of the bullet-proofing code * prefaced with "bug 56704" comments can be removed as well. */ if (aPoint.x < 0) { *aNewContent = mContent; aOffset =0; } // Make enough space to transform nsAutoTextBuffer paintBuffer; nsAutoIndexBuffer indexBuffer; nsresult rv = indexBuffer.GrowTo(mContentLength + 1); if (NS_FAILED(rv)) { // If we've already assigned aNewContent, make sure to 0 it out here. // See bug 56704. *aNewContent = nsnull; return rv; } // Transform text from content into renderable form nsTextTransformer tx(PresContext()); PRInt32 textLength; PRIntn numJustifiableCharacter; PrepareUnicodeText(tx, &indexBuffer, &paintBuffer, &textLength, PR_TRUE, &numJustifiableCharacter); if (textLength <= 0) { // If we've already assigned aNewContent, make sure to 0 it out here. // aNewContent is undefined in the case that we return a failure, // If we were to return a valid pointer, we risk decrementing that node's // ref count an extra time by the caller. // See bug 56704 for more details. *aNewContent = nsnull; return NS_ERROR_FAILURE; } #ifdef IBMBIDI // Simon -- reverse RTL text here PRBool isOddLevel = NS_GET_EMBEDDING_LEVEL(this) & 1; if (isOddLevel) { PRUnichar *tStart, *tEnd; PRUnichar tSwap; for (tStart = paintBuffer.mBuffer, tEnd = tStart + textLength - 1; tEnd > tStart; tStart++, tEnd--) { tSwap = *tStart; *tStart = *tEnd; *tEnd = tSwap; } } #endif // IBMBIDI ComputeExtraJustificationSpacing(*aRendContext, ts, paintBuffer.mBuffer, textLength, numJustifiableCharacter); { //the following will first get the index into the PAINTBUFFER then the actual content nscoord adjustedX = PR_MAX(0,aPoint.x); #ifdef IBMBIDI if (isOddLevel) aOffset = mContentOffset + textLength - GetLengthSlowly(*aRendContext, ts, paintBuffer.mBuffer, textLength, PR_TRUE, adjustedX); else #endif aOffset = mContentOffset + GetLengthSlowly(*aRendContext, ts,paintBuffer.mBuffer, textLength, PR_TRUE, adjustedX); PRInt32 i; PRInt32* ip = indexBuffer.mBuffer; for (i = 0;i <= mContentLength; i ++){ if ((ip[i] >= aOffset) && //reverse mapping (! NS_IS_LOW_SURROGATE(paintBuffer.mBuffer[ip[i]-mContentOffset]))) { aOffset = i + mContentOffset; break; } } } *aNewContent = mContent; if (*aNewContent) (*aNewContent)->AddRef(); return NS_OK; } void nsTextFrame::RenderString(nsIRenderingContext& aRenderingContext, nsStyleContext* aStyleContext, nsPresContext* aPresContext, nsTextPaintStyle& aTextStyle, PRBool aRightToLeftText, PRUnichar* aBuffer, PRInt32 aLength, PRBool aIsEndOfFrame, nscoord aX, nscoord aY, nscoord aWidth, SelectionDetails *aDetails /*=nsnull*/) { PRUnichar buf[TEXT_BUF_SIZE]; PRUnichar* bp0 = buf; nscoord spacingMem[TEXT_BUF_SIZE]; nscoord* sp0 = spacingMem; PRBool spacing = (0 != aTextStyle.mLetterSpacing) || (0 != aTextStyle.mWordSpacing) || aTextStyle.mJustifying; PRBool justifying = aTextStyle.mJustifying && (aTextStyle.mNumJustifiableCharacterReceivingExtraJot != 0 || aTextStyle.mExtraSpacePerJustifiableCharacter != 0); PRBool isCJ = IsChineseJapaneseLangGroup(); PRBool isEndOfLine = aIsEndOfFrame && IsEndOfLine(mState); //German 0x00df might expand to "SS", but no need to count it for speed reason if (aTextStyle.mSmallCaps) { if (aLength*2 > TEXT_BUF_SIZE) { bp0 = new PRUnichar[aLength*2]; if (spacing) sp0 = new nscoord[aLength*2]; } } else if (aLength > TEXT_BUF_SIZE) { bp0 = new PRUnichar[aLength]; if (spacing) sp0 = new nscoord[aLength]; } PRUnichar* bp = bp0; nscoord* sp = sp0; nsIFontMetrics* lastFont = aTextStyle.mLastFont; PRInt32 pendingCount; PRUnichar* runStart = bp; nscoord charWidth, width = 0; PRInt32 countSoFar = 0; // Save the color we want to use for the text, since calls to // PaintTextDecorations in this method will call SetColor() on the rendering // context. nscolor textColor; aRenderingContext.GetColor(textColor); for (; --aLength >= 0; aBuffer++) { nsIFontMetrics* nextFont; nscoord glyphWidth = 0; PRUnichar ch = *aBuffer; if (aTextStyle.mSmallCaps && (IsLowerCase(ch) || (ch == kSZLIG))) { nextFont = aTextStyle.mSmallFont; } else { nextFont = aTextStyle.mNormalFont; } if (nextFont != lastFont) { pendingCount = bp - runStart; if (0 != pendingCount) { // Render the text with the color specified first. aRenderingContext.SetColor(textColor); // Measure previous run of characters using the previous font aRenderingContext.DrawString(runStart, pendingCount, aX, aY + mAscent, -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, aPresContext, aTextStyle, aX, aY, width, aRightToLeftText, 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; } if (nextFont == aTextStyle.mSmallFont) { PRUnichar upper_ch; // German szlig should be expanded to "SS". if (ch == kSZLIG) upper_ch = (PRUnichar)'S'; else upper_ch = ToUpperCase(ch); aRenderingContext.GetWidth(upper_ch, charWidth); glyphWidth += charWidth + aTextStyle.mLetterSpacing; if (ch == kSZLIG) //add an additional 'S' here. { *bp++ = upper_ch; if (spacing) *sp++ = glyphWidth; width += glyphWidth; } ch = upper_ch; } else if (ch == ' ') { glyphWidth += aTextStyle.mSpaceWidth + aTextStyle.mWordSpacing + aTextStyle.mLetterSpacing; } else if (NS_IS_HIGH_SURROGATE(ch) && aLength > 0 && NS_IS_LOW_SURROGATE(*(aBuffer+1))) { // special handling for surrogate pair aRenderingContext.GetWidth(aBuffer, 2, charWidth); glyphWidth += charWidth + aTextStyle.mLetterSpacing; // copy the surrogate low *bp++ = ch; --aLength; aBuffer++; ch = *aBuffer; // put the width into the space buffer width += glyphWidth; if (spacing) *sp++ = glyphWidth; // set the glyphWidth to 0 so the code later will // set a 0 for one element in space array for surrogate low to 0 glyphWidth = 0; } else { aRenderingContext.GetWidth(ch, charWidth); glyphWidth += charWidth + aTextStyle.mLetterSpacing; } if (justifying && (!isEndOfLine || aLength > 0) && IsJustifiableCharacter(ch, isCJ)) { glyphWidth += aTextStyle.mExtraSpacePerJustifiableCharacter; if ((PRUint32)--aTextStyle.mNumJustifiableCharacterToRender < (PRUint32)aTextStyle.mNumJustifiableCharacterReceivingExtraJot) { glyphWidth++; } } *bp++ = ch; if (spacing) *sp++ = glyphWidth; width += glyphWidth; } pendingCount = bp - runStart; if (0 != pendingCount) { // Render the text with the color specified first. aRenderingContext.SetColor(textColor); // Measure previous run of characters using the previous font aRenderingContext.DrawString(runStart, pendingCount, aX, aY + mAscent, -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, aPresContext, aTextStyle, aX, aY, aWidth, aRightToLeftText, runStart, aDetails, countSoFar, pendingCount, spacing ? sp0 : nsnull); } aTextStyle.mLastFont = lastFont; if (bp0 != buf) { delete [] bp0; } if (sp0 != spacingMem) { delete [] sp0; } } inline void nsTextFrame::MeasureSmallCapsText(nsIRenderingContext* aRenderingContext, nsTextStyle& aTextStyle, PRUnichar* aWord, PRInt32 aWordLength, PRBool aIsEndOfFrame, nsTextDimensions* aDimensionsResult) { aDimensionsResult->Clear(); GetTextDimensions(*aRenderingContext, aTextStyle, aWord, aWordLength, aIsEndOfFrame, aDimensionsResult); if (aTextStyle.mLastFont != aTextStyle.mNormalFont) { aRenderingContext->SetFont(aTextStyle.mNormalFont); aTextStyle.mLastFont = aTextStyle.mNormalFont; } } PRInt32 nsTextFrame::GetTextDimensionsOrLength(nsIRenderingContext& aRenderingContext, nsTextStyle& aStyle, PRUnichar* aBuffer, PRInt32 aLength, PRBool aIsEndOfFrame, nsTextDimensions* aDimensionsResult, PRBool aGetTextDimensions/* true=get dimensions false = return length up to aDimensionsResult.width size*/) { PRUnichar *inBuffer = aBuffer; PRInt32 length = aLength; nsAutoTextBuffer dimensionsBuffer; if (NS_FAILED(dimensionsBuffer.GrowTo(length))) { aDimensionsResult->Clear(); return 0; } PRUnichar* bp = dimensionsBuffer.mBuffer; nsIFontMetrics* lastFont = aStyle.mLastFont; nsTextDimensions sum, glyphDimensions; PRBool justifying = aStyle.mJustifying && (aStyle.mNumJustifiableCharacterReceivingExtraJot != 0 || aStyle.mExtraSpacePerJustifiableCharacter != 0); PRBool isCJ = IsChineseJapaneseLangGroup(); PRBool isEndOfLine = aIsEndOfFrame && IsEndOfLine(mState); for (PRInt32 prevLength = length; --length >= 0; prevLength = length) { PRUnichar ch = *inBuffer++; if (aStyle.mSmallCaps && (IsLowerCase(ch) || (ch == kSZLIG))) { PRUnichar upper_ch; // German szlig should be expanded to "SS". if (ch == kSZLIG) upper_ch = (PRUnichar)'S'; else upper_ch = ToUpperCase(ch); if (lastFont != aStyle.mSmallFont) { lastFont = aStyle.mSmallFont; aRenderingContext.SetFont(lastFont); } aRenderingContext.GetTextDimensions(&upper_ch, (PRUint32)1, glyphDimensions); glyphDimensions.width += aStyle.mLetterSpacing; if (ch == kSZLIG) glyphDimensions.width += glyphDimensions.width; } else if (ch == ' ' || ch == CH_NBSP) { glyphDimensions.width = aStyle.mSpaceWidth + aStyle.mLetterSpacing + aStyle.mWordSpacing; } else { if (lastFont != aStyle.mNormalFont) { lastFont = aStyle.mNormalFont; aRenderingContext.SetFont(lastFont); } if (NS_IS_HIGH_SURROGATE(ch) && length > 0 && NS_IS_LOW_SURROGATE(*inBuffer)) { aRenderingContext.GetTextDimensions(inBuffer-1, (PRUint32)2, glyphDimensions); length--; inBuffer++; } else { aRenderingContext.GetTextDimensions(&ch, (PRUint32)1, glyphDimensions); } glyphDimensions.width += aStyle.mLetterSpacing; } if (justifying && (!isEndOfLine || length > 0) && IsJustifiableCharacter(ch, isCJ)) { glyphDimensions.width += aStyle.mExtraSpacePerJustifiableCharacter; if ((PRUint32)--aStyle.mNumJustifiableCharacterToMeasure < (PRUint32)aStyle.mNumJustifiableCharacterReceivingExtraJot) { ++glyphDimensions.width; } } sum.Combine(glyphDimensions); *bp++ = ch; if (!aGetTextDimensions && sum.width >= aDimensionsResult->width) { PRInt32 result = aLength - length; if (2*(sum.width - aDimensionsResult->width) > glyphDimensions.width) //then we have gone too far, back up 1 result = aLength - prevLength; aStyle.mLastFont = lastFont; return result; } } aStyle.mLastFont = lastFont; *aDimensionsResult = sum; return aLength; } // XXX factor in logic from RenderString into here; gaps, justification, etc. void nsTextFrame::GetTextDimensions(nsIRenderingContext& aRenderingContext, nsTextStyle& aTextStyle, PRUnichar* aBuffer, PRInt32 aLength, PRBool aIsEndOfFrame, nsTextDimensions* aDimensionsResult) { GetTextDimensionsOrLength(aRenderingContext,aTextStyle, aBuffer,aLength,aIsEndOfFrame,aDimensionsResult,PR_TRUE); } PRInt32 nsTextFrame::GetLengthSlowly(nsIRenderingContext& aRenderingContext, nsTextStyle& aStyle, PRUnichar* aBuffer, PRInt32 aLength, PRBool aIsEndOfFrame, nscoord aWidth) { nsTextDimensions dimensions; dimensions.width = aWidth; return GetTextDimensionsOrLength(aRenderingContext,aStyle, aBuffer,aLength,aIsEndOfFrame,&dimensions,PR_FALSE); } void nsTextFrame::ComputeExtraJustificationSpacing(nsIRenderingContext& aRenderingContext, nsTextStyle& aTextStyle, PRUnichar* aBuffer, PRInt32 aLength, PRInt32 aNumJustifiableCharacter) { if (aTextStyle.mJustifying) { nsTextDimensions trueDimensions; // OK, so this is a bit ugly. The problem is that to get the right margin // nice and clean, we have to apply a little extra space to *some* of the // justifiable characters. It has to be the same ones every time or things will go haywire. // This implies that the GetTextDimensionsOrLength and RenderString functions depend // on a little bit of secret state: which part of the prepared text they are // looking at. It turns out that they get called in a regular way: they look // at the text from the beginning to the end. So we just count which justifiable character // we're up to, for each context. // This is not a great solution, but a perfect solution requires much more // widespread changes, to explicitly annotate all the transformed text fragments // that are passed around with their position in the transformed text // for the entire frame. aTextStyle.mNumJustifiableCharacterToMeasure = 0; aTextStyle.mExtraSpacePerJustifiableCharacter = 0; aTextStyle.mNumJustifiableCharacterReceivingExtraJot = 0; GetTextDimensions(aRenderingContext, aTextStyle, aBuffer, aLength, PR_TRUE, &trueDimensions); aTextStyle.mNumJustifiableCharacterToMeasure = aNumJustifiableCharacter; aTextStyle.mNumJustifiableCharacterToRender = aNumJustifiableCharacter; nscoord extraSpace = mRect.width - trueDimensions.width; if (extraSpace > 0 && aNumJustifiableCharacter > 0) { aTextStyle.mExtraSpacePerJustifiableCharacter = extraSpace/aNumJustifiableCharacter; aTextStyle.mNumJustifiableCharacterReceivingExtraJot = extraSpace - aTextStyle.mExtraSpacePerJustifiableCharacter*aNumJustifiableCharacter; } } } void nsTextFrame::PaintTextSlowly(nsPresContext* aPresContext, nsIRenderingContext& aRenderingContext, nsStyleContext* aStyleContext, nsTextPaintStyle& aTextStyle, nscoord dx, nscoord dy) { nsCOMPtr selCon; nsCOMPtr shell; PRBool displaySelection; PRBool isPaginated,canDarkenColor=PR_FALSE; PRBool isSelected; PRBool hideStandardSelection; PRInt16 selectionValue; if (NS_FAILED(GetTextInfoForPainting(aPresContext, getter_AddRefs(shell), getter_AddRefs(selCon), displaySelection, isPaginated, isSelected, hideStandardSelection, selectionValue))) { return; } if(isPaginated){ canDarkenColor = CanDarken(aPresContext); } // Make enough space to transform nsAutoTextBuffer paintBuffer; nsAutoIndexBuffer indexBuffer; if (NS_FAILED(indexBuffer.GrowTo(mContentLength + 1))) { return; } nscoord width = mRect.width; PRInt32 textLength; nsTextTransformer tx(aPresContext); PRIntn numJustifiableCharacter; PrepareUnicodeText(tx, (displaySelection ? &indexBuffer : nsnull), &paintBuffer, &textLength, PR_TRUE, &numJustifiableCharacter); PRInt32* ip = indexBuffer.mBuffer; PRUnichar* text = paintBuffer.mBuffer; if (0 != textLength) { #ifdef IBMBIDI PRBool isRightToLeftOnBidiPlatform = PR_FALSE; PRBool isBidiSystem = PR_FALSE; PRBool isOddLevel = PR_FALSE; PRUint32 hints = 0; aRenderingContext.GetHints(hints); PRBool paintCharByChar = (0 == (hints & NS_RENDERING_HINT_REORDER_SPACED_TEXT)) && ((0 != aTextStyle.mLetterSpacing) || (0 != aTextStyle.mWordSpacing) || aTextStyle.mJustifying); nsCharType charType = eCharType_LeftToRight; if (aPresContext->BidiEnabled()) { isBidiSystem = aPresContext->IsBidiSystem(); nsBidiPresUtils* bidiUtils = aPresContext->GetBidiUtils(); if (bidiUtils) { isOddLevel = NS_GET_EMBEDDING_LEVEL(this) & 1; charType = (nsCharType)NS_PTR_TO_INT32(aPresContext->PropertyTable()->GetProperty(this, nsGkAtoms::charType)); #ifdef DEBUG PRInt32 rememberTextLength = textLength; #endif isRightToLeftOnBidiPlatform = (!paintCharByChar && isBidiSystem && (eCharType_RightToLeft == charType || eCharType_RightToLeftArabic == charType)); if (isRightToLeftOnBidiPlatform) { // indicate that the platform should use its native // capabilities to reorder the text with right-to-left // base direction aRenderingContext.SetRightToLeftText(PR_TRUE); } PRUint32 hints = 0; aRenderingContext.GetHints(hints); // If we will be painting char by char, handle the text like on non-bidi platform bidiUtils->ReorderUnicodeText(text, textLength, charType, isOddLevel, (paintCharByChar) ? PR_FALSE : isBidiSystem, (hints & NS_RENDERING_HINT_NEW_TEXT_RUNS) != 0); NS_ASSERTION(rememberTextLength == textLength, "Bidi formatting changed text length"); } } #endif // IBMBIDI ComputeExtraJustificationSpacing(aRenderingContext, aTextStyle, text, textLength, numJustifiableCharacter); if (!displaySelection || !isSelected) { // When there is no selection showing, use the fastest and // simplest rendering approach aRenderingContext.SetColor(nsCSSRendering::TransformColor(aTextStyle.mColor->mColor,canDarkenColor)); RenderString(aRenderingContext, aStyleContext, aPresContext, aTextStyle, PR_FALSE, text, textLength, PR_TRUE, dx, dy, width); } else { SelectionDetails *details = nsnull; nsCOMPtr content; PRInt32 offset; PRInt32 length; nsresult rv = GetContentAndOffsetsForSelection(aPresContext, getter_AddRefs(content), &offset, &length); if (NS_SUCCEEDED(rv)) { details = GetFrameSelection()->LookUpSelection(content, mContentOffset, mContentLength, PR_FALSE); } //where are the selection points "really" SelectionDetails *sdptr = details; while (sdptr){ sdptr->mStart = ip[sdptr->mStart] - mContentOffset; sdptr->mEnd = ip[sdptr->mEnd] - mContentOffset; #ifdef IBMBIDI AdjustSelectionPointsForBidi(sdptr, textLength, CHARTYPE_IS_RTL(charType), isOddLevel, (paintCharByChar) ? PR_FALSE : isBidiSystem); #endif sdptr = sdptr->mNext; } DrawSelectionIterator iter(details, text, (PRUint32)textLength, &aTextStyle, nsTextPaintStyle::eAllSelections); if (!iter.IsDone() && iter.First()) { nscoord currentX = dx; nsTextDimensions newDimensions;//temp #ifdef IBMBIDI // Simon - display substrings RTL in RTL frame if (isRightToLeftOnBidiPlatform) currentX = dx + mRect.width; #endif while (!iter.IsDone()) { PRUnichar *currenttext = iter.CurrentTextUnicharPtr(); PRUint32 currentlength= iter.CurrentLength(); nscolor currentFGColor, currentBKColor; PRBool isCurrentBKColorTransparent; PRBool isSelection = iter.GetSelectionColors(¤tFGColor, ¤tBKColor, &isCurrentBKColorTransparent); PRBool isEndOfFrame = iter.IsLast(); GetTextDimensions(aRenderingContext, aTextStyle, currenttext, (PRInt32)currentlength, isEndOfFrame, &newDimensions); if (newDimensions.width) { #ifdef IBMBIDI if (isRightToLeftOnBidiPlatform) currentX -= newDimensions.width; #endif if (isSelection && !isPaginated) {//DRAW RECT HERE!!! if (!isCurrentBKColorTransparent) { aRenderingContext.SetColor(currentBKColor); aRenderingContext.FillRect(currentX, dy, newDimensions.width, mRect.height); } } } if (isPaginated && !iter.IsBeforeOrAfter()) { aRenderingContext.SetColor(nsCSSRendering::TransformColor(aTextStyle.mColor->mColor, canDarkenColor)); RenderString(aRenderingContext, aStyleContext, aPresContext, aTextStyle, isRightToLeftOnBidiPlatform, currenttext, currentlength, isEndOfFrame, currentX, dy, newDimensions.width, details); } else if (!isPaginated) { aRenderingContext.SetColor(nsCSSRendering::TransformColor(currentFGColor, canDarkenColor)); RenderString(aRenderingContext,aStyleContext, aPresContext, aTextStyle, isRightToLeftOnBidiPlatform, currenttext, currentlength, isEndOfFrame, currentX, dy, newDimensions.width, details); } #ifdef IBMBIDI if (!isRightToLeftOnBidiPlatform) #endif // increment twips X start but remember to get ready for // next draw by reducing current x by letter spacing amount currentX += newDimensions.width; // + aTextStyle.mLetterSpacing; iter.Next(); } } else if (!isPaginated) { aRenderingContext.SetColor(nsCSSRendering::TransformColor(aTextStyle.mColor->mColor,canDarkenColor)); RenderString(aRenderingContext, aStyleContext, aPresContext, aTextStyle, isRightToLeftOnBidiPlatform, text, PRUint32(textLength), PR_TRUE, dx, dy, width, details); } sdptr = details; if (details){ while ((sdptr = details->mNext) != nsnull) { delete details; details = sdptr; } delete details; } } #ifdef IBMBIDI if (isRightToLeftOnBidiPlatform) { // indicate that future text should not be reordered with // right-to-left base direction aRenderingContext.SetRightToLeftText(PR_FALSE); } #endif // IBMBIDI } } void nsTextFrame::PaintAsciiText(nsPresContext* aPresContext, nsIRenderingContext& aRenderingContext, nsStyleContext* aStyleContext, nsTextPaintStyle& aTextStyle, nscoord dx, nscoord dy) { NS_PRECONDITION(0 == (TEXT_HAS_MULTIBYTE & mState), "text is multi-byte"); nsCOMPtr selCon; nsCOMPtr shell; PRBool displaySelection,canDarkenColor=PR_FALSE; PRBool isPaginated; PRBool isSelected; PRBool hideStandardSelection; PRInt16 selectionValue; if (NS_FAILED(GetTextInfoForPainting(aPresContext, getter_AddRefs(shell), getter_AddRefs(selCon), displaySelection, isPaginated, isSelected, hideStandardSelection, selectionValue))) { return; } if(isPaginated){ canDarkenColor = CanDarken(aPresContext); } // Get the text fragment const nsTextFragment* frag = mContent->GetText(); if (!frag) { return; } // Make enough space to transform nsAutoTextBuffer unicodePaintBuffer; nsAutoIndexBuffer indexBuffer; if (displaySelection) { if (NS_FAILED(indexBuffer.GrowTo(mContentLength + 1))) { return; } } nsTextTransformer tx(aPresContext); // See if we need to transform the text. If the text fragment is ascii and // wasn't transformed, then we can skip this step. If we're displaying the // selection and the text is selected, then we need to do this step so we // can create the index buffer PRInt32 textLength = 0; const char* text; char paintBufMem[TEXT_BUF_SIZE]; char* paintBuf = paintBufMem; if (frag->Is2b() || (0 != (mState & TEXT_WAS_TRANSFORMED)) || (displaySelection && isSelected)) { // Transform text from content into Unicode renderable form // XXX If the text fragment is ascii, then we should ask the // text transformer to leave the text in ascii. That way we can // elimninate the conversion from Unicode back to ascii... PrepareUnicodeText(tx, (displaySelection ? &indexBuffer : nsnull), &unicodePaintBuffer, &textLength); // Translate unicode data into ascii for rendering if (textLength > TEXT_BUF_SIZE) { paintBuf = new char[textLength]; if (!paintBuf) { return; } } char* dst = paintBuf; char* end = dst + textLength; PRUnichar* src = unicodePaintBuffer.mBuffer; while (dst < end) { *dst++ = (char) ((unsigned char) *src++); } text = paintBuf; } else if (mContentOffset + mContentLength <= frag->GetLength()) { text = frag->Get1b() + mContentOffset; textLength = mContentLength; // See if we should skip leading whitespace if (0 != (mState & TEXT_SKIP_LEADING_WS)) { while ((textLength > 0) && XP_IS_SPACE(*text)) { text++; textLength--; } } // See if the text ends in a newline if ((textLength > 0) && (text[textLength - 1] == '\n')) { textLength--; } NS_ASSERTION(textLength >= 0, "bad text length"); } else { // This might happen if a paint event beats the reflow; e.g., as // is the case in bug 73291. Not a big deal, because the reflow // will schedule another invalidate. NS_WARNING("content length exceeds fragment length"); } nscoord width = mRect.width; PRInt32* ip = indexBuffer.mBuffer; 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(nsCSSRendering::TransformColor(aTextStyle.mColor->mColor,canDarkenColor)); aRenderingContext.DrawString(text, PRUint32(textLength), dx, dy + mAscent); PaintTextDecorations(aRenderingContext, aStyleContext, aPresContext, aTextStyle, dx, dy, width, PR_FALSE); } else { SelectionDetails *details = nsnull; nsCOMPtr content; PRInt32 offset; PRInt32 length; nsresult rv = GetContentAndOffsetsForSelection(aPresContext, getter_AddRefs(content), &offset, &length); if (NS_SUCCEEDED(rv)) { details = GetFrameSelection()->LookUpSelection(content, mContentOffset, mContentLength, PR_FALSE); } //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; } if (!hideStandardSelection || displaySelection) { //ITS OK TO CAST HERE THE RESULT WE USE WILLNOT DO BAD CONVERSION DrawSelectionIterator iter(details, (PRUnichar *)text, (PRUint32)textLength, &aTextStyle, nsTextPaintStyle::eAllSelections); // See if this rendering backend supports getting cluster // information. PRUint32 clusterHint = 0; aRenderingContext.GetHints(clusterHint); clusterHint &= NS_RENDERING_HINT_TEXT_CLUSTERS; if (!iter.IsDone() && iter.First()) { nscoord currentX = dx; nscoord newWidth;//temp while (!iter.IsDone()) { char *currenttext = iter.CurrentTextCStrPtr(); PRUint32 currentlength= iter.CurrentLength(); nscolor currentFGColor, currentBKColor; PRBool isCurrentBKColorTransparent; if (clusterHint) { PRUint32 tmpWidth; rv = aRenderingContext.GetRangeWidth(text, textLength, currenttext - text, (currenttext - text) + currentlength, tmpWidth); newWidth = nscoord(tmpWidth); } else { rv = aRenderingContext.GetWidth(currenttext, currentlength,newWidth); //ADJUST FOR CHAR SPACING } PRBool isSelection = iter.GetSelectionColors(¤tFGColor, ¤tBKColor, &isCurrentBKColorTransparent); if (NS_SUCCEEDED(rv)) { if (isSelection && !isPaginated) {//DRAW RECT HERE!!! if (!isCurrentBKColorTransparent) { aRenderingContext.SetColor(currentBKColor); aRenderingContext.FillRect(currentX, dy, newWidth, mRect.height); } } } else { newWidth =0; } aRenderingContext.PushState(); nsRect rect(currentX, dy, newWidth, mRect.height); aRenderingContext.SetClipRect(rect, nsClipCombine_kIntersect); if (isPaginated && !iter.IsBeforeOrAfter()) { aRenderingContext.SetColor(nsCSSRendering::TransformColor(aTextStyle.mColor->mColor,canDarkenColor)); aRenderingContext.DrawString(text, PRUint32(textLength), dx, dy + mAscent); } else if (!isPaginated) { aRenderingContext.SetColor(nsCSSRendering::TransformColor(currentFGColor,canDarkenColor)); aRenderingContext.DrawString(text, PRUint32(textLength), dx, dy + mAscent); } aRenderingContext.PopState(); currentX+=newWidth;//increment twips X start iter.Next(); } } else if (!isPaginated) { aRenderingContext.SetColor(nsCSSRendering::TransformColor(aTextStyle.mColor->mColor,canDarkenColor)); aRenderingContext.DrawString(text, PRUint32(textLength), dx, dy + mAscent); } } PaintTextDecorations(aRenderingContext, aStyleContext, aPresContext, aTextStyle, dx, dy, width, PR_FALSE, unicodePaintBuffer.mBuffer, details, 0, textLength); sdptr = details; if (details){ while ((sdptr = details->mNext) != nsnull) { delete details; details = sdptr; } delete details; } } } // Cleanup if (paintBuf != paintBufMem) { delete [] paintBuf; } } // XXX I don't really want to rewrite GetPositionHelper, so I'm doing this // hack for now nsIFrame::ContentOffsets nsTextFrame::CalcContentOffsetsFromFramePoint(nsPoint aPoint) { ContentOffsets offsets; GetPositionHelper(aPoint, getter_AddRefs(offsets.content), offsets.offset, offsets.secondaryOffset); offsets.associateWithNext = (mContentOffset == offsets.offset); return offsets; } //--------------------------------------------------------------------------- // 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::GetPositionHelper(const nsPoint& aPoint, nsIContent ** aNewContent, PRInt32& aContentOffset, PRInt32& aContentOffsetEnd) { // pre-condition tests NS_PRECONDITION(aNewContent, "null arg"); if (!aNewContent) { return NS_ERROR_NULL_POINTER; } // initialize out param *aNewContent = nsnull; DEBUG_VERIFY_NOT_DIRTY(mState); if (mState & NS_FRAME_IS_DIRTY) return NS_ERROR_UNEXPECTED; nsPresContext *presContext = PresContext(); nsIPresShell *shell = presContext->GetPresShell(); if (shell) { nsCOMPtr rendContext; nsresult rv = shell->CreateRenderingContext(this, getter_AddRefs(rendContext)); if (NS_SUCCEEDED(rv)) { nsTextStyle ts(presContext, *rendContext, mStyleContext); SetupTextRunDirection(presContext, rendContext); if (ts.mSmallCaps || ts.mWordSpacing || ts.mLetterSpacing || ts.mJustifying) { nsresult result = GetPositionSlowly(rendContext, aPoint, aNewContent, aContentOffset); aContentOffsetEnd = aContentOffset; return result; } // Make enough space to transform nsAutoTextBuffer paintBuffer; nsAutoIndexBuffer indexBuffer; rv = indexBuffer.GrowTo(mContentLength + 1); if (NS_FAILED(rv)) { return rv; } // Find the font metrics for this text nsLayoutUtils::SetFontFromStyle(rendContext, mStyleContext); // Get the renderable form of the text nsTextTransformer tx(PresContext()); PRInt32 textLength; // no need to worry about justification, that's always on the slow path PrepareUnicodeText(tx, &indexBuffer, &paintBuffer, &textLength); if (textLength <= 0) { aContentOffset = mContentOffset; aContentOffsetEnd = aContentOffset; } else { PRInt32* ip = indexBuffer.mBuffer; PRInt32 indx; PRInt32 textWidth = 0; PRUnichar* text = paintBuffer.mBuffer; // See if the font backend will do all the hard work for us. PRUint32 clusterHint = 0; rendContext->GetHints(clusterHint); clusterHint &= NS_RENDERING_HINT_TEXT_CLUSTERS; if (clusterHint) { indx = rendContext->GetPosition(text, textLength, aPoint); } else { #ifdef IBMBIDI PRBool getReversedPos = NS_GET_EMBEDDING_LEVEL(this) & 1; nscoord posX = (getReversedPos) ? (mRect.width) - (aPoint.x) : aPoint.x; PRBool found = nsLayoutUtils::BinarySearchForPosition(rendContext, text, 0, 0, 0, PRInt32(textLength), PRInt32(posX) , //go to local coordinates indx, textWidth); #else PRBool found = nsLayoutUtils::BinarySearchForPosition(rendContext, text, 0, 0, 0, PRInt32(textLength), PRInt32(aPoint.x) , //go to local coordinates indx, textWidth); #endif // IBMBIDI if (found) { PRInt32 charWidth; if (NS_IS_HIGH_SURROGATE(text[indx])) rendContext->GetWidth(&text[indx], 2, charWidth); else rendContext->GetWidth(text[indx], charWidth); charWidth /= 2; #ifdef IBMBIDI if (getReversedPos) { if (mRect.width - aPoint.x> textWidth+charWidth ) { indx++; } } else #endif // IBMBIDI if ((aPoint.x) > textWidth+charWidth) { indx++; } } } aContentOffset = indx + mContentOffset; //reusing wordBufMem PRInt32 i; for (i = 0; i < mContentLength; i ++){ if ((ip[i] >= aContentOffset) && //reverse mapping (! NS_IS_LOW_SURROGATE(paintBuffer.mBuffer[ip[i]-mContentOffset]))) { break; } } aContentOffset = i + mContentOffset; #ifdef IBMBIDI PRInt32 bidiStopOffset = mContentOffset + mContentLength; if (aContentOffset >= mContentOffset && aContentOffset < bidiStopOffset) { PRInt32 curindx = ip[aContentOffset - mContentOffset] - mContentOffset; while (curindx < textLength && IS_BIDI_DIACRITIC(text[curindx])) { if (++aContentOffset >= bidiStopOffset) break; curindx = ip[aContentOffset - mContentOffset] - mContentOffset; } } #endif // IBMBIDI 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(nsPresContext* aPresContext, nsIDOMRange *aRange, PRBool aSelected, nsSpread aSpread) { DEBUG_VERIFY_NOT_DIRTY(mState); #if 0 //XXXrbs disable due to bug 310318 if (mState & NS_FRAME_IS_DIRTY) return NS_ERROR_UNEXPECTED; #endif if (aSelected && ParentDisablesSelection()) return NS_OK; #if 0 PRBool isSelected = ((GetStateBits() & NS_FRAME_SELECTED_CONTENT) == NS_FRAME_SELECTED_CONTENT); if (!aSelected && !isSelected) //already set thanks { return NS_OK; } #endif // check whether style allows selection PRBool selectable; IsSelectable(&selectable, nsnull); if (!selectable) return NS_OK;//do not continue no selection for this frame. 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->GetEndContainer(getter_AddRefs(endNode)); aRange->GetEndOffset(&endOffset); aRange->GetStartContainer(getter_AddRefs(startNode)); aRange->GetStartOffset(&startOffset); nsCOMPtr thisNode = do_QueryInterface(GetContent()); if (thisNode == startNode) { if ((mContentOffset + mContentLength) >= startOffset) { found = PR_TRUE; if (thisNode == endNode) { //special case if (endOffset == startOffset) //no need to redraw since drawing takes place with cursor found = PR_FALSE; 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 { // null range means the whole thing found = PR_TRUE; } if ( aSelected ) AddStateBits(NS_FRAME_SELECTED_CONTENT); else {//we need to see if any other selection available. SelectionDetails *details = nsnull; nsCOMPtr content; PRInt32 offset; PRInt32 length; nsresult rv = GetContentAndOffsetsForSelection(aPresContext, getter_AddRefs(content), &offset, &length); if (NS_SUCCEEDED(rv) && content) { details = GetFrameSelection()->LookUpSelection(content, offset, length, PR_TRUE); // PR_TRUE last param used here! we need to see if we are still selected. so no shortcut } if (!details) RemoveStateBits(NS_FRAME_SELECTED_CONTENT); else { SelectionDetails *sdptr = details; while ((sdptr = details->mNext) != nsnull) { delete details; details = sdptr; } delete details; } } if (found){ //if range contains this frame... // Selection might change our border, content and outline appearance // But textframes can't have an outline. So just use the simple // bounds Invalidate(nsRect(0, 0, mRect.width, mRect.height), PR_FALSE); } if (aSpread == eSpreadDown) { nsIFrame* frame = GetPrevContinuation(); while(frame){ frame->SetSelected(aPresContext, aRange,aSelected,eSpreadNone); frame = frame->GetPrevContinuation(); } frame = GetNextContinuation(); while (frame){ frame->SetSelected(aPresContext, aRange,aSelected,eSpreadNone); frame = frame->GetNextContinuation(); } } return NS_OK; } NS_IMETHODIMP nsTextFrame::GetPointFromOffset(nsPresContext* aPresContext, nsIRenderingContext* inRendContext, PRInt32 inOffset, nsPoint* outPoint) { if (!aPresContext || !inRendContext || !outPoint) return NS_ERROR_NULL_POINTER; outPoint->x = 0; outPoint->y = 0; DEBUG_VERIFY_NOT_DIRTY(mState); if (mState & NS_FRAME_IS_DIRTY) return NS_ERROR_UNEXPECTED; if (mContentLength <= 0) { return NS_OK; } inOffset-=mContentOffset; if (inOffset < 0){ NS_ASSERTION(0,"offset less than this frame has in GetPointFromOffset"); inOffset = 0; } if (inOffset >= mContentLength) inOffset = mContentLength; nsTextStyle ts(aPresContext, *inRendContext, mStyleContext); SetupTextRunDirection(aPresContext, inRendContext); // Make enough space to transform nsAutoTextBuffer paintBuffer; nsAutoIndexBuffer indexBuffer; nsresult rv = indexBuffer.GrowTo(mContentLength + 1); if (NS_FAILED(rv)) { return rv; } // Transform text from content into renderable form nsTextTransformer tx(aPresContext); PRInt32 textLength; PRIntn numJustifiableCharacter; PrepareUnicodeText(tx, &indexBuffer, &paintBuffer, &textLength, PR_FALSE, &numJustifiableCharacter); ComputeExtraJustificationSpacing(*inRendContext, ts, paintBuffer.mBuffer, textLength, numJustifiableCharacter); PRInt32* ip = indexBuffer.mBuffer; if (inOffset > mContentLength){ NS_ASSERTION(0, "invalid offset passed to GetPointFromOffset"); inOffset = mContentLength; } while (inOffset >=0 && ip[inOffset] < mContentOffset) //buffer has shrunk inOffset --; nscoord width = mRect.width; if (inOffset <0) { NS_ASSERTION(0, "invalid offset passed to GetPointFromOffset"); inOffset=0; width = 0; } else { PRInt32 hitLength = ip[inOffset] - mContentOffset; if (ts.mSmallCaps || (0 != ts.mWordSpacing) || (0 != ts.mLetterSpacing) || ts.mJustifying) { nsTextDimensions dimensions; GetTextDimensions(*inRendContext, ts, paintBuffer.mBuffer, hitLength, textLength == hitLength, &dimensions); width = dimensions.width; } else { PRInt32 totalLength = mContent->TextLength(); // length up to the last-in-flow frame if ((hitLength == textLength) && (inOffset == mContentLength) && (mContentOffset + mContentLength == totalLength)) { // no need to re-measure when at the end of the last-in-flow } else inRendContext->GetWidth(paintBuffer.mBuffer, hitLength, width); } if ((hitLength == textLength && inOffset > 0 && ip[inOffset] == ip[inOffset-1]) && (TEXT_TRIMMED_WS & mState)) { // // Offset must be after a space that has // been trimmed off the end of the frame. // Add the width of the trimmed space back // to the total width, so the caret appears // in the proper place! // // NOTE: the trailing whitespace includes the word and letter spacing!! width += ts.mSpaceWidth + ts.mWordSpacing + ts.mLetterSpacing; } } if (NS_GET_EMBEDDING_LEVEL(this) & 1) outPoint->x = mRect.width - width; else outPoint->x = width; outPoint->y = 0; return NS_OK; } NS_IMETHODIMP nsTextFrame::GetChildFrameContainingOffset(PRInt32 inContentOffset, PRBool inHint, PRInt32* outFrameContentOffset, nsIFrame **outChildFrame) { DEBUG_VERIFY_NOT_DIRTY(mState); #if 0 //XXXrbs disable due to bug 310227 if (mState & NS_FRAME_IS_DIRTY) return NS_ERROR_UNEXPECTED; #endif if (nsnull == outChildFrame) return NS_ERROR_NULL_POINTER; PRInt32 contentOffset = inContentOffset; if (contentOffset != -1) //-1 signified the end of the current content contentOffset = inContentOffset - mContentOffset; if ((contentOffset > mContentLength) || ((contentOffset == mContentLength) && inHint) ) { //this is not the frame we are looking for. nsIFrame* nextContinuation = GetNextContinuation(); if (nextContinuation) { return nextContinuation->GetChildFrameContainingOffset(inContentOffset, inHint, outFrameContentOffset, outChildFrame); } else { if (contentOffset != mContentLength) //that condition was only for when there is a choice return NS_ERROR_FAILURE; } } if (inContentOffset < mContentOffset) //could happen with floats! { *outChildFrame = GetPrevInFlow(); if (*outChildFrame) return (*outChildFrame)->GetChildFrameContainingOffset(inContentOffset, inHint, outFrameContentOffset,outChildFrame); else return NS_OK; //this can't be the right thing to do? } *outFrameContentOffset = contentOffset; *outChildFrame = this; return NS_OK; } PRBool nsTextFrame::PeekOffsetNoAmount(PRBool aForward, PRInt32* aOffset) { NS_ASSERTION (aOffset && *aOffset <= mContentLength, "aOffset out of range"); return (!IsEmpty()); } PRBool nsTextFrame::PeekOffsetCharacter(PRBool aForward, PRInt32* aOffset) { NS_ASSERTION (aOffset && *aOffset <= mContentLength, "aOffset out of range"); PRInt32 startOffset = *aOffset; // A negative offset means "end of frame". if (startOffset < 0) startOffset = mContentLength; nsPresContext* presContext = PresContext(); // Transform text from content into renderable form nsAutoTextBuffer paintBuffer; nsAutoIndexBuffer indexBuffer; nsresult rv = indexBuffer.GrowTo(mContentLength + 1); if (NS_FAILED(rv)) { return PR_FALSE; } PRInt32 textLength; nsTextTransformer tx(presContext); PrepareUnicodeText(tx, &indexBuffer, &paintBuffer, &textLength); PRInt32* ip = indexBuffer.mBuffer; PRBool found = PR_TRUE; PRBool selectable; PRUint8 selectStyle; IsSelectable(&selectable, &selectStyle); if (selectStyle == NS_STYLE_USER_SELECT_ALL) return PR_FALSE; if (!aForward) { *aOffset = 0; PRInt32 i; nsAutoPRUint8Buffer clusterBuffer; rv = FillClusterBuffer(presContext, paintBuffer.mBuffer, (PRUint32)textLength, clusterBuffer); NS_ENSURE_SUCCESS(rv, rv); for (i = startOffset-1; i >= 0; i--){ if ((ip[i] < ip[startOffset]) && (clusterBuffer.mBuffer[ip[i] - mContentOffset]) && (! NS_IS_LOW_SURROGATE(paintBuffer.mBuffer[ip[i]-mContentOffset]))) { *aOffset = i; break; } } #ifdef SUNCTL static NS_DEFINE_CID(kLECID, NS_ULE_CID); nsCOMPtr ctlObj; ctlObj = do_CreateInstance(kLECID, &rv); if (NS_FAILED(rv)) { NS_WARNING("Cell based cursor movement will not be supported\n"); ctlObj = nsnull; } else { PRBool needsCTL = PR_FALSE; PRInt32 previousOffset; ctlObj->NeedsCTLFix(NS_REINTERPRET_CAST(const PRUnichar*, paintBuffer.mBuffer), mContentOffset + startOffset, -1, &needsCTL); if (needsCTL) { ctlObj->PrevCluster(NS_REINTERPRET_CAST(const PRUnichar*, paintBuffer.mBuffer), textLength, mContentOffset + startOffset, &previousOffset); *aOffset = i = previousOffset - mContentOffset; } } #endif /* SUNCTL */ if (i < 0) found = PR_FALSE; } else // aForward { PRInt32 i; *aOffset = mContentLength; nsAutoPRUint8Buffer clusterBuffer; rv = FillClusterBuffer(presContext, paintBuffer.mBuffer, (PRUint32)textLength, clusterBuffer); NS_ENSURE_SUCCESS(rv, rv); for (i = startOffset; i <= mContentLength; i++) { if ((ip[i] > ip[startOffset]) && ((i == mContentLength) || (!NS_IS_LOW_SURROGATE(paintBuffer.mBuffer[ip[i] - mContentOffset])) && (clusterBuffer.mBuffer[ip[i] - mContentOffset]))) { *aOffset = i; break; } } #ifdef SUNCTL static NS_DEFINE_CID(kLECID, NS_ULE_CID); nsCOMPtr ctlObj; ctlObj = do_CreateInstance(kLECID, &rv); if (NS_FAILED(rv)) { NS_WARNING("Cell based cursor movement will not be supported\n"); ctlObj = nsnull; } else { PRBool needsCTL = PR_FALSE; PRInt32 nextOffset; ctlObj->NeedsCTLFix(NS_REINTERPRET_CAST(const PRUnichar*, paintBuffer.mBuffer), mContentOffset + startOffset, 0, &needsCTL); if (needsCTL) { ctlObj->NextCluster(NS_REINTERPRET_CAST(const PRUnichar*, paintBuffer.mBuffer), textLength, mContentOffset + startOffset, &nextOffset); *aOffset = i = nextOffset - mContentOffset; } } #endif /* SUNCTL */ if (i > mContentLength) found = PR_FALSE; } return found; } PRBool nsTextFrame::PeekOffsetWord(PRBool aForward, PRBool aWordSelectEatSpace, PRBool aIsKeyboardSelect, PRInt32* aOffset, PRBool* aSawBeforeType) { NS_ASSERTION (aOffset && *aOffset <= mContentLength, "aOffset out of range"); PRInt32 startOffset = *aOffset; if (startOffset < 0) startOffset = mContentLength; nsTextTransformer tx(PresContext()); PRBool keepSearching = PR_TRUE; //if you run out of chars before you hit the end of word, maybe next frame has more text to select? PRBool found = PR_FALSE; PRBool isWhitespace, wasTransformed; PRInt32 wordLen, contentLen; PRBool selectable; PRUint8 selectStyle; IsSelectable(&selectable, &selectStyle); if (selectStyle == NS_STYLE_USER_SELECT_ALL) found = PR_FALSE; else { tx.Init(this, mContent, mContentOffset + startOffset); if (!aForward) { *aOffset = 0; //initialize #ifdef IBMBIDI wordLen = (mState & NS_FRAME_IS_BIDI) ? mContentOffset : -1; #endif // IBMBIDI if (tx.GetPrevWord(PR_FALSE, &wordLen, &contentLen, &isWhitespace, PR_FALSE, aIsKeyboardSelect) && contentLen <= startOffset) { if ((aWordSelectEatSpace ? isWhitespace : !isWhitespace) || !*aSawBeforeType){ *aOffset = startOffset - contentLen; keepSearching = PR_TRUE; if (aWordSelectEatSpace ? isWhitespace : !isWhitespace) *aSawBeforeType = PR_TRUE; #ifdef IBMBIDI wordLen = (mState & NS_FRAME_IS_BIDI) ? mContentOffset : -1; #endif // IBMBIDI while (tx.GetPrevWord(PR_FALSE, &wordLen, &contentLen, &isWhitespace, PR_FALSE, aIsKeyboardSelect)){ if (aWordSelectEatSpace ? !isWhitespace : *aSawBeforeType) break; if (contentLen >= startOffset) goto TryNextFrame; *aOffset -= contentLen; if (aWordSelectEatSpace ? isWhitespace : !isWhitespace) *aSawBeforeType = PR_TRUE; #ifdef IBMBIDI wordLen = (mState & NS_FRAME_IS_BIDI) ? mContentOffset : -1; #endif // IBMBIDI } keepSearching = *aOffset <= 0; if (!keepSearching) found = PR_TRUE; } else { *aOffset = mContentLength; found = PR_TRUE; } } } else { // aForward *aOffset = mContentLength; //initialize #ifdef IBMBIDI wordLen = (mState & NS_FRAME_IS_BIDI) ? mContentOffset + mContentLength : -1; #endif // IBMBIDI if (tx.GetNextWord(PR_FALSE, &wordLen, &contentLen, &isWhitespace, &wasTransformed, PR_TRUE, PR_FALSE, aIsKeyboardSelect) && (startOffset + contentLen <= mContentLength)) { if ((aWordSelectEatSpace ? isWhitespace : !isWhitespace) || !*aSawBeforeType) { *aOffset = startOffset + contentLen; keepSearching = PR_TRUE; if (aWordSelectEatSpace ? isWhitespace : !isWhitespace) *aSawBeforeType = PR_TRUE; #ifdef IBMBIDI wordLen = (mState & NS_FRAME_IS_BIDI) ? mContentOffset + mContentLength : -1; #endif // IBMBIDI while (tx.GetNextWord(PR_FALSE, &wordLen, &contentLen, &isWhitespace, &wasTransformed, PR_TRUE, PR_FALSE, aIsKeyboardSelect)) { if (aWordSelectEatSpace ? !isWhitespace : *aSawBeforeType) break; if (startOffset + contentLen >= mContentLength) goto TryNextFrame; if (aWordSelectEatSpace ? isWhitespace : !isWhitespace) *aSawBeforeType = PR_TRUE; *aOffset += contentLen; #ifdef IBMBIDI wordLen = (mState & NS_FRAME_IS_BIDI) ? mContentOffset + mContentLength : -1; #endif // IBMBIDI } keepSearching = *aOffset >= mContentLength; if (!keepSearching) found = PR_TRUE; } else { *aOffset = 0; found = PR_TRUE; } } } } TryNextFrame: if (!found || *aOffset > mContentLength || *aOffset < 0) { found = PR_FALSE; *aOffset = PR_MIN(*aOffset, mContentLength); *aOffset = PR_MAX(*aOffset, 0); } return found; } NS_IMETHODIMP nsTextFrame::CheckVisibility(nsPresContext* aContext, PRInt32 aStartIndex, PRInt32 aEndIndex, PRBool aRecurse, PRBool *aFinished, PRBool *_retval) { if (!aFinished || !_retval) return NS_ERROR_NULL_POINTER; if (*aFinished) return NS_ERROR_FAILURE; //dont call with finished != false if (mContentOffset > aEndIndex) return NS_OK; //reached the end if (mContentOffset > aStartIndex) aStartIndex = mContentOffset; if (aStartIndex >= aEndIndex) //how can it be greater?? check anyway return NS_OK; //reached the end. nsresult rv ; if (aStartIndex < (mContentOffset + mContentLength)) { //get the presshell nsIPresShell *shell = aContext->GetPresShell(); if (!shell) return NS_ERROR_FAILURE; //get the document nsIDocument *doc = shell->GetDocument(); if (!doc) return NS_ERROR_FAILURE; //create texttransformer nsTextTransformer tx(aContext); //create the buffers nsAutoTextBuffer paintBuffer; nsAutoIndexBuffer indexBuffer; if (NS_FAILED(indexBuffer.GrowTo(mContentLength + 1))) return NS_ERROR_FAILURE;//bail out PRInt32 textLength; PrepareUnicodeText(tx, &indexBuffer, &paintBuffer, &textLength); if (textLength)//we have something to measure? { PRInt32 start = PR_MAX(aStartIndex,mContentOffset); PRInt32 end = PR_MIN(mContentOffset + mContentLength-1, aEndIndex); //base 0 index of array while (start != end) { if (indexBuffer.mBuffer[start] < indexBuffer.mBuffer[start+1]) //we have a rendered char! { *aFinished = PR_TRUE;//we are done bubble out. *_retval = PR_TRUE;//hit a drawn char return NS_OK; } start++; } if (start == aEndIndex) { *aFinished = PR_TRUE; } } } if (aRecurse) //recurse through the siblings. { nsIFrame *nextInFlow = this; rv = NS_OK; while (!aFinished && nextInFlow && NS_SUCCEEDED(rv) && !*_retval) //while we havent found anything visible { nextInFlow = nextInFlow->GetNextInFlow(); if (nextInFlow) { rv = nextInFlow->CheckVisibility(aContext,aStartIndex,aEndIndex,PR_FALSE,aFinished,_retval); } } } return NS_OK; } NS_IMETHODIMP nsTextFrame::GetOffsets(PRInt32 &start, PRInt32 &end) const { start = mContentOffset; end = mContentOffset+mContentLength; return NS_OK; } #define TEXT_MAX_NUM_SEGMENTS 65 struct SegmentData { PRUint32 mIsWhitespace : 1; PRUint32 mContentLen : 31; // content length PRBool IsWhitespace() {return PRBool(mIsWhitespace);} // Get the content length. This is a running total of all // the previous segments as well PRInt32 ContentLen() {return PRInt32(mContentLen);} }; struct TextRun { // Total number of characters and the accumulated content length PRInt32 mTotalNumChars, mTotalContentLen; // Words and whitespace each count as a segment PRInt32 mNumSegments; // Possible break points specified as offsets into the buffer PRInt32 mBreaks[TEXT_MAX_NUM_SEGMENTS]; // Per segment data SegmentData mSegments[TEXT_MAX_NUM_SEGMENTS]; TextRun() { Reset(); } void Reset() { mNumSegments = 0; mTotalNumChars = 0; mTotalContentLen = 0; } // Returns PR_TRUE if we're currently buffering text PRBool IsBuffering() { return mNumSegments > 0; } void AddSegment(PRInt32 aNumChars, PRInt32 aContentLen, PRBool aIsWhitespace) { NS_PRECONDITION(mNumSegments < TEXT_MAX_NUM_SEGMENTS, "segment overflow"); #ifdef IBMBIDI if (mNumSegments >= TEXT_MAX_NUM_SEGMENTS) { return; } #endif // IBMBIDI mTotalNumChars += aNumChars; mBreaks[mNumSegments] = mTotalNumChars; mSegments[mNumSegments].mIsWhitespace = aIsWhitespace; mTotalContentLen += aContentLen; mSegments[mNumSegments].mContentLen = PRUint32(mTotalContentLen); mNumSegments++; } }; PRUint32 nsTextFrame::EstimateNumChars(PRUint32 aAvailableWidth, PRUint32 aAverageCharWidth) { // Estimate the number of characters that will fit. Use 105% of the available // width divided by the average character width. // If mAveCharWidth is zero, we can fit the entire line. if (aAverageCharWidth == 0) { return PR_UINT32_MAX; } PRUint32 estimatedNumChars = aAvailableWidth / aAverageCharWidth; return estimatedNumChars + estimatedNumChars / 20; } // Replaced by precompiled CCMap (see bug 180266). To update the list // of characters, see one of files included below. As for the way // the original list of characters was obtained by Frank Tang, see bug 54467. // Updated to fix the regression (bug 263411). The list contains // characters of the following Unicode character classes : Ps, Pi, Po, Pf, Pe. // (ref.: http://www.w3.org/TR/2004/CR-CSS21-20040225/selector.html#first-letter) // Note that the file does NOT yet include non-BMP characters because // there's no point including them without fixing the way we identify // 'first-letter' currently working only with BMP characters. #include "punct_marks.ccmap" DEFINE_CCMAP(gPuncCharsCCMap, const); #define IsPunctuationMark(ch) (CCMAP_HAS_CHAR(gPuncCharsCCMap, ch)) static PRBool CanBreakBetween(nsTextFrame* aBefore, PRBool aBreakWhitespaceBefore, nsTextFrame* aAfter, PRBool aBreakWhitespaceAfter, PRBool aSkipLeadingWhitespaceAfter, nsIFrame* aLineContainer) { // This assumes text transforms don't change text breaking properties const nsTextFragment* fragBefore = aBefore->GetContent()->GetText(); const nsTextFragment* fragAfter = aAfter->GetContent()->GetText(); NS_ASSERTION(fragBefore && fragAfter, "text frames with no text!"); // The end of the before-content PRInt32 beforeOffset = aBefore->GetContentOffset() + aBefore->GetContentLength(); // The start of the after-content PRInt32 afterOffset = aAfter->GetContentOffset(); PRInt32 afterLength = fragAfter->GetLength() - afterOffset; if (beforeOffset <= 0 || afterLength <= 0) { // This shouldn't really happen, text frames shouldn't map no content #if 0 NS_WARNING("Textframe maps no content"); #endif return PR_FALSE; } PRUnichar lastBefore = fragBefore->CharAt(beforeOffset - 1); PRUnichar firstAfter = fragAfter->CharAt(afterOffset); // If we're skipping leading whitespace in the after-frame, and we actually // have leading whitespace in the after-frame, then we can't break before // it. We will rely on a saved break opportunity from the end of the last frame // (if any). The problem is that we can't accurately figure out here whether // a break is allowed. if (aSkipLeadingWhitespaceAfter && XP_IS_SPACE(firstAfter)) return PR_FALSE; while (IS_DISCARDED(firstAfter)) { ++afterOffset; --afterLength; if (afterLength == 0) { // aAfter will be entirely skipped. No breaking allowed here. return PR_FALSE; } firstAfter = fragAfter->CharAt(afterOffset); } while (IS_DISCARDED(lastBefore)) { --beforeOffset; if (beforeOffset == 0) { // aBefore was entirely skipped. Who knows why it called SetTrailingTextFrame. NS_WARNING("Before-frame should not have called SetTrailingTextFrame"); return PR_FALSE; } lastBefore = fragBefore->CharAt(beforeOffset - 1); } // If the character before or after the boundary is a breakable whitespace // character that's not skipped, we're good to break. if ((XP_IS_SPACE(lastBefore) && aBreakWhitespaceBefore) || (XP_IS_SPACE(firstAfter) && aBreakWhitespaceAfter)) return PR_TRUE; // See nsJISx4051LineBreaker::BreakInBetween ... characters 0-255 don't // trigger complex line breaking behaviour. This is an approximation since // currently nsJISx4051LineBreaker can look far along a line, but it'll do // until the line breaker is sorted out. if (!fragBefore->Is2b() && !fragAfter->Is2b()) return PR_FALSE; nsIFrame* f = nsLayoutUtils::GetClosestCommonAncestorViaPlaceholders(aBefore, aAfter, aLineContainer); NS_ASSERTION(f, "Frames to check not in the same document???"); // Check if our nearest common ancestor allows wrapping between its children if (f->GetStyleText()->WhiteSpaceCanWrap()) return PR_FALSE; nsAutoString beforeStr; nsAutoString afterStr; fragBefore->AppendTo(beforeStr, 0, beforeOffset); fragAfter->AppendTo(afterStr, afterOffset, afterLength); return nsContentUtils::LineBreaker()->BreakInBetween( beforeStr.get(), beforeStr.Length(), afterStr.get(), afterStr.Length()); } nsReflowStatus nsTextFrame::MeasureText(nsPresContext* aPresContext, const nsHTMLReflowState& aReflowState, nsTextTransformer& aTx, nsTextStyle& aTs, TextReflowData& aTextData) { PRBool firstThing = PR_TRUE; nscoord maxWidth = aReflowState.availableWidth; nsLineLayout& lineLayout = *aReflowState.mLineLayout; PRInt32 contentLength = aTx.GetContentLength(); PRInt32 startingOffset = aTextData.mOffset; PRInt32 column = mColumn; nscoord prevAscent = 0, prevDescent = 0; PRInt32 lastWordLen = 0; PRUnichar* lastWordPtr = nsnull; PRBool endsInWhitespace = PR_FALSE; PRBool endsInNewline = PR_FALSE; PRBool justDidFirstLetter = PR_FALSE; nsTextDimensions dimensions, lastWordDimensions; PRBool measureTextRuns = PR_FALSE; // Check whether we can break between the last text frame (if any) and this one PRBool trailingTextFrameCanWrap; nsIFrame* lastTextFrame = lineLayout.GetTrailingTextFrame(&trailingTextFrameCanWrap); PRBool canBreakBetweenTextFrames = PR_FALSE; if (lastTextFrame) { NS_ASSERTION(lastTextFrame->GetType() == nsGkAtoms::textFrame, "Trailing text frame isn't text!"); canBreakBetweenTextFrames = CanBreakBetween(NS_STATIC_CAST(nsTextFrame*, lastTextFrame), trailingTextFrameCanWrap, this, aTextData.mWrapping, aTextData.mSkipWhitespace, lineLayout.GetLineContainerFrame()); } if (contentLength == 0) { aTextData.mX = 0; aTextData.mAscent = 0; aTextData.mDescent = 0; return NS_FRAME_COMPLETE; } #if defined(_WIN32) || defined(XP_OS2) || defined(MOZ_X11) || defined(XP_BEOS) // see if we have implementation for GetTextDimensions() PRUint32 hints = 0; aReflowState.rendContext->GetHints(hints); if (hints & NS_RENDERING_HINT_FAST_MEASURE) { measureTextRuns = !aTs.mPreformatted && !aTs.mSmallCaps && !aTs.mWordSpacing && !aTs.mLetterSpacing && aTextData.mWrapping; } // Don't measure text runs with letter spacing active, it doesn't work // it also doesn't work if we are not word-wrapping (bug 42832) #endif /* defined(_WIN32) || defined(XP_OS2) || defined(MOZ_X11) || defined(XP_BEOS)*/ TextRun textRun; PRUint32 estimatedNumChars = EstimateNumChars(maxWidth - aTextData.mX, aTs.mAveCharWidth); #ifdef IBMBIDI nsTextFrame* nextBidi = nsnull; PRInt32 start = -1, end; if (mState & NS_FRAME_IS_BIDI) { nextBidi = NS_STATIC_CAST(nsTextFrame*, GetLastInFlow()->GetNextContinuation()); if (nextBidi) { if (mContentLength < 1) { mContentLength = 1; } nextBidi->GetOffsets(start, end); if (start <= mContentOffset) { nextBidi->AdjustOffsetsForBidi(mContentOffset + mContentLength, end); } else { mContentLength = start - mContentOffset; } } } #endif //IBMBIDI aTextData.mX = 0; if (aTextData.mMeasureText) { aTs.mNormalFont->GetMaxAscent(aTextData.mAscent); aTs.mNormalFont->GetMaxDescent(aTextData.mDescent); } PRBool firstWordDone = PR_FALSE; for (;;) { #ifdef IBMBIDI if (nextBidi && (mContentLength <= 0) ) { if (textRun.IsBuffering()) { // Measure the remaining text goto MeasureTextRun; } else { break; } } #endif // IBMBIDI // Get next word/whitespace from the text PRBool isWhitespace, wasTransformed; PRInt32 wordLen, contentLen; union { char* bp1; PRUnichar* bp2; }; #ifdef IBMBIDI wordLen = start; #endif // IBMBIDI bp2 = aTx.GetNextWord(aTextData.mInWord, &wordLen, &contentLen, &isWhitespace, &wasTransformed, textRun.mNumSegments == 0); // We need to set aTextData.mCanBreakBefore to true after 1st word. But we can't set // aTextData.mCanBreakBefore without seeing the 2nd word. That's because this frame // may only contain part of one word, the other part is in next frame. // we don't care if first word is whitespace, that will be addressed later. if (!aTextData.mCanBreakBefore && !firstThing && !isWhitespace) { firstWordDone = PR_TRUE; } #ifdef IBMBIDI if (nextBidi) { mContentLength -= contentLen; if (mContentLength < 0) { contentLen += mContentLength; wordLen = PR_MIN(wordLen, contentLen); } } #endif // IBMBIDI // Remember if the text was transformed if (wasTransformed) { mState |= TEXT_WAS_TRANSFORMED; } if (bp2) { if (firstWordDone) { // The first word has been processed, and 2nd word is seen // we can set it be breakable here after. aTextData.mCanBreakBefore = PR_TRUE; } } else { if (textRun.IsBuffering()) { // Measure the remaining text goto MeasureTextRun; } else { // Advance the offset in case we just consumed a bunch of // discarded characters. Otherwise, if this is the first piece // of content for this frame we will attempt to break-before it. aTextData.mOffset += contentLen; break; } } lastWordLen = wordLen; lastWordPtr = bp2; aTextData.mInWord = PR_FALSE; // Measure the word/whitespace PRUnichar firstChar; if (aTx.TransformedTextIsAscii()) { firstChar = *bp1; } else { firstChar = *bp2; } if (isWhitespace) { if ('\n' == firstChar) { // We hit a newline. Stop looping. NS_ASSERTION(aTs.mPreformatted, "newline w/o ts.mPreformatted"); aTextData.mOffset++; endsInWhitespace = PR_TRUE; endsInNewline = PR_TRUE; break; } if (aTextData.mSkipWhitespace) { aTextData.mOffset += contentLen; aTextData.mSkipWhitespace = PR_FALSE; // XXXldb Eh? if (wasTransformed) { // As long as there were no discarded characters, then don't consider // skipped leading whitespace as being transformed if (wordLen == contentLen) { mState &= ~TEXT_WAS_TRANSFORMED; } } // Only set flag when we actually do skip whitespace mState |= TEXT_SKIP_LEADING_WS; continue; } firstThing = PR_FALSE; // NOTE: Even if the textRun absorbs the whitespace below, we still // want to remember that we're breakable. aTextData.mCanBreakBefore = PR_TRUE; aTextData.mFirstLetterOK = PR_FALSE; if ('\t' == firstChar) { // Expand tabs to the proper width wordLen = 8 - (7 & column); // Apply word spacing to every space derived from a tab dimensions.width = (aTs.mSpaceWidth + aTs.mWordSpacing + aTs.mLetterSpacing)*wordLen; // Because we have to expand the tab when rendering consider that // a transformation of the text mState |= TEXT_WAS_TRANSFORMED; } else if (textRun.IsBuffering()) { // Add a whitespace segment textRun.AddSegment(wordLen, contentLen, PR_TRUE); continue; } else { // Apply word spacing to every space, if there's more than one dimensions.width = wordLen*(aTs.mWordSpacing + aTs.mLetterSpacing + aTs.mSpaceWidth);// XXX simplistic } //Even if there is not enough space for this "space", we still put it //here instead of next line column += wordLen; endsInWhitespace = PR_TRUE; aTextData.mOffset += contentLen; if (aTextData.mMeasureText) { //if we're wrapping, then don't add the whitespace width to the // x-offset unless the whitespace will fit within maxWidth.'' if (aTextData.mWrapping) { if (aTextData.mX + dimensions.width <= maxWidth) { aTextData.mX += dimensions.width; } else { // since we didn't add the trailing space width, set this flag so that // we will not trim this non-existing space aTextData.mTrailingSpaceTrimmed = PR_TRUE; // Note: word-spacing or letter-spacing can make the "space" really // wide. But since this space is left out from our width, linelayout // may still try to fit something narrower at the end of the line. // So on return (see below), we flag a soft-break status to ensure // that linelayout doesn't place something where the "space" should // be. break; } } else { //if we're not wrapping, then always advance // the x-offset regardless of maxWidth aTextData.mX += dimensions.width; } } //(aTextData.mMeasureText) } else { firstThing = PR_FALSE; aTextData.mSkipWhitespace = PR_FALSE; // XXX :first-letter should be handled during frame construction // (and it has a good bit in common with nextBidi) if (aTextData.mFirstLetterOK) { if (IsPunctuationMark(firstChar)) { if (contentLen > 1) { wordLen = 2; contentLen = 2; } } else { wordLen = 1; contentLen = 1; } justDidFirstLetter = PR_TRUE; } if (aTextData.mMeasureText) { if (measureTextRuns && !justDidFirstLetter) { // Add another word to the text run textRun.AddSegment(wordLen, contentLen, PR_FALSE); // See if we should measure the text if ((textRun.mTotalNumChars >= estimatedNumChars) || (textRun.mNumSegments >= (TEXT_MAX_NUM_SEGMENTS - 1))) { goto MeasureTextRun; } } else { if (aTs.mSmallCaps) { MeasureSmallCapsText(aReflowState.rendContext, aTs, bp2, wordLen, PR_FALSE, &dimensions); } else { // Measure just the one word if (aTx.TransformedTextIsAscii()) { aReflowState.rendContext->GetTextDimensions(bp1, wordLen, dimensions); } else { aReflowState.rendContext->GetTextDimensions(bp2, wordLen, dimensions); } #ifdef MOZ_MATHML // If GetBoundingMetrics is available, use the exact glyph metrics // for ::first-letter // XXX remove the #ifdef if GetBoundingMetrics becomes mainstream if (justDidFirstLetter) { nsresult res; nsBoundingMetrics bm; if (aTx.TransformedTextIsAscii()) { res = aReflowState.rendContext->GetBoundingMetrics(bp1, wordLen, bm); } else { res = aReflowState.rendContext->GetBoundingMetrics(bp2, wordLen, bm); } if (NS_SUCCEEDED(res)) { aTextData.mAscent = dimensions.ascent = bm.ascent; aTextData.mDescent = dimensions.descent = bm.descent; } } #endif if (aTs.mLetterSpacing) { dimensions.width += aTs.mLetterSpacing * wordLen; } if (aTs.mWordSpacing) { if (aTx.TransformedTextIsAscii()) { for (char* bp = bp1; bp < bp1 + wordLen; bp++) { if (*bp == ' ') // || *bp == CH_CJKSP) dimensions.width += aTs.mWordSpacing; } } else { for (PRUnichar* bp = bp2; bp < bp2 + wordLen; bp++) { if (*bp == ' ') // || *bp == CH_CJKSP) dimensions.width += aTs.mWordSpacing; } } } } lastWordDimensions = dimensions; PRBool canBreak; if (0 == aTextData.mX) { canBreak = canBreakBetweenTextFrames; // Allow breaking between text frames even if mWrapping is false // (e.g., we're white-space:pre). If canBreakBetweenTextFrames is // true, then the previous text frame's mWrapping must have been // true, and we allow breaking between text frames if at least // one of them allows breaking. } else { canBreak = aTextData.mWrapping; } if (canBreak) { // Remember that we *could* have broken here, even if we choose not to PRBool forceBreak = lineLayout.NotifyOptionalBreakPosition(GetContent(), aTextData.mOffset, PR_TRUE); // See if there is room for the text if (forceBreak || (aTextData.mX + dimensions.width > maxWidth)) { // The text will not fit, or a break was forced. break; } } prevAscent = aTextData.mAscent; prevDescent = aTextData.mDescent; aTextData.mX += dimensions.width; if (aTextData.mAscent < dimensions.ascent) { aTextData.mAscent = dimensions.ascent; } if (aTextData.mDescent < dimensions.descent) { aTextData.mDescent = dimensions.descent; } column += wordLen; endsInWhitespace = PR_FALSE; aTextData.mOffset += contentLen; if (justDidFirstLetter) { // Time to stop break; } } } else { // Remember that we *could* have broken before this chunk of text. PRBool canBreak; if (aTextData.mOffset == startingOffset) { canBreak = canBreakBetweenTextFrames; } else { canBreak = aTextData.mWrapping; } if (canBreak) { #ifdef DEBUG PRBool forceBreak = #endif lineLayout.NotifyOptionalBreakPosition(GetContent(), aTextData.mOffset, PR_TRUE); NS_ASSERTION(!forceBreak, "If we're supposed to break, we should be " "really measuring"); } // We didn't measure the text, but we need to update our state column += wordLen; endsInWhitespace = PR_FALSE; aTextData.mOffset += contentLen; if (justDidFirstLetter) { // Time to stop break; } } } continue; MeasureTextRun: #if defined(_WIN32) || defined(XP_OS2) || defined(MOZ_X11) || defined(XP_BEOS) // see if we have implementation for GetTextDimensions() if (hints & NS_RENDERING_HINT_FAST_MEASURE) { PRInt32 numCharsFit; PRInt32 forcedOffset = lineLayout.GetForcedBreakPosition(GetContent()); PRInt32 measureChars = textRun.mTotalNumChars; if (forcedOffset == -1) { forcedOffset -= aTextData.mOffset; #ifdef MOZ_CAIRO_GFX NS_ASSERTION(forcedOffset >= 0, "Overshot forced offset, we should have already exited"); #endif if (forcedOffset >= 0 && forcedOffset < textRun.mTotalNumChars) { // Only measure up to forcedOffset characters. We still need to measure // to make sure we get the right text dimensions, even though we know // where we're going to break. This will reduce the number of chars that // fit, which we then detect as a required break. measureChars = forcedOffset; } } // These calls can return numCharsFit not positioned at a break in the textRun. Beware. if (aTx.TransformedTextIsAscii()) { aReflowState.rendContext->GetTextDimensions((char*)aTx.GetWordBuffer(), measureChars, maxWidth - aTextData.mX, textRun.mBreaks, textRun.mNumSegments, dimensions, numCharsFit, lastWordDimensions); } else { aReflowState.rendContext->GetTextDimensions(aTx.GetWordBuffer(), measureChars, maxWidth - aTextData.mX, textRun.mBreaks, textRun.mNumSegments, dimensions, numCharsFit, lastWordDimensions); } // See how much of the text fit PRBool canBreak; if (0 == aTextData.mX) { canBreak = canBreakBetweenTextFrames; } else { canBreak = aTextData.mWrapping; } if (canBreak && aTextData.mX + dimensions.width > maxWidth) { // None of the text fits #ifdef IBMBIDI nextBidi = nsnull; #endif // IBMBIDI break; } // Find the index of the last segment that fit PRInt32 lastSegment; if (numCharsFit >= textRun.mTotalNumChars) { // fast path, normal case NS_ASSERTION(numCharsFit == textRun.mTotalNumChars, "shouldn't overshoot"); lastSegment = textRun.mNumSegments - 1; } else { for (lastSegment = 0; textRun.mBreaks[lastSegment] < numCharsFit; lastSegment++) ; NS_ASSERTION(lastSegment < textRun.mNumSegments, "failed to find segment"); // now we have textRun.mBreaks[lastSegment] >= numCharsFit /* O'Callahan XXX: This snippet together with the snippet below prevents mail from loading Justification seems to work just fine without these changes. We get into trouble in a case where lastSegment gets set to -1 if (textRun.mBreaks[lastSegment] > numCharsFit) { // NOTE: this segment did not actually fit! lastSegment--; } */ } /* O'Callahan XXX: This snippet together with the snippet above prevents mail from loading if (lastSegment < 0) { // no segments fit break; } */ aTextData.mX += dimensions.width; if (aTextData.mAscent < dimensions.ascent) { aTextData.mAscent = dimensions.ascent; } if (aTextData.mDescent < dimensions.descent) { aTextData.mDescent = dimensions.descent; } // this is where to backup if line-breaking happens to push the last word prevAscent = aTextData.mAscent; prevDescent = aTextData.mDescent; // we can now consider the last word since we know where to backup if (aTextData.mAscent < lastWordDimensions.ascent) { aTextData.mAscent = lastWordDimensions.ascent; } if (aTextData.mDescent < lastWordDimensions.descent) { aTextData.mDescent = lastWordDimensions.descent; } endsInWhitespace = textRun.mSegments[lastSegment].IsWhitespace(); // Save last possible backup position. Whitespace segments are always // breakable since nsTextTransformer reports preformatted spaces as // "not whitespace". Note that each segment is either entirely whitespace // or entirely non-whitespace. PRInt32 lastWhitespaceSegment = endsInWhitespace ? lastSegment : lastSegment - 1; if (lastWhitespaceSegment >= 0) { lineLayout.NotifyOptionalBreakPosition(GetContent(), aTextData.mOffset + textRun.mSegments[lastWhitespaceSegment].ContentLen(), PR_TRUE); } column += numCharsFit; aTextData.mOffset += textRun.mSegments[lastSegment].ContentLen(); // If all the text didn't fit, then we're done if (numCharsFit != textRun.mTotalNumChars) { #ifdef IBMBIDI nextBidi = nsnull; #endif // IBMBIDI break; } #ifdef IBMBIDI if (nextBidi && (mContentLength <= 0) ) { break; } #endif // IBMBIDI if (nsnull == bp2) { // No more text so we're all finished. Advance the offset in case the last // call to GetNextWord() discarded characters aTextData.mOffset += contentLen; break; } // Reset the number of text run segments textRun.Reset(); // Estimate the remaining number of characters we think will fit estimatedNumChars = EstimateNumChars(maxWidth - aTextData.mX, aTs.mAveCharWidth); } #endif /* defined(_WIN32) || defined(XP_OS2) || defined(MOZ_X11) || defined(XP_BEOS) */ ; } // If we didn't actually measure any text, then make sure it looks // like we did if (!aTextData.mMeasureText) { aTextData.mAscent = mAscent; aTextData.mDescent = mRect.height - aTextData.mAscent; aTextData.mX = mRect.width; if (mState & TEXT_TRIMMED_WS) { // Add back in the width of a space since it was trimmed away last time // NOTE: Trailing whitespace includes word and letter spacing! aTextData.mX += aTs.mSpaceWidth + aTs.mWordSpacing + aTs.mLetterSpacing; } } // 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, nor should it // be setting InWord state, and it should be ignored when subsequent text // considers whether it can break-before. lineLayout.SetColumn(column); lineLayout.SetUnderstandsWhiteSpace(PR_TRUE); if (0 != aTextData.mX) { lineLayout.SetTrailingTextFrame(this, aTextData.mWrapping); lineLayout.SetEndsInWhiteSpace(endsInWhitespace); lineLayout.SetInWord(!endsInWhitespace); } else { // Don't allow subsequent text frame to break-before. All our text is // being skipped (usually whitespace, could be discarded Unicode control // characters). lineLayout.SetTrailingTextFrame(nsnull, PR_FALSE); lineLayout.SetInWord(PR_FALSE); } if (justDidFirstLetter) { lineLayout.SetFirstLetterFrame(this); lineLayout.SetFirstLetterStyleOK(PR_FALSE); mState |= TEXT_FIRST_LETTER; } // Return our reflow status nsReflowStatus rs = (aTextData.mOffset == contentLength) #ifdef IBMBIDI || (aTextData.mOffset == start) #endif // IBMBIDI ? NS_FRAME_COMPLETE : NS_FRAME_NOT_COMPLETE; if (canBreakBetweenTextFrames && aTextData.mOffset == startingOffset) { // Couldn't place any text but we can break between text frames, so do that. return NS_INLINE_LINE_BREAK_BEFORE(); } if (endsInNewline) { lineLayout.SetLineEndsInBR(PR_TRUE); return NS_INLINE_LINE_BREAK_AFTER(rs); } if (aTextData.mTrailingSpaceTrimmed && rs == NS_FRAME_COMPLETE) { // Flag a soft-break that we can check (below) if we come back here lineLayout.SetLineEndsInSoftBR(PR_TRUE); } else if (lineLayout.GetLineEndsInSoftBR() && !lineLayout.GetEndsInWhiteSpace()) { // Break-before a word that follows the soft-break flagged earlier return NS_INLINE_LINE_BREAK_BEFORE(); } if (rs == NS_FRAME_COMPLETE && 0 != aTextData.mX && endsInWhitespace && aTextData.mWrapping) { // Remember the break opportunity at the end of this frame if (lineLayout.NotifyOptionalBreakPosition(GetContent(), aTextData.mOffset, aTextData.mX <= maxWidth)) return NS_INLINE_LINE_BREAK_AFTER(rs); } if ((aTextData.mOffset != contentLength) && (aTextData.mOffset == startingOffset)) { // Break-before a long-word that doesn't fit here return NS_INLINE_LINE_BREAK_BEFORE(); } return rs; } /* virtual */ void nsTextFrame::MarkIntrinsicWidthsDirty() { // Clear the TEXT_OPTIMIZE_RESIZE for the next time around. It'll get // reset late in Reflow. RemoveStateBits(TEXT_OPTIMIZE_RESIZE); nsFrame::MarkIntrinsicWidthsDirty(); } // XXX This should really share more code with the first half of MeasureText. /* virtual */ void nsTextFrame::AddInlineMinWidth(nsIRenderingContext *aRenderingContext, nsIFrame::InlineMinWidthData *aData) { nsresult rv; nsPresContext *presContext = PresContext(); nsTextStyle ts(presContext, *aRenderingContext, mStyleContext); SetupTextRunDirection(presContext, aRenderingContext); if (!ts.mFont->mSize) // XXX If font size is zero, we still need to figure out whether we've // got non-whitespace text and whether we end in whitespace. return; const nsStyleText *styleText = GetStyleText(); PRBool wrapping = styleText->WhiteSpaceCanWrap(); PRBool wsSignificant = styleText->WhiteSpaceIsSignificant(); PRBool atStart = PR_TRUE; PRBool forceArabicShaping = (ts.mSmallCaps || (0 != ts.mWordSpacing) || (0 != ts.mLetterSpacing) || ts.mJustifying); nsTextTransformer tx(presContext); // Keep the text in ascii if possible. Note that if we're measuring small // caps text then transform to Unicode because the helper function only // accepts Unicode text rv = tx.Init(this, mContent, mContentOffset, forceArabicShaping, !ts.mSmallCaps); if (NS_FAILED(rv)) { NS_NOTREACHED("failure initializing text transformer"); return; } if (aData->trailingTextFrame && CanBreakBetween(NS_STATIC_CAST(nsTextFrame*, aData->trailingTextFrame), aData->trailingTextFrame-> GetStyleText()->WhiteSpaceCanWrap(), this, wrapping, aData->skipWhitespace, // XXX ??? nsnull)) // XXX Better to pass real frame { aData->Break(aRenderingContext); } for (;;) { union { char* bp1; PRUnichar* bp2; }; PRInt32 wordLen = -1, contentLen; PRBool isWhitespace, wasTransformed; // XXX Is !aData->skipWhitespace the right criterion for when the // text transformer should capitalize the first letter? bp2 = tx.GetNextWord(!aData->skipWhitespace, &wordLen, &contentLen, &isWhitespace, &wasTransformed); if (!bp2) break; // XXX Watch mContentLength! if (isWhitespace) { PRUnichar firstChar; if (tx.TransformedTextIsAscii()) { firstChar = *bp1; } else { firstChar = *bp2; } if ('\n' == firstChar) { aData->Break(aRenderingContext); aData->skipWhitespace = PR_TRUE; aData->trailingWhitespace = 0; } else if (!aData->skipWhitespace || wsSignificant) { atStart = PR_FALSE; nscoord width; if ('\t' == firstChar) { // XXX Need to track column! wordLen = 8; // Apply word spacing to every space derived from a tab width = (ts.mSpaceWidth + ts.mWordSpacing + ts.mLetterSpacing)*wordLen; } else { // Apply word spacing to every space, if there's more than one width = wordLen*(ts.mWordSpacing + ts.mLetterSpacing + ts.mSpaceWidth);// XXX simplistic } aData->currentLine += width; if (wsSignificant) { aData->trailingWhitespace = 0; aData->skipWhitespace = PR_FALSE; } else { aData->trailingWhitespace += width; aData->skipWhitespace = PR_TRUE; } if (wrapping) { aData->Break(aRenderingContext); } } } else { if (!atStart && wrapping) { aData->Break(aRenderingContext); } atStart = PR_FALSE; nscoord width; if (ts.mSmallCaps) { nsTextDimensions dimensions; // MeasureSmallCapsText measures one character at a time so it *should* // be OK to just say "LTR" here without breaking things (any more than // they're already broken) aRenderingContext->SetTextRunRTL(PR_FALSE); MeasureSmallCapsText(aRenderingContext, ts, bp2, wordLen, PR_FALSE, &dimensions); width = dimensions.width; } else { // Unfortunately we might have mixed-directionality text at this point, // and we might not know our embedding level. So we have to make some // approximations, until bidi resolution is moved to frame construction. if (tx.TransformedTextIsAscii()) { // It may not actually be LTR, but hopefully the width doesn't care aRenderingContext->SetTextRunRTL(PR_FALSE); aRenderingContext->GetWidth(bp1, wordLen, width); } else { // We may get the directions reversed but at least we'll be breaking // the string up and measuring segments in the same direction width = nsLayoutUtils::GetStringWidth(this, aRenderingContext, bp2, wordLen); } width += ts.mLetterSpacing * wordLen; } aData->currentLine += width; aData->skipWhitespace = PR_FALSE; aData->trailingWhitespace = 0; } } aData->trailingTextFrame = this; } /* virtual */ void nsTextFrame::AddInlinePrefWidth(nsIRenderingContext *aRenderingContext, nsIFrame::InlinePrefWidthData *aData) { nsresult rv; nsPresContext *presContext = PresContext(); nsTextStyle ts(presContext, *aRenderingContext, mStyleContext); if (!ts.mFont->mSize) // XXX If font size is zero, we still need to figure out whether we've // got non-whitespace text and whether we end in whitespace. return; PRBool forceArabicShaping = (ts.mSmallCaps || (0 != ts.mWordSpacing) || (0 != ts.mLetterSpacing) || ts.mJustifying); nsTextTransformer tx(presContext); // Keep the text in ascii if possible. Note that if we're measuring small // caps text then transform to Unicode because the helper function only // accepts Unicode text rv = tx.Init(this, mContent, mContentOffset, forceArabicShaping, !ts.mSmallCaps); if (NS_FAILED(rv)) { NS_NOTREACHED("failure initializing text transformer"); return; } for (;;) { union { char* bp1; PRUnichar* bp2; }; PRInt32 wordLen = -1, contentLen; PRBool isWhitespace, wasTransformed; // XXX Should fix this to use something better than GetNextWord! // XXX Is !aData->skipWhitespace the right criterion for when the // text transformer should capitalize the first letter? bp2 = tx.GetNextWord(!aData->skipWhitespace, &wordLen, &contentLen, &isWhitespace, &wasTransformed); if (!bp2) break; // XXX Watch mContentLength! if (isWhitespace) { PRUnichar firstChar; if (tx.TransformedTextIsAscii()) { firstChar = *bp1; } else { firstChar = *bp2; } if ('\n' == firstChar) { aData->Break(aRenderingContext); } else if (!aData->skipWhitespace) { nscoord width; if ('\t' == firstChar) { // XXX Need to track column! wordLen = 8; // Apply word spacing to every space derived from a tab width = (ts.mSpaceWidth + ts.mWordSpacing + ts.mLetterSpacing)*wordLen; } else { // Apply word spacing to every space, if there's more than one width = wordLen*(ts.mWordSpacing + ts.mLetterSpacing + ts.mSpaceWidth);// XXX simplistic } aData->currentLine += width; if (GetStyleText()->WhiteSpaceIsSignificant()) // XXX Should we also subtract the old value of // trailingWhitespace from currentLine? aData->trailingWhitespace = 0; else aData->trailingWhitespace += width; } } else { nscoord width; if (ts.mSmallCaps) { nsTextDimensions dimensions; // MeasureSmallCapsText measures one character at a time so it *should* // be OK to just say "LTR" here without breaking things (any more than // they're already broken) aRenderingContext->SetTextRunRTL(PR_FALSE); MeasureSmallCapsText(aRenderingContext, ts, bp2, wordLen, PR_FALSE, &dimensions); width = dimensions.width; } else { // Unfortunately we might have mixed-directionality text at this point, // because we may not have reflowed yet, and we might not know our // embedding level. So we have to make some approximations, until // bidi resolution is moved to frame construction. if (tx.TransformedTextIsAscii()) { // It may not actually be LTR, but hopefully the width doesn't care aRenderingContext->SetTextRunRTL(PR_FALSE); aRenderingContext->GetWidth(bp1, wordLen, width); } else { // We may get the directions reversed but at least we'll be breaking // the string up and measuring segments in the same direction width = nsLayoutUtils::GetStringWidth(this, aRenderingContext, bp2, wordLen); } width += ts.mLetterSpacing * wordLen; } aData->currentLine += width; aData->skipWhitespace = PR_FALSE; aData->trailingWhitespace = 0; } } } /* virtual */ nsSize nsTextFrame::ComputeSize(nsIRenderingContext *aRenderingContext, nsSize aCBSize, nscoord aAvailableWidth, nsSize aMargin, nsSize aBorder, nsSize aPadding, PRBool aShrinkWrap) { // Inlines and text don't compute size before reflow. return nsSize(NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE); } NS_IMETHODIMP nsTextFrame::Reflow(nsPresContext* aPresContext, nsHTMLReflowMetrics& aMetrics, const nsHTMLReflowState& aReflowState, nsReflowStatus& aStatus) { DO_GLOBAL_REFLOW_COUNT("nsTextFrame"); DISPLAY_REFLOW(aPresContext, this, aReflowState, aMetrics, aStatus); #ifdef NOISY_REFLOW ListTag(stdout); printf(": BeginReflow: availableSize=%d,%d\n", aReflowState.availableWidth, aReflowState.availableHeight); #endif mState &= ~TEXT_IS_END_OF_LINE; // 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; #ifdef MOZ_MATHML if (NS_REFLOW_CALC_BOUNDING_METRICS & aMetrics.mFlags) aMetrics.mBoundingMetrics.Clear(); #endif return NS_OK; } // Get starting offset into the content PRInt32 startingOffset = 0; nsIFrame* prevInFlow = GetPrevInFlow(); if (nsnull != prevInFlow) { nsTextFrame* prev = NS_STATIC_CAST(nsTextFrame*, prevInFlow); startingOffset = prev->mContentOffset + prev->mContentLength; // If our starting offset doesn't agree with mContentOffset, then our // prev-in-flow has changed the number of characters it maps and so we // need to measure text and not try and optimize a resize reflow if (startingOffset != mContentOffset) { mState &= ~TEXT_OPTIMIZE_RESIZE; } } nsLineLayout& lineLayout = *aReflowState.mLineLayout; nsTextStyle ts(aPresContext, *aReflowState.rendContext, mStyleContext); SetupTextRunDirection(aPresContext, aReflowState.rendContext); if ( (mContentLength > 0) && (mState & NS_FRAME_IS_BIDI) ) { startingOffset = mContentOffset; } if (aPresContext->BidiEnabled()) { nsCharType charType = eCharType_LeftToRight; PRUint32 hints = 0; aReflowState.rendContext->GetHints(hints); charType = (nsCharType)NS_PTR_TO_INT32(aPresContext->PropertyTable()->GetProperty(this, nsGkAtoms::charType)); if ((eCharType_RightToLeftArabic == charType && (hints & NS_RENDERING_HINT_ARABIC_SHAPING) == NS_RENDERING_HINT_ARABIC_SHAPING) || (eCharType_RightToLeft == charType && (hints & NS_RENDERING_HINT_BIDI_REORDERING) == NS_RENDERING_HINT_BIDI_REORDERING)) { // XXXldb This needs to happen before |Reflow|. aPresContext->SetIsBidiSystem(PR_TRUE); } } // Clear out the reflow state flags in mState (without destroying // the TEXT_BLINK_ON bit). PRBool lastTimeWeSkippedLeadingWS = 0 != (mState & TEXT_SKIP_LEADING_WS); mState &= ~TEXT_REFLOW_FLAGS; if (aReflowState.mFlags.mBlinks) { if (0 == (mState & TEXT_BLINK_ON)) { mState |= TEXT_BLINK_ON; nsBlinkTimer::AddBlinkFrame(aPresContext, this); } } else { if (0 != (mState & TEXT_BLINK_ON)) { mState &= ~TEXT_BLINK_ON; nsBlinkTimer::RemoveBlinkFrame(this); } } PRBool wrapping = ts.mText->WhiteSpaceCanWrap(); // Set whitespace skip flag PRBool skipWhitespace = PR_FALSE; if (!ts.mPreformatted) { if (lineLayout.GetEndsInWhiteSpace()) { skipWhitespace = PR_TRUE; } } nscoord maxWidth = aReflowState.availableWidth; // Setup text transformer to transform this frames text content nsIDocument* doc = mContent->GetDocument(); if (!doc) { NS_WARNING("Content has no document."); return NS_ERROR_FAILURE; } PRBool forceArabicShaping = (ts.mSmallCaps || (0 != ts.mWordSpacing) || (0 != ts.mLetterSpacing) || ts.mJustifying); nsTextTransformer tx(aPresContext); // Keep the text in ascii if possible. Note that if we're measuring small // caps text then transform to Unicode because the helper function only // accepts Unicode text nsresult rv = tx.Init(this, mContent, startingOffset, forceArabicShaping, !ts.mSmallCaps); if (NS_OK != rv) { return rv; } //PRInt32 contentLength = tx.GetContentLength(); // 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) { mState |= TEXT_IN_WORD; } mState &= ~TEXT_FIRST_LETTER; PRInt32 column = lineLayout.GetColumn(); PRInt32 prevColumn = mColumn; mColumn = column; PRBool measureText = PR_TRUE; // We can avoid actually measuring the text if: // - intrinsic widths haven't been marked dirty (which clears // TEXT_OPTIMIZE_RESIZE) // - we don't have a next in flow // - the previous reflow successfully reflowed all text in the // available space // - we aren't computing the max element size (that requires we measure // text) // - skipping leading whitespace is the same as it was the last time // - we're wrapping text and the available width is at least as big as our // current frame width -or- // we're not wrapping text and we're at the same column as before (this is // an issue for preformatted tabbed text only) // - AND we aren't justified (in which case the frame width has already been tweaked and can't be used) nscoord realWidth = mRect.width; if (mState & TEXT_TRIMMED_WS) { // NOTE: Trailing whitespace includes word and letter spacing! realWidth += ts.mSpaceWidth + ts.mWordSpacing + ts.mLetterSpacing; } if (!GetNextInFlow() && (mState & TEXT_OPTIMIZE_RESIZE) && lineLayout.GetForcedBreakPosition(GetContent()) == -1 && (lastTimeWeSkippedLeadingWS == skipWhitespace) && ((wrapping && (maxWidth >= realWidth)) || (!wrapping && (prevColumn == column))) && #ifdef IBMBIDI (0 == (mState & NS_FRAME_IS_BIDI) ) && #endif // IBMBIDI !ts.mJustifying) { // We can skip measuring of text and use the value from our // previous reflow measureText = PR_FALSE; #ifdef NOISY_REFLOW printf(" => measureText=%s wrapping=%s skipWhitespace=%s", measureText ? "yes" : "no", wrapping ? "yes" : "no", skipWhitespace ? "yes" : "no"); printf(" realWidth=%d maxWidth=%d\n", realWidth, maxWidth); #endif } // Local state passed to the routines that do the actual text measurement TextReflowData textData(startingOffset, wrapping, skipWhitespace, measureText, inWord, lineLayout.GetFirstLetterStyleOK(), lineLayout.LineIsBreakable(), PR_FALSE); // Measure the text // MeasureText may set TEXT_TRIMMED_WS flag, so don't clear after the call if (ts.mFont->mSize) aStatus = MeasureText(aPresContext, aReflowState, tx, ts, textData); else { textData.mX = 0; textData.mAscent = 0; textData.mDescent = 0; aStatus = NS_FRAME_COMPLETE; } if (textData.mTrailingSpaceTrimmed) mState |= TEXT_TRIMMED_WS; else mState &= ~TEXT_TRIMMED_WS; if (tx.HasMultibyte()) { mState |= TEXT_HAS_MULTIBYTE; } // Setup metrics for caller; store final max-element-size information aMetrics.width = textData.mX; if ((0 == textData.mX) && !ts.mPreformatted) { aMetrics.height = 0; aMetrics.ascent = 0; } else { aMetrics.ascent = textData.mAscent; aMetrics.height = textData.mAscent + textData.mDescent; } mAscent = aMetrics.ascent; // Set content offset and length mContentOffset = startingOffset; mContentLength = textData.mOffset - startingOffset; // Compute space and letter counts for justification, if required // Also use this one-shot path to compute the metrics needed for MathML, if required // (the flag is set only if this text happens to be inside MathML) PRBool calcMathMLMetrics = PR_FALSE; nsAutoTextBuffer* textBufferPtr = nsnull; #ifdef MOZ_MATHML nsAutoTextBuffer textBuffer; calcMathMLMetrics = (NS_REFLOW_CALC_BOUNDING_METRICS & aMetrics.mFlags) != 0; if (calcMathMLMetrics) { textBufferPtr = &textBuffer; // always use the Unicode path with MathML fonts in gfx, it is safer this way mState |= TEXT_HAS_MULTIBYTE; } #endif if (ts.mJustifying || calcMathMLMetrics) { PRIntn numJustifiableCharacter; PRInt32 textLength; // This will include a space for trailing whitespace, if any is present. // This is corrected for in nsLineLayout::TrimWhiteSpaceIn. // This work could be done in MeasureText, but it's complex to do accurately // there because of the need to repair counts when wrapped words are backed out. // So I do it via PrepareUnicodeText ... a little slower perhaps, but a lot saner, // and it localizes the counting logic to one place. PrepareUnicodeText(tx, nsnull, textBufferPtr, &textLength, PR_TRUE, &numJustifiableCharacter); lineLayout.SetTextJustificationWeights(numJustifiableCharacter, textLength - numJustifiableCharacter); #ifdef MOZ_MATHML if (calcMathMLMetrics) { nsLayoutUtils::SetFontFromStyle(aReflowState.rendContext, mStyleContext); nsBoundingMetrics bm; rv = aReflowState.rendContext->GetBoundingMetrics(textBuffer.mBuffer, textLength, bm); if (NS_SUCCEEDED(rv)) aMetrics.mBoundingMetrics = bm; else { // Things didn't turn out well, just return the reflow metrics. aMetrics.mBoundingMetrics.ascent = aMetrics.ascent; aMetrics.mBoundingMetrics.descent = aMetrics.height - aMetrics.ascent; aMetrics.mBoundingMetrics.width = aMetrics.width; aMetrics.mBoundingMetrics.rightBearing = aMetrics.width; } } #endif } nscoord maxFrameWidth = mRect.width; nscoord maxFrameHeight = mRect.height; // For future resize reflows we would like to avoid measuring the text. // We can only do this if after this reflow we're: // - complete. If we're not complete then our desired width doesn't // represent our total size // - we fit in the available space. We may be complete, but if we // return a larger desired width than is available we may get pushed // and our frame width won't get set if (NS_FRAME_IS_COMPLETE(aStatus) && !NS_INLINE_IS_BREAK(aStatus) && (aMetrics.width <= maxWidth)) { mState |= TEXT_OPTIMIZE_RESIZE; mRect.width = aMetrics.width; } else { mState &= ~TEXT_OPTIMIZE_RESIZE; } // 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 || eReflowReason_Dirty == aReflowState.reason) {*/ // XXX See bug 71523 We should really adjust the frames x coordinate to // a pixel boundary to solve this. // For now we add 1 pixel to the width of the invalidated rect. // This fixes cases where the twips to pixel roundoff causes the invalidated // rect's width to be one pixel short. nscoord onePixel = nsPresContext::CSSPixelsToAppUnits(1); maxFrameWidth = PR_MAX(maxFrameWidth, mRect.width) + onePixel; maxFrameHeight = PR_MAX(maxFrameHeight, mRect.height); nsRect damage(0,0,maxFrameWidth,maxFrameHeight); Invalidate(damage); /*}*/ #ifdef NOISY_REFLOW ListTag(stdout); printf(": desiredSize=%d,%d(b=%d) status=%x\n", aMetrics.width, aMetrics.height, aMetrics.ascent, aStatus); #endif NS_FRAME_SET_TRUNCATION(aStatus, aReflowState, aMetrics); return NS_OK; } /* virtual */ PRBool nsTextFrame::CanContinueTextRun() const { // We can continue a text run through a text frame return PR_TRUE; } NS_IMETHODIMP nsTextFrame::TrimTrailingWhiteSpace(nsPresContext* aPresContext, nsIRenderingContext& aRC, nscoord& aDeltaWidth, PRBool& aLastCharIsJustifiable) { aLastCharIsJustifiable = PR_FALSE; mState |= TEXT_IS_END_OF_LINE; // in some situation (for instance, in wrapping mode, last space will not // be added to total width if it exceed maxwidth), this flag will be set // and we shouldn't trim non-added space if (mState & TEXT_TRIMMED_WS) { aDeltaWidth = 0; return NS_OK; } nscoord dw = 0; const nsStyleText* textStyle = GetStyleText(); if (mContentLength && (NS_STYLE_WHITESPACE_PRE != textStyle->mWhiteSpace) && (NS_STYLE_WHITESPACE_MOZ_PRE_WRAP != textStyle->mWhiteSpace)) { // Get the text fragments that make up our content const nsTextFragment* frag = mContent->GetText(); if (frag) { PRInt32 lastCharIndex = mContentOffset + mContentLength - 1; if (lastCharIndex < frag->GetLength()) { PRUnichar ch = frag->CharAt(lastCharIndex); if (XP_IS_SPACE(ch)) { // Get font metrics for a space so we can adjust the width by the // right amount. nsLayoutUtils::SetFontFromStyle(&aRC, mStyleContext); aRC.GetWidth(' ', dw); // NOTE: Trailing whitespace includes word and letter spacing! nsStyleUnit unit; unit = textStyle->mWordSpacing.GetUnit(); if (eStyleUnit_Coord == unit) { dw += textStyle->mWordSpacing.GetCoordValue(); } unit = textStyle->mLetterSpacing.GetUnit(); if (eStyleUnit_Coord == unit) { dw += textStyle->mLetterSpacing.GetCoordValue(); } aLastCharIsJustifiable = PR_TRUE; } else if (IsJustifiableCharacter(ch, IsChineseJapaneseLangGroup())) { aLastCharIsJustifiable = PR_TRUE; } } } } #ifdef NOISY_TRIM ListTag(stdout); printf(": trim => %d\n", dw); #endif if (0 != dw) { mState |= TEXT_TRIMMED_WS; } else { mState &= ~TEXT_TRIMMED_WS; } aDeltaWidth = dw; return NS_OK; } #ifdef DEBUG // Translate the mapped content into a string that's printable void nsTextFrame::ToCString(nsString& aBuf, PRInt32* aTotalContentLength) const { // Get the frames text content const nsTextFragment* frag = mContent->GetText(); if (!frag) { return; } // Compute the total length of the text content. *aTotalContentLength = frag->GetLength(); // Set current fragment and current fragment offset if (0 == mContentLength) { return; } PRInt32 fragOffset = mContentOffset; PRInt32 n = fragOffset + mContentLength; while (fragOffset < n) { PRUnichar ch = frag->CharAt(fragOffset++); if (ch == '\r') { aBuf.AppendLiteral("\\r"); } else if (ch == '\n') { aBuf.AppendLiteral("\\n"); } else if (ch == '\t') { aBuf.AppendLiteral("\\t"); } else if ((ch < ' ') || (ch >= 127)) { aBuf.AppendLiteral("\\0"); aBuf.AppendInt((PRInt32)ch, 8); } else { aBuf.Append(ch); } } } #endif nsIAtom* nsTextFrame::GetType() const { return nsGkAtoms::textFrame; } /* virtual */ PRBool nsTextFrame::IsEmpty() { NS_ASSERTION(!(mState & TEXT_IS_ONLY_WHITESPACE) || !(mState & TEXT_ISNOT_ONLY_WHITESPACE), "Invalid state"); // XXXldb Should this check compatibility mode as well??? if (GetStyleText()->WhiteSpaceIsSignificant()) { return PR_FALSE; } if (mState & TEXT_ISNOT_ONLY_WHITESPACE) { return PR_FALSE; } if (mState & TEXT_IS_ONLY_WHITESPACE) { return PR_TRUE; } PRBool isEmpty = mContent->TextIsOnlyWhitespace(); mState |= (isEmpty ? TEXT_IS_ONLY_WHITESPACE : TEXT_ISNOT_ONLY_WHITESPACE); return isEmpty; } #ifdef DEBUG NS_IMETHODIMP nsTextFrame::GetFrameName(nsAString& aResult) const { return MakeFrameName(NS_LITERAL_STRING("Text"), aResult); } NS_IMETHODIMP_(nsFrameState) nsTextFrame::GetDebugStateBits() const { // mask out our emptystate flags; those are just caches return nsFrame::GetDebugStateBits() & ~(TEXT_WHITESPACE_FLAGS | TEXT_REFLOW_FLAGS); } NS_IMETHODIMP nsTextFrame::List(FILE* out, PRInt32 aIndent) const { // Output the tag IndentBy(out, aIndent); ListTag(out); #ifdef DEBUG_waterson fprintf(out, " [parent=%p]", mParent); #endif if (HasView()) { fprintf(out, " [view=%p]", NS_STATIC_CAST(void*, GetView())); } PRInt32 totalContentLength; nsAutoString tmp; ToCString(tmp, &totalContentLength); // Output the first/last content offset and prev/next in flow info PRBool isComplete = (mContentOffset + mContentLength) == totalContentLength; fprintf(out, "[%d,%d,%c] ", mContentOffset, mContentLength, isComplete ? 'T':'F'); if (nsnull != mNextSibling) { fprintf(out, " next=%p", NS_STATIC_CAST(void*, mNextSibling)); } nsIFrame* prevContinuation = GetPrevContinuation(); if (nsnull != prevContinuation) { fprintf(out, " prev-continuation=%p", NS_STATIC_CAST(void*, prevContinuation)); } if (nsnull != mNextContinuation) { fprintf(out, " next-continuation=%p", NS_STATIC_CAST(void*, mNextContinuation)); } // Output the rect and state fprintf(out, " {%d,%d,%d,%d}", mRect.x, mRect.y, mRect.width, mRect.height); if (0 != mState) { if (mState & NS_FRAME_SELECTED_CONTENT) { fprintf(out, " [state=%08x] SELECTED", mState); } else { fprintf(out, " [state=%08x]", mState); } } fprintf(out, " [content=%p]", NS_STATIC_CAST(void*, mContent)); fprintf(out, " sc=%p", NS_STATIC_CAST(void*, mStyleContext)); nsIAtom* pseudoTag = mStyleContext->GetPseudoType(); if (pseudoTag) { nsAutoString atomString; pseudoTag->ToString(atomString); fprintf(out, " pst=%s", NS_LossyConvertUTF16toASCII(atomString).get()); } fputs("<\n", out); // Output the text aIndent++; IndentBy(out, aIndent); fputs("\"", out); fputs(NS_LossyConvertUTF16toASCII(tmp).get(), out); fputs("\"\n", out); aIndent--; IndentBy(out, aIndent); fputs(">\n", out); return NS_OK; } #endif void nsTextFrame::AdjustSelectionPointsForBidi(SelectionDetails *sdptr, PRInt32 textLength, PRBool isRTLChars, PRBool isOddLevel, PRBool isBidiSystem) { /* This adjustment is required whenever the text has been reversed by * Mozilla before rendering. * * In theory this means any text whose Bidi embedding level has been * set by the Unicode Bidi algorithm to an odd value, but this is * only true in practice on a non-Bidi platform. * * On a Bidi platform the situation is more complicated because the * platform will automatically reverse right-to-left characters; so * Mozilla reverses text whose natural directionality is the opposite * of its embedding level: right-to-left characters whose Bidi * embedding level is even (e.g. Visual Hebrew) or left-to-right and * neutral characters whose Bidi embedding level is odd (e.g. English * text with ). * * The following condition is accordingly an optimization of * if ( (!isBidiSystem && isOddLevel) || * (isBidiSystem && * ((isRTLChars && !isOddLevel) || * (!isRTLChars && isOddLevel)))) */ if (isOddLevel ^ (isRTLChars && isBidiSystem)) { PRInt32 swap = sdptr->mStart; sdptr->mStart = textLength - sdptr->mEnd; sdptr->mEnd = textLength - swap; // temp fix for 75026 crasher until we fix the bidi code // the above bidi code cause mStart < 0 in some case // the problem is we have whitespace compression code in // nsTextTransformer which cause mEnd > textLength NS_ASSERTION((sdptr->mStart >= 0) , "mStart >= 0"); if(sdptr->mStart < 0 ) sdptr->mStart = 0; NS_ASSERTION((sdptr->mEnd >= 0) , "mEnd >= 0"); if(sdptr->mEnd < 0 ) sdptr->mEnd = 0; NS_ASSERTION((sdptr->mStart <= sdptr->mEnd), "mStart <= mEnd"); if(sdptr->mStart > sdptr->mEnd) sdptr->mEnd = sdptr->mStart; } return; } void nsTextFrame::AdjustOffsetsForBidi(PRInt32 aStart, PRInt32 aEnd) { AddStateBits(NS_FRAME_IS_BIDI); SetOffsets(aStart, aEnd); } void nsTextFrame::SetOffsets(PRInt32 aStart, PRInt32 aEnd) { mContentOffset = aStart; mContentLength = aEnd - aStart; } /** * @return PR_TRUE if this text frame ends with a newline character. It should return * PR_FALSE if it is not a text frame. */ PRBool nsTextFrame::HasTerminalNewline() const { const nsTextFragment* frag = mContent->GetText(); if (frag && mContentLength > 0) { PRUnichar ch = frag->CharAt(mContentOffset + mContentLength - 1); if (ch == '\n') return PR_TRUE; } return PR_FALSE; }