зеркало из https://github.com/mozilla/gecko-dev.git
4781 строка
154 KiB
C++
4781 строка
154 KiB
C++
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
|
|
*
|
|
* The contents of this file are subject to the Netscape Public
|
|
* License Version 1.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/NPL/
|
|
*
|
|
* 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 Netscape are
|
|
* Copyright (C) 1998 Netscape Communications Corporation. All
|
|
* Rights Reserved.
|
|
*
|
|
* Contributor(s):
|
|
* Pierre Phaneuf <pp@ludusdesign.com>
|
|
* Robert O'Callahan <roc+moz@cs.cmu.edu>
|
|
*/
|
|
#include "nsCOMPtr.h"
|
|
#include "nsHTMLParts.h"
|
|
#include "nsCRT.h"
|
|
#include "nsSplittableFrame.h"
|
|
#include "nsLineLayout.h"
|
|
#include "nsString.h"
|
|
#include "nsIPresContext.h"
|
|
#include "nsIContent.h"
|
|
#include "nsStyleConsts.h"
|
|
#include "nsIStyleContext.h"
|
|
#include "nsCoord.h"
|
|
#include "nsIFontMetrics.h"
|
|
#include "nsIRenderingContext.h"
|
|
#include "nsHTMLIIDs.h"
|
|
#include "nsIPresShell.h"
|
|
#include "nsIView.h"
|
|
#include "nsIViewManager.h"
|
|
#include "nsITimerCallback.h"
|
|
#include "nsITimer.h"
|
|
#include "prtime.h"
|
|
#include "nsVoidArray.h"
|
|
#include "prprf.h"
|
|
#include "nsIDOMText.h"
|
|
#include "nsIDocument.h"
|
|
#include "nsIDeviceContext.h"
|
|
#include "nsIFocusTracker.h"
|
|
#include "nsICaret.h"
|
|
#include "nsHTMLAtoms.h"
|
|
#include "nsILineBreaker.h"
|
|
#include "nsIWordBreaker.h"
|
|
|
|
#include "nsITextContent.h"
|
|
#include "nsTextFragment.h"
|
|
#include "nsTextTransformer.h"
|
|
#include "nsLayoutAtoms.h"
|
|
#include "nsIFrameSelection.h"
|
|
#include "nsISelection.h"
|
|
#include "nsIDOMRange.h"
|
|
#include "nsILookAndFeel.h"
|
|
|
|
#include "nsILineIterator.h"
|
|
|
|
#include "nsIPref.h"
|
|
#include "nsIServiceManager.h"
|
|
|
|
#ifndef PR_ABS
|
|
#define PR_ABS(x) ((x) < 0 ? -(x) : (x))
|
|
#endif
|
|
|
|
static NS_DEFINE_IID(kIDOMTextIID, NS_IDOMTEXT_IID);
|
|
static NS_DEFINE_CID(kPrefCID, NS_PREF_CID);
|
|
|
|
#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 TEXT_BUF_SIZE 100
|
|
|
|
//----------------------------------------
|
|
|
|
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)
|
|
{
|
|
nsCRT::memset(mAutoBuffer, 0, sizeof(mAutoBuffer));
|
|
}
|
|
|
|
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;
|
|
}
|
|
nsCRT::memset(newBuffer, 0, sizeof(PRInt32) * newSize);
|
|
nsCRT::memcpy(newBuffer, mBuffer, sizeof(PRInt32) * 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(nsIPresContext* aPresContext, nsIFrame* aFrame);
|
|
|
|
PRBool RemoveFrame(nsIFrame* aFrame);
|
|
|
|
PRInt32 FrameCount();
|
|
|
|
void Start();
|
|
|
|
void Stop();
|
|
|
|
NS_IMETHOD_(void) Notify(nsITimer *timer);
|
|
|
|
static nsresult AddBlinkFrame(nsIPresContext* aPresContext, nsIFrame* aFrame);
|
|
static nsresult RemoveBlinkFrame(nsIFrame* aFrame);
|
|
|
|
static PRBool GetBlinkIsOff() { return sBlinkTextOff; }
|
|
|
|
protected:
|
|
|
|
struct FrameData {
|
|
nsIPresContext* mPresContext; // pres context associated with the frame
|
|
nsIFrame* mFrame;
|
|
|
|
|
|
FrameData(nsIPresContext* aPresContext,
|
|
nsIFrame* aFrame)
|
|
: mPresContext(aPresContext), mFrame(aFrame) {}
|
|
};
|
|
|
|
nsCOMPtr<nsITimer> mTimer;
|
|
nsVoidArray mFrames;
|
|
nsIPresContext* mPresContext;
|
|
|
|
protected:
|
|
|
|
static nsBlinkTimer* sTextBlinker;
|
|
static PRBool sBlinkTextOff;
|
|
|
|
};
|
|
|
|
nsBlinkTimer* nsBlinkTimer::sTextBlinker = nsnull;
|
|
PRBool nsBlinkTimer::sBlinkTextOff = PR_FALSE;
|
|
|
|
#ifdef NOISY_BLINK
|
|
static PRTime gLastTick;
|
|
#endif
|
|
|
|
nsBlinkTimer::nsBlinkTimer()
|
|
{
|
|
NS_INIT_REFCNT();
|
|
}
|
|
|
|
nsBlinkTimer::~nsBlinkTimer()
|
|
{
|
|
Stop();
|
|
sTextBlinker = nsnull;
|
|
}
|
|
|
|
void nsBlinkTimer::Start()
|
|
{
|
|
nsresult rv;
|
|
mTimer = do_CreateInstance("@mozilla.org/timer;1", &rv);
|
|
if (NS_OK == rv) {
|
|
mTimer->Init(this, 750, NS_PRIORITY_NORMAL, NS_TYPE_REPEATING_PRECISE);
|
|
}
|
|
}
|
|
|
|
void nsBlinkTimer::Stop()
|
|
{
|
|
if (nsnull != mTimer) {
|
|
mTimer->Cancel();
|
|
}
|
|
}
|
|
|
|
static NS_DEFINE_IID(kITimerCallbackIID, NS_ITIMERCALLBACK_IID);
|
|
NS_IMPL_ISUPPORTS(nsBlinkTimer, kITimerCallbackIID);
|
|
|
|
void nsBlinkTimer::AddFrame(nsIPresContext* aPresContext, nsIFrame* aFrame) {
|
|
FrameData* frameData = new FrameData(aPresContext, aFrame);
|
|
mFrames.AppendElement(frameData);
|
|
if (1 == mFrames.Count()) {
|
|
Start();
|
|
}
|
|
}
|
|
|
|
PRBool nsBlinkTimer::RemoveFrame(nsIFrame* aFrame) {
|
|
PRInt32 i, n = mFrames.Count();
|
|
PRBool rv = PR_FALSE;
|
|
for (i = 0; i < n; i++) {
|
|
FrameData* frameData = (FrameData*) mFrames.ElementAt(i);
|
|
|
|
if (frameData->mFrame == aFrame) {
|
|
rv = mFrames.RemoveElementAt(i);
|
|
delete frameData;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (0 == mFrames.Count()) {
|
|
Stop();
|
|
}
|
|
return rv;
|
|
}
|
|
|
|
PRInt32 nsBlinkTimer::FrameCount() {
|
|
return mFrames.Count();
|
|
}
|
|
|
|
NS_IMETHODIMP_(void) nsBlinkTimer::Notify(nsITimer *timer)
|
|
{
|
|
// Toggle blink state bit so that text code knows whether or not to
|
|
// render. All text code shares the same flag so that they all blink
|
|
// in unison.
|
|
sBlinkTextOff = PRBool(!sBlinkTextOff);
|
|
|
|
#ifndef REPEATING_TIMERS
|
|
// XXX hack to get auto-repeating timers; restart before doing
|
|
// expensive work so that time between ticks is more even
|
|
Stop();
|
|
Start();
|
|
#endif
|
|
|
|
#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++) {
|
|
FrameData* frameData = (FrameData*) mFrames.ElementAt(i);
|
|
|
|
// Determine damaged area and tell view manager to redraw it
|
|
nsPoint offset;
|
|
nsRect bounds;
|
|
frameData->mFrame->GetRect(bounds);
|
|
nsIView* view;
|
|
frameData->mFrame->GetOffsetFromView(frameData->mPresContext, offset, &view);
|
|
nsIViewManager* vm;
|
|
view->GetViewManager(vm);
|
|
bounds.x = offset.x;
|
|
bounds.y = offset.y;
|
|
vm->UpdateView(view, bounds, 0);
|
|
NS_RELEASE(vm);
|
|
}
|
|
}
|
|
|
|
|
|
// static
|
|
nsresult nsBlinkTimer::AddBlinkFrame(nsIPresContext* aPresContext, nsIFrame* aFrame)
|
|
{
|
|
if (!sTextBlinker)
|
|
{
|
|
sTextBlinker = new nsBlinkTimer;
|
|
if (!sTextBlinker) return NS_ERROR_OUT_OF_MEMORY;
|
|
}
|
|
|
|
NS_ADDREF(sTextBlinker);
|
|
|
|
sTextBlinker->AddFrame(aPresContext, 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;
|
|
}
|
|
|
|
|
|
//----------------------------------------------------------------------
|
|
|
|
class nsTextFrame : public nsFrame {
|
|
public:
|
|
nsTextFrame();
|
|
|
|
// nsIFrame
|
|
NS_IMETHOD Paint(nsIPresContext* aPresContext,
|
|
nsIRenderingContext& aRenderingContext,
|
|
const nsRect& aDirtyRect,
|
|
nsFramePaintLayer aWhichLayer);
|
|
|
|
NS_IMETHOD GetCursor(nsIPresContext* aPresContext,
|
|
nsPoint& aPoint,
|
|
PRInt32& aCursor);
|
|
|
|
NS_IMETHOD ContentChanged(nsIPresContext* aPresContext,
|
|
nsIContent* aChild,
|
|
nsISupports* aSubContent);
|
|
|
|
NS_IMETHOD GetNextInFlow(nsIFrame** aNextInFlow) const {
|
|
*aNextInFlow = mNextInFlow;
|
|
return NS_OK;
|
|
}
|
|
NS_IMETHOD SetNextInFlow(nsIFrame* aNextInFlow) {
|
|
mNextInFlow = aNextInFlow;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHOD IsSplittable(nsSplittableType& aIsSplittable) const {
|
|
aIsSplittable = NS_FRAME_SPLITTABLE;
|
|
return NS_OK;
|
|
}
|
|
|
|
/**
|
|
* Get the "type" of the frame
|
|
*
|
|
* @see nsLayoutAtoms::textFrame
|
|
*/
|
|
NS_IMETHOD GetFrameType(nsIAtom** aType) const;
|
|
|
|
#ifdef DEBUG
|
|
NS_IMETHOD List(nsIPresContext* aPresContext, FILE* out, PRInt32 aIndent) const;
|
|
NS_IMETHOD SizeOf(nsISizeOfHandler* aHandler, PRUint32* aResult) const;
|
|
NS_IMETHOD GetFrameName(nsString& aResult) const;
|
|
#endif
|
|
|
|
NS_IMETHOD GetPosition(nsIPresContext* aCX,
|
|
const nsPoint& aPoint,
|
|
nsIContent ** aNewContent,
|
|
PRInt32& aContentOffset,
|
|
PRInt32& aContentOffsetEnd);
|
|
|
|
NS_IMETHOD GetContentAndOffsetsFromPoint(nsIPresContext* aCX,
|
|
const nsPoint& aPoint,
|
|
nsIContent ** aNewContent,
|
|
PRInt32& aContentOffset,
|
|
PRInt32& aContentOffsetEnd,
|
|
PRBool& aBeginFrameContent);
|
|
|
|
NS_IMETHOD GetPositionSlowly(nsIPresContext* aCX,
|
|
nsIRenderingContext * aRendContext,
|
|
const nsPoint& aPoint,
|
|
nsIContent ** aNewContent,
|
|
PRInt32& aOffset);
|
|
|
|
|
|
NS_IMETHOD SetSelected(nsIPresContext* aPresContext,
|
|
nsIDOMRange *aRange,
|
|
PRBool aSelected,
|
|
nsSpread aSpread);
|
|
|
|
NS_IMETHOD PeekOffset(nsIPresContext* aPresContext, nsPeekOffsetStruct *aPos);
|
|
NS_IMETHOD CheckVisibility(nsIPresContext* aContext, PRInt32 aStartIndex, PRInt32 aEndIndex, PRBool aRecurse, PRBool *aFinished, PRBool *_retval);
|
|
|
|
NS_IMETHOD HandleMultiplePress(nsIPresContext* aPresContext,
|
|
nsGUIEvent * aEvent,
|
|
nsEventStatus* aEventStatus);
|
|
|
|
NS_IMETHOD GetOffsets(PRInt32 &start, PRInt32 &end)const;
|
|
|
|
NS_IMETHOD GetPointFromOffset(nsIPresContext* inPresContext,
|
|
nsIRenderingContext* inRendContext,
|
|
PRInt32 inOffset,
|
|
nsPoint* outPoint);
|
|
|
|
NS_IMETHOD GetChildFrameContainingOffset(PRInt32 inContentOffset,
|
|
PRBool inHint,
|
|
PRInt32* outFrameContentOffset,
|
|
nsIFrame* *outChildFrame);
|
|
|
|
// nsIHTMLReflow
|
|
NS_IMETHOD Reflow(nsIPresContext* aPresContext,
|
|
nsHTMLReflowMetrics& aMetrics,
|
|
const nsHTMLReflowState& aReflowState,
|
|
nsReflowStatus& aStatus);
|
|
NS_IMETHOD CanContinueTextRun(PRBool& aContinueTextRun) const;
|
|
NS_IMETHOD AdjustFrameSize(nscoord aExtraSpace, nscoord& aUsedSpace);
|
|
NS_IMETHOD TrimTrailingWhiteSpace(nsIPresContext* aPresContext,
|
|
nsIRenderingContext& aRC,
|
|
nscoord& aDeltaWidth);
|
|
|
|
struct TextStyle {
|
|
const nsStyleFont* mFont;
|
|
const nsStyleText* mText;
|
|
const nsStyleColor* mColor;
|
|
const nsStyleDisplay* mDisplay;
|
|
nsIFontMetrics* mNormalFont;
|
|
nsIFontMetrics* mSmallFont;
|
|
nsIFontMetrics* mLastFont;
|
|
PRBool mSmallCaps;
|
|
nscoord mWordSpacing;
|
|
nscoord mLetterSpacing;
|
|
nscolor mSelectionTextColor;
|
|
nscolor mSelectionBGColor;
|
|
nscoord mSpaceWidth;
|
|
nscoord mAveCharWidth;
|
|
PRBool mJustifying;
|
|
PRBool mPreformatted;
|
|
PRInt32 mNumSpacesToRender;
|
|
PRInt32 mNumSpacesToMeasure;
|
|
nscoord mExtraSpacePerSpace;
|
|
PRInt32 mNumSpacesReceivingExtraJot;
|
|
|
|
TextStyle(nsIPresContext* aPresContext,
|
|
nsIRenderingContext& aRenderingContext,
|
|
nsIStyleContext* sc)
|
|
{
|
|
mFont = nsnull;
|
|
mText = nsnull;
|
|
mColor = nsnull;
|
|
mNormalFont = nsnull;
|
|
mSmallFont = nsnull;
|
|
mLastFont = nsnull;
|
|
|
|
// Get style data
|
|
mColor = (const nsStyleColor*) sc->GetStyleData(eStyleStruct_Color);
|
|
mFont = (const nsStyleFont*) sc->GetStyleData(eStyleStruct_Font);
|
|
mText = (const nsStyleText*) sc->GetStyleData(eStyleStruct_Text);
|
|
mDisplay = (const nsStyleDisplay*) sc->GetStyleData(eStyleStruct_Display);
|
|
|
|
// Cache the original decorations and reuse the current font
|
|
// to query metrics, rather than creating a new font which is expensive.
|
|
nsFont* plainFont = (nsFont *)&mFont->mFont; //XXX: Change to use a CONST_CAST macro.
|
|
PRUint8 originalDecorations = plainFont->decorations;
|
|
plainFont->decorations = NS_FONT_DECORATION_NONE;
|
|
nsCOMPtr<nsIDeviceContext> deviceContext;
|
|
aRenderingContext.GetDeviceContext(*getter_AddRefs(deviceContext));
|
|
nsCOMPtr<nsIAtom> langGroup;
|
|
if (mDisplay->mLanguage) {
|
|
mDisplay->mLanguage->GetLanguageGroup(getter_AddRefs(langGroup));
|
|
}
|
|
deviceContext->GetMetricsFor(*plainFont, langGroup, mNormalFont);
|
|
aRenderingContext.SetFont(mNormalFont);
|
|
aRenderingContext.GetWidth(' ', mSpaceWidth);
|
|
mAveCharWidth = 0;
|
|
#ifdef _WIN32
|
|
mNormalFont->GetAveCharWidth(mAveCharWidth);
|
|
#endif
|
|
if (0 == mAveCharWidth) {
|
|
// provide a default if it could not be resolved
|
|
mAveCharWidth = 10;
|
|
}
|
|
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);
|
|
aPresContext->GetMetricsFor(*plainFont, &mSmallFont);
|
|
// Reset to the size value saved earlier.
|
|
plainFont->size = originalSize;
|
|
}
|
|
else {
|
|
mSmallFont = nsnull;
|
|
}
|
|
|
|
// Reset to the decoration saved earlier
|
|
plainFont->decorations = originalDecorations;
|
|
|
|
// Get colors from look&feel
|
|
mSelectionBGColor = NS_RGB(0, 0, 0);
|
|
mSelectionTextColor = NS_RGB(255, 255, 255);
|
|
nsILookAndFeel* look = nsnull;
|
|
if (NS_SUCCEEDED(aPresContext->GetLookAndFeel(&look)) && look) {
|
|
look->GetColor(nsILookAndFeel::eColor_TextSelectBackground, mSelectionBGColor);
|
|
look->GetColor(nsILookAndFeel::eColor_TextSelectForeground, mSelectionTextColor);
|
|
NS_RELEASE(look);
|
|
}
|
|
|
|
// Get the word and letter spacing
|
|
mWordSpacing = 0;
|
|
PRIntn unit = mText->mWordSpacing.GetUnit();
|
|
if (eStyleUnit_Coord == unit) {
|
|
mWordSpacing = mText->mWordSpacing.GetCoordValue();
|
|
}
|
|
|
|
mLetterSpacing = 0;
|
|
unit = mText->mLetterSpacing.GetUnit();
|
|
if (eStyleUnit_Coord == unit) {
|
|
mLetterSpacing = mText->mLetterSpacing.GetCoordValue();
|
|
}
|
|
mWordSpacing += mLetterSpacing; // bug 1046
|
|
|
|
mNumSpacesToRender = 0;
|
|
mNumSpacesToMeasure = 0;
|
|
mNumSpacesReceivingExtraJot = 0;
|
|
mExtraSpacePerSpace = 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;
|
|
}
|
|
|
|
~TextStyle() {
|
|
NS_RELEASE(mNormalFont);
|
|
NS_IF_RELEASE(mSmallFont);
|
|
|
|
mFont = nsnull;
|
|
mText = nsnull;
|
|
mColor = nsnull;
|
|
mNormalFont = nsnull;
|
|
mSmallFont = nsnull;
|
|
mLastFont = nsnull;
|
|
|
|
}
|
|
};
|
|
|
|
struct TextReflowData {
|
|
PRInt32 mX; // OUT
|
|
PRInt32 mOffset; // IN/OUT How far along we are in the content
|
|
nscoord mMaxWordWidth; // OUT
|
|
PRPackedBool mWrapping; // IN
|
|
PRPackedBool mSkipWhitespace; // IN
|
|
PRPackedBool mMeasureText; // IN
|
|
PRPackedBool mInWord; // IN
|
|
PRPackedBool mFirstLetterOK; // IN
|
|
PRPackedBool mIsBreakable; // IN
|
|
PRPackedBool mComputeMaxWordWidth; // IN
|
|
|
|
TextReflowData(PRInt32 aStartingOffset,
|
|
PRBool aWrapping,
|
|
PRBool aSkipWhitespace,
|
|
PRBool aMeasureText,
|
|
PRBool aInWord,
|
|
PRBool aFirstLetterOK,
|
|
PRBool aIsBreakable,
|
|
PRBool aComputeMaxWordWidth)
|
|
: mX(0),
|
|
mOffset(aStartingOffset),
|
|
mMaxWordWidth(0),
|
|
mWrapping(aWrapping),
|
|
mSkipWhitespace(aSkipWhitespace),
|
|
mMeasureText(aMeasureText),
|
|
mInWord(aInWord),
|
|
mFirstLetterOK(aFirstLetterOK),
|
|
mIsBreakable(aIsBreakable),
|
|
mComputeMaxWordWidth(aComputeMaxWordWidth)
|
|
{}
|
|
};
|
|
|
|
nsIDocument* GetDocument(nsIPresContext* aPresContext);
|
|
|
|
PRIntn PrepareUnicodeText(nsTextTransformer& aTransformer,
|
|
nsAutoIndexBuffer* aIndexBuffer,
|
|
nsAutoTextBuffer* aTextBuffer,
|
|
PRInt32* aTextLen);
|
|
void ComputeExtraJustificationSpacing(nsIRenderingContext& aRenderingContext,
|
|
TextStyle& aTextStyle,
|
|
PRUnichar* aBuffer, PRInt32 aLength, PRInt32 aNumSpaces);
|
|
|
|
void PaintTextDecorations(nsIRenderingContext& aRenderingContext,
|
|
nsIStyleContext* aStyleContext,
|
|
TextStyle& aStyle,
|
|
nscoord aX, nscoord aY, nscoord aWidth,
|
|
PRUnichar* aText = nsnull,
|
|
SelectionDetails *aDetails = nsnull,
|
|
PRUint32 aIndex = 0,
|
|
PRUint32 aLength = 0,
|
|
const nscoord* aSpacing = nsnull);
|
|
|
|
void PaintTextSlowly(nsIPresContext* aPresContext,
|
|
nsIRenderingContext& aRenderingContext,
|
|
nsIStyleContext* aStyleContext,
|
|
TextStyle& aStyle,
|
|
nscoord aX, nscoord aY);
|
|
|
|
void RenderString(nsIRenderingContext& aRenderingContext,
|
|
nsIStyleContext* aStyleContext,
|
|
TextStyle& aStyle,
|
|
PRUnichar* aBuffer, PRInt32 aLength,
|
|
nscoord aX, nscoord aY,
|
|
nscoord aWidth,
|
|
SelectionDetails *aDetails = nsnull);
|
|
|
|
void MeasureSmallCapsText(const nsHTMLReflowState& aReflowState,
|
|
TextStyle& aStyle,
|
|
PRUnichar* aWord,
|
|
PRInt32 aWordLength,
|
|
nscoord* aWidthResult);
|
|
|
|
nsReflowStatus MeasureText(nsIPresContext* aPresContext,
|
|
const nsHTMLReflowState& aReflowState,
|
|
nsTextTransformer& aTx,
|
|
nsILineBreaker* aLb,
|
|
TextStyle& aTs,
|
|
TextReflowData& aTextData);
|
|
|
|
void GetWidth(nsIRenderingContext& aRenderingContext,
|
|
TextStyle& aStyle,
|
|
PRUnichar* aBuffer, PRInt32 aLength,
|
|
nscoord* aWidthResult);
|
|
|
|
//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,
|
|
TextStyle& aStyle,
|
|
PRUnichar* aBuffer, PRInt32 aLength,
|
|
nscoord aWidth);
|
|
|
|
void PaintUnicodeText(nsIPresContext* aPresContext,
|
|
nsIRenderingContext& aRenderingContext,
|
|
nsIStyleContext* aStyleContext,
|
|
TextStyle& aStyle,
|
|
nscoord dx, nscoord dy);
|
|
|
|
void PaintAsciiText(nsIPresContext* aPresContext,
|
|
nsIRenderingContext& aRenderingContext,
|
|
nsIStyleContext* aStyleContext,
|
|
TextStyle& aStyle,
|
|
nscoord dx, nscoord dy);
|
|
|
|
nscoord ComputeTotalWordWidth(nsIPresContext* aPresContext,
|
|
nsILineBreaker* aLineBreaker,
|
|
nsLineLayout& aLineLayout,
|
|
const nsHTMLReflowState& aReflowState,
|
|
nsIFrame* aNextFrame,
|
|
nscoord aBaseWidth,
|
|
PRUnichar* aWordBuf,
|
|
PRUint32 aWordBufLen,
|
|
PRUint32 aWordBufSize);
|
|
|
|
nscoord ComputeWordFragmentWidth(nsIPresContext* aPresContext,
|
|
nsILineBreaker* aLineBreaker,
|
|
nsLineLayout& aLineLayout,
|
|
const nsHTMLReflowState& aReflowState,
|
|
nsIFrame* aNextFrame,
|
|
nsIContent* aContent,
|
|
nsITextContent* aText,
|
|
PRBool* aStop,
|
|
const PRUnichar* aWordBuf,
|
|
PRUint32 &aWordBufLen,
|
|
PRUint32 aWordBufSize);
|
|
|
|
void ToCString(nsString& aBuf, PRInt32* aTotalContentLength) const;
|
|
|
|
protected:
|
|
virtual ~nsTextFrame();
|
|
|
|
nsIFrame* mNextInFlow;
|
|
PRInt32 mContentOffset;
|
|
PRInt32 mContentLength;
|
|
PRInt32 mColumn;
|
|
//factored out method for getwidth and getlengthslowly. if aGetWidth is non-zero number then measure to that width and return the length. else shove total width into result
|
|
PRInt32 GetWidthOrLength(nsIRenderingContext& aRenderingContext,
|
|
TextStyle& aStyle,
|
|
PRUnichar* aBuffer, PRInt32 aLength,
|
|
nscoord* aWidthResult,
|
|
PRBool aGetWidth/* true=get width false = return length up to aWidthResult size*/);
|
|
nsresult GetContentAndOffsetsForSelection(nsIPresContext* aPresContext,nsIContent **aContent, PRInt32 *aOffset, PRInt32 *aLength);
|
|
|
|
};
|
|
|
|
class nsContinuingTextFrame : public nsTextFrame {
|
|
public:
|
|
NS_IMETHOD Init(nsIPresContext* aPresContext,
|
|
nsIContent* aContent,
|
|
nsIFrame* aParent,
|
|
nsIStyleContext* aContext,
|
|
nsIFrame* aPrevInFlow);
|
|
|
|
NS_IMETHOD GetPrevInFlow(nsIFrame** aPrevInFlow) const {
|
|
*aPrevInFlow = mPrevInFlow;
|
|
return NS_OK;
|
|
}
|
|
NS_IMETHOD SetPrevInFlow(nsIFrame* aPrevInFlow) {
|
|
mPrevInFlow = aPrevInFlow;
|
|
return NS_OK;
|
|
}
|
|
|
|
#ifdef DEBUG
|
|
NS_IMETHOD SizeOf(nsISizeOfHandler* aHandler, PRUint32* aResult) const;
|
|
#endif
|
|
|
|
protected:
|
|
nsIFrame* mPrevInFlow;
|
|
};
|
|
|
|
NS_IMETHODIMP
|
|
nsContinuingTextFrame::Init(nsIPresContext* aPresContext,
|
|
nsIContent* aContent,
|
|
nsIFrame* aParent,
|
|
nsIStyleContext* aContext,
|
|
nsIFrame* aPrevInFlow)
|
|
{
|
|
nsresult rv;
|
|
|
|
rv = nsTextFrame::Init(aPresContext, aContent, aParent, aContext, aPrevInFlow);
|
|
|
|
if (aPrevInFlow) {
|
|
// Hook the frame into the flow
|
|
mPrevInFlow = aPrevInFlow;
|
|
aPrevInFlow->SetNextInFlow(this);
|
|
}
|
|
|
|
return rv;
|
|
}
|
|
|
|
#ifdef DEBUG
|
|
NS_IMETHODIMP
|
|
nsContinuingTextFrame::SizeOf(nsISizeOfHandler* aHandler, PRUint32* aResult) const
|
|
{
|
|
if (!aResult) {
|
|
return NS_ERROR_NULL_POINTER;
|
|
}
|
|
*aResult = sizeof(*this);
|
|
return NS_OK;
|
|
}
|
|
#endif
|
|
|
|
|
|
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;
|
|
}
|
|
|
|
|
|
//DRAW SELECTION ITERATOR USED FOR TEXTFRAMES ONLY
|
|
//helper class for drawing multiply selected text
|
|
class DrawSelectionIterator
|
|
{
|
|
enum {DISABLED_COLOR = NS_RGB(176,176,176)};
|
|
enum {SELECTION_TYPES_WE_CARE_ABOUT=nsISelectionController::SELECTION_NONE+nsISelectionController::SELECTION_NORMAL};
|
|
public:
|
|
DrawSelectionIterator(const SelectionDetails *aSelDetails, PRUnichar *aText,
|
|
PRUint32 aTextLength, nsTextFrame::TextStyle &aTextStyle,
|
|
PRInt16 aSelectionStatus);
|
|
~DrawSelectionIterator();
|
|
PRBool First();
|
|
PRBool Next();
|
|
PRBool IsDone();
|
|
|
|
PRUnichar * CurrentTextUnicharPtr();
|
|
char * CurrentTextCStrPtr();
|
|
PRUint32 CurrentLength();
|
|
nsTextFrame::TextStyle & CurrentStyle();
|
|
nscolor CurrentForeGroundColor();
|
|
PRBool CurrentBackGroundColor(nscolor &aColor);
|
|
private:
|
|
union {
|
|
PRUnichar *mUniStr;
|
|
char *mCStr;
|
|
};
|
|
PRUint32 mLength;
|
|
PRUint32 mCurrentIdx;
|
|
PRUint32 mCurrentLength;
|
|
nsTextFrame::TextStyle &mOldStyle;//base new styles on this one???
|
|
const SelectionDetails *mDetails;
|
|
PRBool mDone;
|
|
PRUint8 * mTypes;
|
|
PRBool mInit;
|
|
PRInt16 mSelectionStatus;//see nsIDocument.h SetDisplaySelection()
|
|
nscolor mDisabledColor;
|
|
//private methods
|
|
void FillCurrentData();
|
|
};
|
|
|
|
DrawSelectionIterator::DrawSelectionIterator(const SelectionDetails *aSelDetails, PRUnichar *aText,
|
|
PRUint32 aTextLength, nsTextFrame::TextStyle &aTextStyle, PRInt16 aSelectionStatus)
|
|
:mOldStyle(aTextStyle)
|
|
{
|
|
mDetails = aSelDetails;
|
|
mCurrentIdx = 0;
|
|
mUniStr = aText;
|
|
mLength = aTextLength;
|
|
mTypes = nsnull;
|
|
mInit = PR_FALSE;
|
|
mSelectionStatus = aSelectionStatus;
|
|
mDisabledColor = EnsureDifferentColors(DISABLED_COLOR, mOldStyle.mSelectionBGColor);
|
|
|
|
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)
|
|
{
|
|
mTypes = new PRUint8[mLength];
|
|
if (!mTypes)
|
|
return;
|
|
memset(mTypes,0,mLength);//initialize to 0
|
|
while (details)
|
|
{
|
|
if ((details->mType & SELECTION_TYPES_WE_CARE_ABOUT ) &&
|
|
(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;//eh
|
|
}
|
|
mTypes[i]|=details->mType;//add this bit
|
|
}
|
|
}
|
|
details= details->mNext;
|
|
}
|
|
if (!mInit && mTypes) //we have details but none that we care about.
|
|
{
|
|
delete mTypes;
|
|
mTypes = nsnull;
|
|
mDone = PR_TRUE;//we are finished
|
|
}
|
|
}
|
|
else if (details->mStart == details->mEnd)//no collapsed selections here!
|
|
{
|
|
mDone = PR_TRUE;
|
|
return;
|
|
}
|
|
else if (!(details->mType & SELECTION_TYPES_WE_CARE_ABOUT ))//if all we have is selection we DONT care about, do nothing
|
|
{
|
|
mDone = PR_TRUE;
|
|
return;
|
|
}
|
|
mInit = PR_TRUE;
|
|
}
|
|
|
|
DrawSelectionIterator::~DrawSelectionIterator()
|
|
{
|
|
if (mTypes)
|
|
delete mTypes;
|
|
}
|
|
|
|
void
|
|
DrawSelectionIterator::FillCurrentData()
|
|
{
|
|
if (mDone)
|
|
return;
|
|
if (!mTypes)
|
|
{
|
|
mCurrentIdx+=mCurrentLength;
|
|
if (mCurrentIdx >= mLength)
|
|
{
|
|
mDone = PR_TRUE;
|
|
return;
|
|
}
|
|
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
|
|
{
|
|
mCurrentIdx+=mCurrentLength;//advance to this chunk
|
|
if (mCurrentIdx >= mLength)
|
|
{
|
|
mDone = PR_TRUE;
|
|
return;
|
|
}
|
|
uint8 typevalue = mTypes[mCurrentIdx];
|
|
while (typevalue == mTypes[mCurrentIdx+mCurrentLength] && (mCurrentIdx+mCurrentLength) <mLength)
|
|
{
|
|
mCurrentLength++;
|
|
}
|
|
}
|
|
}
|
|
|
|
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::IsDone()
|
|
{
|
|
return mDone || !mInit;
|
|
}
|
|
|
|
|
|
PRUnichar *
|
|
DrawSelectionIterator::CurrentTextUnicharPtr()
|
|
{
|
|
return mUniStr+mCurrentIdx;
|
|
}
|
|
|
|
char *
|
|
DrawSelectionIterator::CurrentTextCStrPtr()
|
|
{
|
|
return mCStr+mCurrentIdx;
|
|
}
|
|
|
|
PRUint32
|
|
DrawSelectionIterator::CurrentLength()
|
|
{
|
|
return mCurrentLength;
|
|
}
|
|
|
|
nsTextFrame::TextStyle &
|
|
DrawSelectionIterator::CurrentStyle()
|
|
{
|
|
return mOldStyle;
|
|
}
|
|
|
|
nscolor
|
|
DrawSelectionIterator::CurrentForeGroundColor()
|
|
{
|
|
nscolor foreColor;
|
|
PRBool colorSet = PR_FALSE;
|
|
|
|
if (!mTypes)
|
|
{
|
|
if (mCurrentIdx == (PRUint32)mDetails->mStart)
|
|
{
|
|
foreColor = mOldStyle.mSelectionTextColor;
|
|
colorSet = PR_TRUE;
|
|
}
|
|
}
|
|
else if (mTypes[mCurrentIdx] | nsISelectionController::SELECTION_NORMAL)//Find color based on mTypes[mCurrentIdx];
|
|
{
|
|
foreColor = mOldStyle.mSelectionTextColor;
|
|
colorSet = PR_TRUE;
|
|
}
|
|
|
|
if (colorSet && (foreColor != NS_DONT_CHANGE_COLOR))
|
|
return foreColor;
|
|
|
|
return mOldStyle.mColor->mColor;
|
|
}
|
|
|
|
PRBool
|
|
DrawSelectionIterator::CurrentBackGroundColor(nscolor &aColor)
|
|
{
|
|
//Find color based on mTypes[mCurrentIdx];
|
|
if (!mTypes)
|
|
{
|
|
if (mCurrentIdx == (PRUint32)mDetails->mStart)
|
|
{
|
|
aColor = (mSelectionStatus==nsISelectionController::SELECTION_ON)?mOldStyle.mSelectionBGColor:mDisabledColor;
|
|
return PR_TRUE;
|
|
}
|
|
}
|
|
else if (mTypes[mCurrentIdx] | nsISelectionController::SELECTION_NORMAL)
|
|
{
|
|
aColor = (mSelectionStatus==nsISelectionController::SELECTION_ON)?mOldStyle.mSelectionBGColor:mDisabledColor;
|
|
return PR_TRUE;
|
|
}
|
|
return PR_FALSE;
|
|
}
|
|
|
|
|
|
//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
|
|
|
|
//----------------------------------------------------------------------
|
|
|
|
nsresult
|
|
NS_NewTextFrame(nsIPresShell* aPresShell, nsIFrame** aNewFrame)
|
|
{
|
|
NS_PRECONDITION(aNewFrame, "null OUT ptr");
|
|
if (nsnull == aNewFrame) {
|
|
return NS_ERROR_NULL_POINTER;
|
|
}
|
|
nsTextFrame* it = new (aPresShell) nsTextFrame;
|
|
if (nsnull == it) {
|
|
return NS_ERROR_OUT_OF_MEMORY;
|
|
}
|
|
*aNewFrame = it;
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
NS_NewContinuingTextFrame(nsIPresShell* aPresShell, nsIFrame** aNewFrame)
|
|
{
|
|
NS_PRECONDITION(aNewFrame, "null OUT ptr");
|
|
if (nsnull == aNewFrame) {
|
|
return NS_ERROR_NULL_POINTER;
|
|
}
|
|
nsContinuingTextFrame* it = new (aPresShell) nsContinuingTextFrame;
|
|
if (nsnull == it) {
|
|
return NS_ERROR_OUT_OF_MEMORY;
|
|
}
|
|
*aNewFrame = it;
|
|
return NS_OK;
|
|
}
|
|
|
|
nsTextFrame::nsTextFrame()
|
|
{
|
|
}
|
|
|
|
nsTextFrame::~nsTextFrame()
|
|
{
|
|
if (0 != (mState & TEXT_BLINK_ON))
|
|
{
|
|
nsBlinkTimer::RemoveBlinkFrame(this);
|
|
}
|
|
}
|
|
|
|
nsIDocument*
|
|
nsTextFrame::GetDocument(nsIPresContext* aPresContext)
|
|
{
|
|
nsIDocument* result = nsnull;
|
|
if (mContent) {
|
|
mContent->GetDocument(result);
|
|
}
|
|
if (!result && aPresContext) {
|
|
nsIPresShell* shell;
|
|
aPresContext->GetShell(&shell);
|
|
if (shell) {
|
|
shell->GetDocument(&result);
|
|
NS_RELEASE(shell);
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsTextFrame::GetCursor(nsIPresContext* aPresContext,
|
|
nsPoint& aPoint,
|
|
PRInt32& aCursor)
|
|
{
|
|
const nsStyleColor* styleColor;
|
|
GetStyleData(eStyleStruct_Color, (const nsStyleStruct*&)styleColor);
|
|
aCursor = styleColor->mCursor;
|
|
|
|
if (NS_STYLE_CURSOR_AUTO == aCursor && nsnull != mParent) {
|
|
mParent->GetCursor(aPresContext, aPoint, aCursor);
|
|
if (NS_STYLE_CURSOR_AUTO == aCursor) {
|
|
aCursor = NS_STYLE_CURSOR_TEXT;
|
|
}
|
|
}
|
|
return NS_OK;
|
|
}
|
|
|
|
static nsIFrame*
|
|
GetLastInFlow(nsIFrame* aFrame)
|
|
{
|
|
nsIFrame* lastInFlow;
|
|
nsIFrame* nextInFlow = aFrame;
|
|
while (nsnull!=nextInFlow) {
|
|
lastInFlow = nextInFlow;
|
|
lastInFlow->GetNextInFlow(&nextInFlow);
|
|
}
|
|
NS_POSTCONDITION(nsnull!=lastInFlow, "illegal state in flow chain.");
|
|
return lastInFlow;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsTextFrame::ContentChanged(nsIPresContext* aPresContext,
|
|
nsIContent* aChild,
|
|
nsISupports* aSubContent)
|
|
{
|
|
nsIFrame* targetTextFrame = this;
|
|
|
|
PRBool markAllDirty = PR_TRUE;
|
|
if (aSubContent) {
|
|
nsCOMPtr<nsITextContentChangeData> tccd = do_QueryInterface(aSubContent);
|
|
if (tccd) {
|
|
nsITextContentChangeData::ChangeType type;
|
|
tccd->GetChangeType(&type);
|
|
if (nsITextContentChangeData::Append == type) {
|
|
markAllDirty = PR_FALSE;
|
|
nsTextFrame* frame = (nsTextFrame*)::GetLastInFlow(this);
|
|
frame->mState |= NS_FRAME_IS_DIRTY;
|
|
targetTextFrame = frame;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (markAllDirty) {
|
|
// Mark this frame and all the next-in-flow frames as dirty
|
|
nsTextFrame* textFrame = this;
|
|
while (textFrame) {
|
|
textFrame->mState |= NS_FRAME_IS_DIRTY;
|
|
textFrame = (nsTextFrame*)textFrame->mNextInFlow;
|
|
}
|
|
}
|
|
|
|
// Ask the parent frame to reflow me.
|
|
nsresult rv;
|
|
nsCOMPtr<nsIPresShell> shell;
|
|
rv = aPresContext->GetShell(getter_AddRefs(shell));
|
|
if (NS_SUCCEEDED(rv) && shell && mParent) {
|
|
mParent->ReflowDirtyChild(shell, targetTextFrame);
|
|
}
|
|
|
|
|
|
return rv;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsTextFrame::Paint(nsIPresContext* aPresContext,
|
|
nsIRenderingContext& aRenderingContext,
|
|
const nsRect& aDirtyRect,
|
|
nsFramePaintLayer aWhichLayer)
|
|
{
|
|
if (NS_FRAME_PAINT_LAYER_FOREGROUND != aWhichLayer) {
|
|
return NS_OK;
|
|
}
|
|
if ((0 != (mState & TEXT_BLINK_ON)) && nsBlinkTimer::GetBlinkIsOff()) {
|
|
return NS_OK;
|
|
}
|
|
nsIStyleContext* sc = mStyleContext;
|
|
const nsStyleDisplay* disp = (const nsStyleDisplay*)
|
|
sc->GetStyleData(eStyleStruct_Display);
|
|
if (disp->IsVisible()) {
|
|
TextStyle ts(aPresContext, aRenderingContext, mStyleContext);
|
|
if (ts.mSmallCaps || (0 != ts.mWordSpacing) || (0 != ts.mLetterSpacing)
|
|
|| ts.mJustifying) {
|
|
PaintTextSlowly(aPresContext, aRenderingContext, sc, ts, 0, 0);
|
|
}
|
|
else {
|
|
// Get the text fragment
|
|
nsCOMPtr<nsITextContent> tc = do_QueryInterface(mContent);
|
|
const nsTextFragment* frag = nsnull;
|
|
if (tc.get()) {
|
|
tc->GetText(&frag);
|
|
}
|
|
if (!frag) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
// 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);
|
|
|
|
// If we have ascii text that doesn't contain multi-byte characters
|
|
// and the text doesn't need transforming then always render as ascii
|
|
if ((0 == (mState & TEXT_WAS_TRANSFORMED)) && !frag->Is2b() && !hasMultiByteChars) {
|
|
PaintAsciiText(aPresContext, aRenderingContext, sc, ts, 0, 0);
|
|
|
|
} else if (hasMultiByteChars || (0 == (hints & NS_RENDERING_HINT_FAST_8BIT_TEXT))) {
|
|
// If it has multi-byte characters then we have to render it as Unicode
|
|
// regardless of whether the text fragment is 1-byte or 2-byte characters.
|
|
// Or if the text fragment requires transforming then leave it up to the
|
|
// rendering context's preference
|
|
PaintUnicodeText(aPresContext, aRenderingContext, sc, ts, 0, 0);
|
|
}
|
|
else {
|
|
// Use char rendering routine
|
|
PaintAsciiText(aPresContext, aRenderingContext, sc, ts, 0, 0);
|
|
}
|
|
}
|
|
}
|
|
return NS_OK;
|
|
}
|
|
|
|
|
|
/**
|
|
* Prepare the text in the content for rendering. If aIndexes is not nsnull
|
|
* then fill in aIndexes's with the mapping from the original input to
|
|
* the prepared output.
|
|
*/
|
|
PRIntn
|
|
nsTextFrame::PrepareUnicodeText(nsTextTransformer& aTX,
|
|
nsAutoIndexBuffer* aIndexBuffer,
|
|
nsAutoTextBuffer* aTextBuffer,
|
|
PRInt32* aTextLen)
|
|
{
|
|
PRIntn numSpaces = 0;
|
|
|
|
// Setup transform to operate starting in the content at our content
|
|
// offset
|
|
aTX.Init(this, mContent, mContentOffset);
|
|
|
|
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;
|
|
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.
|
|
PRBool inWord = (TEXT_IN_WORD & mState) ? PR_TRUE : PR_FALSE;
|
|
PRInt32 column = mColumn;
|
|
PRInt32 textLength = 0;
|
|
PRInt32 dstOffset = 0;
|
|
while (0 != n) {
|
|
PRUnichar* bp;
|
|
PRBool isWhitespace, wasTransformed;
|
|
PRInt32 wordLen, contentLen;
|
|
|
|
// Get the next word
|
|
bp = aTX.GetNextWord(inWord, &wordLen, &contentLen, &isWhitespace, &wasTransformed);
|
|
if (nsnull == bp) {
|
|
break;
|
|
}
|
|
if (contentLen > n) {
|
|
contentLen = n;
|
|
}
|
|
if (wordLen > n) {
|
|
wordLen = n;
|
|
}
|
|
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++;
|
|
}
|
|
}
|
|
}
|
|
numSpaces += wordLen;
|
|
}
|
|
else {
|
|
PRInt32 i;
|
|
if (nsnull != indexp) {
|
|
// Point mapping indicies at each content index in the word
|
|
i = contentLen;
|
|
while (--i >= 0) {
|
|
*indexp++ = strInx++;
|
|
}
|
|
}
|
|
// Nonbreaking spaces count as spaces, not letters
|
|
PRUnichar* tp = bp;
|
|
i = wordLen;
|
|
while (--i >= 0) {
|
|
if (*tp++ == ' ') {
|
|
numSpaces++;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Grow the buffer before we run out of room. The only time this
|
|
// happens is because of tab expansion.
|
|
if (aTextBuffer != nsnull && dstOffset + wordLen > aTextBuffer->mBufferLen) {
|
|
nsresult rv = aTextBuffer->GrowBy(wordLen);
|
|
if (NS_FAILED(rv)) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
column += wordLen;
|
|
textLength += wordLen;
|
|
n -= contentLen;
|
|
if (aTextBuffer != nsnull) {
|
|
nsCRT::memcpy(aTextBuffer->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 (aTextBuffer) {
|
|
NS_ASSERTION(dstOffset <= aTextBuffer->mBufferLen,
|
|
"yikes - we just overwrote memory");
|
|
}
|
|
|
|
#endif
|
|
|
|
// Remove trailing whitespace if it was trimmed after reflow
|
|
if (TEXT_TRIMMED_WS & mState) {
|
|
NS_ASSERTION(aTextBuffer != nsnull,
|
|
"Nonexistent text buffer should only occur during reflow, i.e. before whitespace is trimmed");
|
|
if (--dstOffset >= 0) {
|
|
PRUnichar ch = aTextBuffer->mBuffer[dstOffset];
|
|
if (XP_IS_SPACE(ch)) {
|
|
textLength--;
|
|
numSpaces--;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (aIndexBuffer) {
|
|
PRInt32* ip = aIndexBuffer->mBuffer;
|
|
ip[mContentLength] = ip[mContentLength-1];
|
|
if ((ip[mContentLength] - mContentOffset) < textLength) {
|
|
// Must set up last one for selection beyond edge if in boundary
|
|
ip[mContentLength]++;
|
|
}
|
|
}
|
|
|
|
*aTextLen = textLength;
|
|
return numSpaces;
|
|
}
|
|
|
|
|
|
//#define SHOW_SELECTION_CURSOR // should be turned off when the caret code is activated
|
|
|
|
#ifdef SHOW_SELECTION_CURSOR
|
|
|
|
// XXX This clearly needs to be done by the container, *somehow*
|
|
#define CURSOR_COLOR NS_RGB(0,0,255)
|
|
static void
|
|
RenderSelectionCursor(nsIRenderingContext& aRenderingContext,
|
|
nscoord dx, nscoord dy, nscoord aHeight,
|
|
nscolor aCursorColor)
|
|
{
|
|
nsPoint pnts[4];
|
|
nscoord ox = aHeight / 4;
|
|
nscoord oy = ox;
|
|
nscoord x0 = dx;
|
|
nscoord y0 = dy + aHeight;
|
|
pnts[0].x = x0 - ox;
|
|
pnts[0].y = y0;
|
|
pnts[1].x = x0;
|
|
pnts[1].y = y0 - oy;
|
|
pnts[2].x = x0 + ox;
|
|
pnts[2].y = y0;
|
|
pnts[3].x = x0 - ox;
|
|
pnts[3].y = y0;
|
|
|
|
// Draw little blue triangle
|
|
aRenderingContext.SetColor(aCursorColor);
|
|
aRenderingContext.FillPolygon(pnts, 4);
|
|
}
|
|
|
|
#endif
|
|
|
|
// XXX letter-spacing
|
|
// XXX word-spacing
|
|
#if defined(XP_PC) || defined(XP_UNIX) || defined(XP_MAC)
|
|
#define USE_INVERT_FOR_SELECTION
|
|
#endif
|
|
|
|
// XXX we should get the following from style sheet or LookAndFeel later
|
|
#define IME_RAW_COLOR NS_RGB(198,33,66)
|
|
#define IME_CONVERTED_COLOR NS_RGB(255,198,198)
|
|
|
|
void
|
|
nsTextFrame::PaintTextDecorations(nsIRenderingContext& aRenderingContext,
|
|
nsIStyleContext* aStyleContext,
|
|
TextStyle& aTextStyle,
|
|
nscoord aX, nscoord aY, nscoord aWidth,
|
|
PRUnichar *aText, /*=nsnull*/
|
|
SelectionDetails *aDetails,/*= nsnull*/
|
|
PRUint32 aIndex, /*= 0*/
|
|
PRUint32 aLength, /*= 0*/
|
|
const nscoord* aSpacing /* = nsnull*/ )
|
|
|
|
{
|
|
nscolor overColor;
|
|
nscolor underColor;
|
|
nscolor strikeColor;
|
|
nsIStyleContext* context = aStyleContext;
|
|
PRUint8 decorations = aTextStyle.mFont->mFont.decorations;
|
|
PRUint8 decorMask = decorations;
|
|
|
|
NS_ADDREF(context);
|
|
do { // find decoration colors
|
|
const nsStyleText* styleText =
|
|
(const nsStyleText*)context->GetStyleData(eStyleStruct_Text);
|
|
if (decorMask & styleText->mTextDecoration) { // a decoration defined here
|
|
const nsStyleColor* styleColor =
|
|
(const nsStyleColor*)context->GetStyleData(eStyleStruct_Color);
|
|
if (NS_STYLE_TEXT_DECORATION_UNDERLINE & decorMask & styleText->mTextDecoration) {
|
|
underColor = styleColor->mColor;
|
|
decorMask &= ~NS_STYLE_TEXT_DECORATION_UNDERLINE;
|
|
}
|
|
if (NS_STYLE_TEXT_DECORATION_OVERLINE & decorMask & styleText->mTextDecoration) {
|
|
overColor = styleColor->mColor;
|
|
decorMask &= ~NS_STYLE_TEXT_DECORATION_OVERLINE;
|
|
}
|
|
if (NS_STYLE_TEXT_DECORATION_LINE_THROUGH & decorMask & styleText->mTextDecoration) {
|
|
strikeColor = styleColor->mColor;
|
|
decorMask &= ~NS_STYLE_TEXT_DECORATION_LINE_THROUGH;
|
|
}
|
|
}
|
|
if (0 != decorMask) {
|
|
nsIStyleContext* lastContext = context;
|
|
context = context->GetParent();
|
|
NS_RELEASE(lastContext);
|
|
}
|
|
} while ((nsnull != context) && (0 != decorMask));
|
|
NS_IF_RELEASE(context);
|
|
|
|
nscoord offset;
|
|
nscoord size;
|
|
nscoord baseline;
|
|
aTextStyle.mNormalFont->GetMaxAscent(baseline);
|
|
if (decorations & (NS_FONT_DECORATION_OVERLINE | NS_FONT_DECORATION_UNDERLINE)) {
|
|
aTextStyle.mNormalFont->GetUnderline(offset, size);
|
|
if (decorations & NS_FONT_DECORATION_OVERLINE) {
|
|
aRenderingContext.SetColor(overColor);
|
|
aRenderingContext.FillRect(aX, aY, aWidth, size);
|
|
}
|
|
if (decorations & NS_FONT_DECORATION_UNDERLINE) {
|
|
aRenderingContext.SetColor(underColor);
|
|
aRenderingContext.FillRect(aX, aY + baseline - offset, aWidth, size);
|
|
}
|
|
}
|
|
if (decorations & NS_FONT_DECORATION_LINE_THROUGH) {
|
|
aTextStyle.mNormalFont->GetStrikeout(offset, size);
|
|
aRenderingContext.SetColor(strikeColor);
|
|
aRenderingContext.FillRect(aX, aY + baseline - offset, aWidth, size);
|
|
}
|
|
if (aDetails){
|
|
nsRect rect;
|
|
GetRect(rect);
|
|
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);
|
|
|
|
}
|
|
switch (aDetails->mType)
|
|
{
|
|
case nsISelectionController::SELECTION_NORMAL:
|
|
#if 0
|
|
{
|
|
//using new selectionpainting now
|
|
//
|
|
// XOR InvertRect is currently implemented only in the unix and windows
|
|
// rendering contexts. When other platforms implement InvertRect(), they
|
|
// can be added here. Eventually this #ifdef should die.
|
|
//
|
|
// For platforms that dont implement InvertRect(), the selection will be
|
|
// a non-filled rectangle.
|
|
#ifdef USE_INVERT_FOR_SELECTION
|
|
aRenderingContext.SetColor(NS_RGB(255,255,255));
|
|
aRenderingContext.InvertRect(aX + startOffset, aY, textWidth, rect.height);
|
|
#else
|
|
aRenderingContext.SetColor(NS_RGB(0,0,0));
|
|
aRenderingContext.DrawRect(aX + startOffset, aY, textWidth, rect.height);
|
|
#endif
|
|
}
|
|
#endif //0
|
|
break;
|
|
case nsISelectionController::SELECTION_SPELLCHECK:{
|
|
aTextStyle.mNormalFont->GetUnderline(offset, size);
|
|
aRenderingContext.SetColor(NS_RGB(255,0,0));
|
|
aRenderingContext.FillRect(aX + startOffset, aY + baseline - offset, textWidth, size);
|
|
}break;
|
|
case nsISelectionController::SELECTION_IME_SELECTEDRAWTEXT:{
|
|
#ifdef USE_INVERT_FOR_SELECTION
|
|
aRenderingContext.SetColor(NS_RGB(255,255,255));
|
|
aRenderingContext.InvertRect(aX + startOffset, aY, textWidth, rect.height);
|
|
#else
|
|
aRenderingContext.SetColor(NS_RGB(255,255,128));
|
|
aRenderingContext.DrawRect(aX + startOffset, aY, textWidth, rect.height);
|
|
#endif
|
|
aTextStyle.mNormalFont->GetUnderline(offset, size);
|
|
aRenderingContext.SetColor(IME_RAW_COLOR);
|
|
aRenderingContext.FillRect(aX + startOffset+size, aY + baseline - offset, textWidth-2*size, size);
|
|
}break;
|
|
case nsISelectionController::SELECTION_IME_RAWINPUT:{
|
|
aTextStyle.mNormalFont->GetUnderline(offset, size);
|
|
aRenderingContext.SetColor(IME_RAW_COLOR);
|
|
aRenderingContext.FillRect(aX + startOffset+size, aY + baseline - offset, textWidth-2*size, size);
|
|
}break;
|
|
case nsISelectionController::SELECTION_IME_SELECTEDCONVERTEDTEXT:{
|
|
#ifdef USE_INVERT_FOR_SELECTION
|
|
aRenderingContext.SetColor(NS_RGB(255,255,255));
|
|
aRenderingContext.InvertRect(aX + startOffset, aY, textWidth, rect.height);
|
|
#else
|
|
aRenderingContext.SetColor(NS_RGB(255,255,128));
|
|
aRenderingContext.DrawRect(aX + startOffset, aY, textWidth, rect.height);
|
|
#endif
|
|
aTextStyle.mNormalFont->GetUnderline(offset, size);
|
|
aRenderingContext.SetColor(IME_CONVERTED_COLOR);
|
|
aRenderingContext.FillRect(aX + startOffset+size, aY + baseline - offset, textWidth-2*size, size);
|
|
}break;
|
|
case nsISelectionController::SELECTION_IME_CONVERTEDTEXT:{
|
|
aTextStyle.mNormalFont->GetUnderline(offset, size);
|
|
aRenderingContext.SetColor(IME_CONVERTED_COLOR);
|
|
aRenderingContext.FillRect(aX + startOffset+size, aY + baseline - offset, textWidth-2*size, size);
|
|
}break;
|
|
default:
|
|
NS_ASSERTION(0,"what type of selection do i not know about?");
|
|
break;
|
|
}
|
|
|
|
}
|
|
}
|
|
aDetails = aDetails->mNext;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
nsresult
|
|
nsTextFrame::GetContentAndOffsetsForSelection(nsIPresContext *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;
|
|
nsresult rv = GetParent(&parent);
|
|
if (NS_SUCCEEDED(rv) && parent)
|
|
{
|
|
nsFrameState parentFrameState;
|
|
|
|
parent->GetFrameState(&parentFrameState);
|
|
if ((parentFrameState & 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
|
|
rv = parent->GetContent(aContent);
|
|
if (NS_FAILED(rv) || !*aContent)
|
|
return rv?rv:NS_ERROR_FAILURE;
|
|
|
|
//ARE WE A BEFORE FRAME? if not then we assume we are an after frame. this may be bad later
|
|
nsIFrame *grandParent;
|
|
nsIFrame *firstParent;
|
|
rv = parent->GetParent(&grandParent);
|
|
if (NS_SUCCEEDED(rv) && grandParent)
|
|
{
|
|
rv = grandParent->FirstChild(aPresContext,nsnull, &firstParent);
|
|
if (NS_SUCCEEDED(rv) && firstParent)
|
|
{
|
|
*aLength = 0;
|
|
if (firstParent == parent) //then our parent is the first child of granddad. use BEFORE
|
|
{
|
|
*aOffset = 0;
|
|
}
|
|
else
|
|
{
|
|
PRInt32 numChildren;
|
|
if (NS_SUCCEEDED(rv = (*aContent)->ChildCount(numChildren)))
|
|
*aOffset = numChildren;
|
|
else
|
|
return rv;
|
|
}
|
|
}
|
|
else
|
|
return rv;
|
|
}
|
|
}
|
|
}
|
|
//END GENERATED BLOCK
|
|
if (!*aContent)
|
|
{
|
|
*aContent = mContent;
|
|
NS_IF_ADDREF(*aContent);
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
|
|
void
|
|
nsTextFrame::PaintUnicodeText(nsIPresContext* aPresContext,
|
|
nsIRenderingContext& aRenderingContext,
|
|
nsIStyleContext* aStyleContext,
|
|
TextStyle& aTextStyle,
|
|
nscoord dx, nscoord dy)
|
|
{
|
|
nsCOMPtr<nsIPresShell> shell;
|
|
//get the presshell
|
|
nsresult rv = aPresContext->GetShell(getter_AddRefs(shell));
|
|
if (NS_FAILED(rv) || !shell)
|
|
return;
|
|
//get the selection controller
|
|
nsCOMPtr<nsISelectionController> selCon;
|
|
rv = GetSelectionController(aPresContext, getter_AddRefs(selCon));
|
|
if (NS_FAILED(rv) || !selCon)
|
|
return;
|
|
|
|
PRInt16 selectionValue;
|
|
selCon->GetDisplaySelection(&selectionValue);
|
|
//if greater than hidden then we display some kind of selection
|
|
PRBool displaySelection = selectionValue > nsISelectionController::SELECTION_HIDDEN;
|
|
|
|
// 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 text 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()...
|
|
nsCOMPtr<nsIDocument> doc;
|
|
shell->GetDocument(getter_AddRefs(doc));
|
|
if (!doc)
|
|
return;
|
|
nsCOMPtr<nsILineBreaker> lb;
|
|
doc->GetLineBreaker(getter_AddRefs(lb));
|
|
nsTextTransformer tx(lb, nsnull, aPresContext);
|
|
PRInt32 textLength;
|
|
// no need to worry about justification, that's always on the slow path
|
|
PrepareUnicodeText(tx, (displaySelection ? &indexBuffer : nsnull),
|
|
&paintBuffer, &textLength);
|
|
|
|
PRInt32* ip = indexBuffer.mBuffer;
|
|
PRUnichar* text = paintBuffer.mBuffer;
|
|
nsFrameState frameState;
|
|
PRBool isSelected;
|
|
GetFrameState(&frameState);
|
|
isSelected = (frameState & NS_FRAME_SELECTED_CONTENT) == NS_FRAME_SELECTED_CONTENT;
|
|
|
|
if (0 != textLength)
|
|
{
|
|
if (!displaySelection || !isSelected ) //draw text normally
|
|
{
|
|
// When there is no selection showing, use the fastest and
|
|
// simplest rendering approach
|
|
aRenderingContext.SetColor(aTextStyle.mColor->mColor);
|
|
aRenderingContext.DrawString(text, PRUint32(textLength), dx, dy);
|
|
PaintTextDecorations(aRenderingContext, aStyleContext, aTextStyle,
|
|
dx, dy, width);
|
|
}
|
|
else
|
|
{ //we draw according to selection rules
|
|
SelectionDetails *details = nsnull;
|
|
nsCOMPtr<nsIFrameSelection> frameSelection;
|
|
//get the frameSelection from the selection controller
|
|
if (NS_SUCCEEDED(rv) && selCon)
|
|
{
|
|
frameSelection = do_QueryInterface(selCon); //this MAY implement
|
|
}
|
|
//if that failed get it from the pres shell
|
|
if (!frameSelection)
|
|
rv = shell->GetFrameSelection(getter_AddRefs(frameSelection));
|
|
if (NS_SUCCEEDED(rv) && frameSelection){
|
|
nsCOMPtr<nsIContent> content;
|
|
PRInt32 offset;
|
|
PRInt32 length;
|
|
|
|
rv = GetContentAndOffsetsForSelection(aPresContext,getter_AddRefs(content),&offset,&length);
|
|
if (NS_SUCCEEDED(rv) && content){
|
|
rv = frameSelection->LookUpSelection(content, mContentOffset,
|
|
mContentLength , &details, 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,aTextStyle, selectionValue);
|
|
if (!iter.IsDone() && iter.First())
|
|
{
|
|
nscoord currentX = dx;
|
|
nscoord newWidth;//temp
|
|
while (!iter.IsDone())
|
|
{
|
|
PRUnichar *currenttext = iter.CurrentTextUnicharPtr();
|
|
PRUint32 currentlength= iter.CurrentLength();
|
|
//TextStyle ¤tStyle = iter.CurrentStyle();
|
|
nscolor currentFGColor = iter.CurrentForeGroundColor();
|
|
nscolor currentBKColor;
|
|
|
|
if (NS_SUCCEEDED(aRenderingContext.GetWidth(currenttext, currentlength,newWidth)))//ADJUST FOR CHAR SPACING
|
|
{
|
|
if (iter.CurrentBackGroundColor(currentBKColor))
|
|
{//DRAW RECT HERE!!!
|
|
aRenderingContext.SetColor(currentBKColor);
|
|
aRenderingContext.FillRect(currentX, dy, newWidth, mRect.height);
|
|
currentFGColor = EnsureDifferentColors(currentFGColor, currentBKColor);
|
|
}
|
|
}
|
|
else
|
|
newWidth =0;
|
|
|
|
|
|
aRenderingContext.SetColor(currentFGColor);
|
|
aRenderingContext.DrawString(currenttext, currentlength, currentX, dy);
|
|
|
|
currentX+=newWidth;//increment twips X start
|
|
|
|
iter.Next();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
aRenderingContext.SetColor(aTextStyle.mColor->mColor);
|
|
aRenderingContext.DrawString(text, PRUint32(textLength), dx, dy);
|
|
}
|
|
PaintTextDecorations(aRenderingContext, aStyleContext,
|
|
aTextStyle, dx, dy, width, text, details,0,(PRUint32)textLength);
|
|
sdptr = details;
|
|
if (details){
|
|
while ((sdptr = details->mNext) != nsnull) {
|
|
delete details;
|
|
details = sdptr;
|
|
}
|
|
delete details;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//measure Spaced Textvoid
|
|
nsresult
|
|
nsTextFrame::GetPositionSlowly(nsIPresContext* aPresContext,
|
|
nsIRenderingContext* aRendContext,
|
|
const nsPoint& aPoint,
|
|
nsIContent** aNewContent,
|
|
PRInt32& aOffset)
|
|
|
|
{
|
|
// pre-condition tests
|
|
NS_PRECONDITION(aPresContext && aRendContext && aNewContent, "null arg");
|
|
if (!aPresContext || !aRendContext || !aNewContent) {
|
|
return NS_ERROR_NULL_POINTER;
|
|
}
|
|
// initialize out param
|
|
*aNewContent = nsnull;
|
|
|
|
TextStyle ts(aPresContext, *aRendContext, mStyleContext);
|
|
if (!ts.mSmallCaps && !ts.mWordSpacing && !ts.mLetterSpacing && !ts.mJustifying) {
|
|
return NS_ERROR_INVALID_ARG;
|
|
}
|
|
nsIView * view;
|
|
nsPoint origin;
|
|
GetOffsetFromView(aPresContext, origin, &view);
|
|
|
|
/* 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 - origin.x < 0)
|
|
{
|
|
*aNewContent = mContent;
|
|
aOffset =0;
|
|
}
|
|
nsCOMPtr<nsIDocument> doc(getter_AddRefs(GetDocument(aPresContext)));
|
|
|
|
// 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
|
|
nsCOMPtr<nsILineBreaker> lb;
|
|
doc->GetLineBreaker(getter_AddRefs(lb));
|
|
nsTextTransformer tx(lb, nsnull, aPresContext);
|
|
PRInt32 textLength;
|
|
PRInt32 numSpaces;
|
|
|
|
numSpaces = PrepareUnicodeText(tx, &indexBuffer, &paintBuffer, &textLength);
|
|
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;
|
|
}
|
|
|
|
ComputeExtraJustificationSpacing(*aRendContext, ts, paintBuffer.mBuffer, textLength, numSpaces);
|
|
|
|
//IF STYLE SAYS TO SELECT TO END OF FRAME HERE...
|
|
nsCOMPtr<nsIPref> prefs( do_GetService(kPrefCID, &rv) );
|
|
PRInt32 prefInt = 0;
|
|
PRBool outofstylehandled = PR_FALSE;
|
|
if (NS_SUCCEEDED(rv) && prefs)
|
|
{
|
|
if (NS_SUCCEEDED(prefs->GetIntPref("browser.drag_out_of_frame_style", &prefInt)) && prefInt)
|
|
{
|
|
nsRect bounds(mRect);
|
|
|
|
bounds.MoveBy(origin.x, origin.y);
|
|
if (aPoint.y < bounds.y)//above rectangle
|
|
{
|
|
aOffset = mContentOffset;
|
|
outofstylehandled = PR_TRUE;
|
|
}
|
|
else if (aPoint.y > (bounds.y + bounds.height))
|
|
{
|
|
aOffset = mContentOffset + mContentLength;
|
|
outofstylehandled = PR_TRUE;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!outofstylehandled) //then we drag to closest X point and dont worry about the 'Y'
|
|
//END STYLE RULE
|
|
{
|
|
//the following will first get the index into the PAINTBUFFER then the actual content
|
|
nscoord adjustedX = PR_MAX(0,aPoint.x-origin.x);
|
|
|
|
aOffset = mContentOffset+GetLengthSlowly(*aRendContext, ts,paintBuffer.mBuffer,textLength,adjustedX);
|
|
PRInt32 i;
|
|
for (i = 0;i <= mContentLength; i ++){
|
|
if (indexBuffer.mBuffer[i] >= aOffset){ //reverse mapping
|
|
aOffset = i + mContentOffset;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
*aNewContent = mContent;
|
|
if (*aNewContent)
|
|
(*aNewContent)->AddRef();
|
|
return NS_OK;
|
|
}
|
|
|
|
void
|
|
nsTextFrame::RenderString(nsIRenderingContext& aRenderingContext,
|
|
nsIStyleContext* aStyleContext,
|
|
TextStyle& aTextStyle,
|
|
PRUnichar* aBuffer, PRInt32 aLength,
|
|
nscoord aX, nscoord aY,
|
|
nscoord aWidth,
|
|
SelectionDetails *aDetails /*=nsnull*/)
|
|
{
|
|
PRUnichar buf[TEXT_BUF_SIZE];
|
|
PRUnichar* bp0 = buf;
|
|
if (aLength > TEXT_BUF_SIZE) {
|
|
bp0 = new PRUnichar[aLength];
|
|
}
|
|
PRUnichar* bp = bp0;
|
|
|
|
PRBool spacing = (0 != aTextStyle.mLetterSpacing) ||
|
|
(0 != aTextStyle.mWordSpacing) || aTextStyle.mJustifying;
|
|
nscoord spacingMem[TEXT_BUF_SIZE];
|
|
PRIntn* sp0 = spacingMem;
|
|
if (spacing && (aLength > TEXT_BUF_SIZE)) {
|
|
sp0 = new nscoord[aLength];
|
|
}
|
|
PRIntn* sp = sp0;
|
|
|
|
nscoord smallY = aY;
|
|
if (aTextStyle.mSmallCaps) {
|
|
nscoord normalAscent, smallAscent;
|
|
aTextStyle.mNormalFont->GetMaxAscent(normalAscent);
|
|
aTextStyle.mSmallFont->GetMaxAscent(smallAscent);
|
|
if (normalAscent > smallAscent) {
|
|
smallY = aY + normalAscent - smallAscent;
|
|
}
|
|
}
|
|
|
|
nsIFontMetrics* lastFont = aTextStyle.mLastFont;
|
|
nscoord lastY = aY;
|
|
if (lastFont == aTextStyle.mSmallFont) {
|
|
lastY = smallY;
|
|
}
|
|
PRInt32 pendingCount;
|
|
PRUnichar* runStart = bp;
|
|
nscoord charWidth, width = 0;
|
|
PRInt32 countSoFar = 0;
|
|
for (; --aLength >= 0; aBuffer++) {
|
|
nsIFontMetrics* nextFont;
|
|
nscoord nextY, glyphWidth;
|
|
PRUnichar ch = *aBuffer;
|
|
if (aTextStyle.mSmallCaps && nsCRT::IsLower(ch)) {
|
|
nextFont = aTextStyle.mSmallFont;
|
|
nextY = smallY;
|
|
ch = nsCRT::ToUpper(ch);
|
|
if (lastFont != aTextStyle.mSmallFont) {
|
|
aRenderingContext.SetFont(aTextStyle.mSmallFont);
|
|
aRenderingContext.GetWidth(ch, charWidth);
|
|
aRenderingContext.SetFont(aTextStyle.mNormalFont);
|
|
}
|
|
else {
|
|
aRenderingContext.GetWidth(ch, charWidth);
|
|
}
|
|
glyphWidth = charWidth + aTextStyle.mLetterSpacing;
|
|
}
|
|
else if (ch == ' ') {
|
|
nextFont = aTextStyle.mNormalFont;
|
|
nextY = aY;
|
|
glyphWidth = aTextStyle.mSpaceWidth + aTextStyle.mWordSpacing
|
|
+ aTextStyle.mExtraSpacePerSpace;
|
|
if ((PRUint32)--aTextStyle.mNumSpacesToRender <
|
|
(PRUint32)aTextStyle.mNumSpacesReceivingExtraJot) {
|
|
glyphWidth++;
|
|
}
|
|
}
|
|
else {
|
|
if (lastFont != aTextStyle.mNormalFont) {
|
|
aRenderingContext.SetFont(aTextStyle.mNormalFont);
|
|
aRenderingContext.GetWidth(ch, charWidth);
|
|
aRenderingContext.SetFont(aTextStyle.mSmallFont);
|
|
}
|
|
else {
|
|
aRenderingContext.GetWidth(ch, charWidth);
|
|
}
|
|
nextFont = aTextStyle.mNormalFont;
|
|
nextY = aY;
|
|
glyphWidth = charWidth + aTextStyle.mLetterSpacing;
|
|
}
|
|
if (nextFont != lastFont) {
|
|
pendingCount = bp - runStart;
|
|
if (0 != pendingCount) {
|
|
// Measure previous run of characters using the previous font
|
|
//aRenderingContext.SetColor(aTextStyle.mColor->mColor); commenting out redundat(and destructive) call to setcolor
|
|
aRenderingContext.DrawString(runStart, pendingCount,
|
|
aX, lastY, -1,
|
|
spacing ? sp0 : nsnull);
|
|
|
|
// Note: use aY not small-y so that decorations are drawn with
|
|
// respect to the normal-font not the current font.
|
|
PaintTextDecorations(aRenderingContext, aStyleContext, aTextStyle,
|
|
aX, aY, width, runStart, aDetails,countSoFar,pendingCount, spacing ? sp0 : nsnull);
|
|
countSoFar += pendingCount;
|
|
aWidth -= width;
|
|
aX += width;
|
|
runStart = bp = bp0;
|
|
sp = sp0;
|
|
width = 0;
|
|
}
|
|
aRenderingContext.SetFont(nextFont);
|
|
lastFont = nextFont;
|
|
lastY = nextY;
|
|
}
|
|
*bp++ = ch;
|
|
*sp++ = glyphWidth;
|
|
width += glyphWidth;
|
|
}
|
|
pendingCount = bp - runStart;
|
|
if (0 != pendingCount) {
|
|
// Measure previous run of characters using the previous font
|
|
aRenderingContext.DrawString(runStart, pendingCount, aX, lastY, -1,
|
|
spacing ? sp0 : nsnull);
|
|
|
|
// Note: use aY not small-y so that decorations are drawn with
|
|
// respect to the normal-font not the current font.
|
|
PaintTextDecorations(aRenderingContext, aStyleContext, aTextStyle,
|
|
aX, aY, aWidth, runStart, aDetails,countSoFar,pendingCount,
|
|
spacing ? sp0 : nsnull);
|
|
}
|
|
aTextStyle.mLastFont = lastFont;
|
|
|
|
if (bp0 != buf) {
|
|
delete [] bp0;
|
|
}
|
|
if (sp0 != spacingMem) {
|
|
delete [] sp0;
|
|
}
|
|
}
|
|
|
|
inline void
|
|
nsTextFrame::MeasureSmallCapsText(const nsHTMLReflowState& aReflowState,
|
|
TextStyle& aTextStyle,
|
|
PRUnichar* aWord,
|
|
PRInt32 aWordLength,
|
|
nscoord* aWidthResult)
|
|
{
|
|
nsIRenderingContext& rc = *aReflowState.rendContext;
|
|
*aWidthResult = 0;
|
|
GetWidth(rc, aTextStyle, aWord, aWordLength, aWidthResult);
|
|
if (aTextStyle.mLastFont != aTextStyle.mNormalFont) {
|
|
rc.SetFont(aTextStyle.mNormalFont);
|
|
aTextStyle.mLastFont = aTextStyle.mNormalFont;
|
|
}
|
|
}
|
|
|
|
|
|
PRInt32
|
|
nsTextFrame::GetWidthOrLength(nsIRenderingContext& aRenderingContext,
|
|
TextStyle& aStyle,
|
|
PRUnichar* aBuffer, PRInt32 aLength,
|
|
nscoord* aWidthResult,
|
|
PRBool aGetWidth/* true=get width false = return length up to aWidthResult size*/)
|
|
{
|
|
PRUnichar *inBuffer = aBuffer;
|
|
PRInt32 length = aLength;
|
|
nsAutoTextBuffer widthBuffer;
|
|
if (NS_FAILED(widthBuffer.GrowTo(length))) {
|
|
*aWidthResult = 0;
|
|
return 0;
|
|
}
|
|
PRUnichar* bp = widthBuffer.mBuffer;
|
|
|
|
nsIFontMetrics* lastFont = aStyle.mLastFont;
|
|
nscoord sum = 0;
|
|
nscoord charWidth;
|
|
while (--length >= 0) {
|
|
nscoord glyphWidth;
|
|
PRUnichar ch = *inBuffer++;
|
|
if (aStyle.mSmallCaps && nsCRT::IsLower(ch)) {
|
|
ch = nsCRT::ToUpper(ch);
|
|
if (lastFont != aStyle.mSmallFont) {
|
|
lastFont = aStyle.mSmallFont;
|
|
aRenderingContext.SetFont(lastFont);
|
|
}
|
|
aRenderingContext.GetWidth(ch, charWidth);
|
|
glyphWidth = charWidth + aStyle.mLetterSpacing;
|
|
}
|
|
else if (ch == ' ') {
|
|
glyphWidth = aStyle.mSpaceWidth + aStyle.mWordSpacing
|
|
+ aStyle.mExtraSpacePerSpace;
|
|
if ((PRUint32)--aStyle.mNumSpacesToMeasure
|
|
< (PRUint32)aStyle.mNumSpacesReceivingExtraJot) {
|
|
glyphWidth++;
|
|
}
|
|
}
|
|
else {
|
|
if (lastFont != aStyle.mNormalFont) {
|
|
lastFont = aStyle.mNormalFont;
|
|
aRenderingContext.SetFont(lastFont);
|
|
}
|
|
aRenderingContext.GetWidth(ch, charWidth);
|
|
glyphWidth = charWidth + aStyle.mLetterSpacing;
|
|
}
|
|
sum += glyphWidth;
|
|
*bp++ = ch;
|
|
if (!aGetWidth && sum >= *aWidthResult)
|
|
{
|
|
PRInt32 result = aLength - length;
|
|
if (2*(sum - *aWidthResult) > glyphWidth) //then we have gone too far, back up 1
|
|
result--;
|
|
aStyle.mLastFont = lastFont;
|
|
return result;
|
|
}
|
|
}
|
|
aStyle.mLastFont = lastFont;
|
|
*aWidthResult = sum;
|
|
return aLength;
|
|
}
|
|
|
|
|
|
// XXX factor in logic from RenderString into here; gaps, justification, etc.
|
|
void
|
|
nsTextFrame::GetWidth(nsIRenderingContext& aRenderingContext,
|
|
TextStyle& aTextStyle,
|
|
PRUnichar* aBuffer, PRInt32 aLength,
|
|
nscoord* aWidthResult)
|
|
{
|
|
GetWidthOrLength(aRenderingContext,aTextStyle,aBuffer,aLength,aWidthResult,PR_TRUE);
|
|
}
|
|
|
|
PRInt32
|
|
nsTextFrame::GetLengthSlowly(nsIRenderingContext& aRenderingContext,
|
|
TextStyle& aStyle,
|
|
PRUnichar* aBuffer, PRInt32 aLength,
|
|
nscoord aWidth)
|
|
{
|
|
return GetWidthOrLength(aRenderingContext,aStyle,aBuffer,aLength,&aWidth,PR_FALSE);
|
|
}
|
|
|
|
void
|
|
nsTextFrame::ComputeExtraJustificationSpacing(nsIRenderingContext& aRenderingContext,
|
|
TextStyle& aTextStyle,
|
|
PRUnichar* aBuffer, PRInt32 aLength,
|
|
PRInt32 aNumSpaces)
|
|
{
|
|
if (aTextStyle.mJustifying) {
|
|
nscoord trueWidth;
|
|
|
|
// 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
|
|
// spaces. It has to be the same ones every time or things will go haywire.
|
|
// This implies that the GetWidthOrLength 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 spaces
|
|
// 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.mNumSpacesToMeasure = 0;
|
|
aTextStyle.mExtraSpacePerSpace = 0;
|
|
aTextStyle.mNumSpacesReceivingExtraJot = 0;
|
|
|
|
GetWidth(aRenderingContext, aTextStyle, aBuffer, aLength, &trueWidth);
|
|
|
|
aTextStyle.mNumSpacesToMeasure = aNumSpaces;
|
|
aTextStyle.mNumSpacesToRender = aNumSpaces;
|
|
|
|
nscoord extraSpace = mRect.width - trueWidth;
|
|
|
|
if (extraSpace > 0 && aNumSpaces > 0) {
|
|
aTextStyle.mExtraSpacePerSpace = extraSpace/aNumSpaces;
|
|
aTextStyle.mNumSpacesReceivingExtraJot =
|
|
extraSpace - aTextStyle.mExtraSpacePerSpace*aNumSpaces;
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
nsTextFrame::PaintTextSlowly(nsIPresContext* aPresContext,
|
|
nsIRenderingContext& aRenderingContext,
|
|
nsIStyleContext* aStyleContext,
|
|
TextStyle& aTextStyle,
|
|
nscoord dx, nscoord dy)
|
|
{
|
|
nsCOMPtr<nsIPresShell> shell;
|
|
nsresult rv = aPresContext->GetShell(getter_AddRefs(shell));
|
|
if (NS_FAILED(rv) || !shell)
|
|
return;
|
|
nsCOMPtr<nsISelectionController> selCon;
|
|
rv = GetSelectionController(aPresContext, getter_AddRefs(selCon));
|
|
if (NS_FAILED(rv) || !selCon)
|
|
return;
|
|
|
|
PRInt16 selectionValue;
|
|
selCon->GetDisplaySelection(&selectionValue);
|
|
PRBool displaySelection = selectionValue > nsISelectionController::SELECTION_HIDDEN;
|
|
|
|
|
|
// Make enough space to transform
|
|
nsAutoTextBuffer paintBuffer;
|
|
nsAutoIndexBuffer indexBuffer;
|
|
if (NS_FAILED(indexBuffer.GrowTo(mContentLength + 1))) {
|
|
return;
|
|
}
|
|
nscoord width = mRect.width;
|
|
PRInt32 textLength;
|
|
|
|
// Transform text from content into renderable form
|
|
nsCOMPtr<nsIDocument> doc;
|
|
shell->GetDocument(getter_AddRefs(doc));
|
|
if (!doc)
|
|
return;
|
|
nsCOMPtr<nsILineBreaker> lb;
|
|
doc->GetLineBreaker(getter_AddRefs(lb));
|
|
nsTextTransformer tx(lb, nsnull, aPresContext);
|
|
PRInt32 numSpaces;
|
|
|
|
numSpaces = PrepareUnicodeText(tx, (displaySelection ? &indexBuffer : nsnull),
|
|
&paintBuffer, &textLength);
|
|
|
|
|
|
PRInt32* ip = indexBuffer.mBuffer;
|
|
PRUnichar* text = paintBuffer.mBuffer;
|
|
nsFrameState frameState;
|
|
PRBool isSelected;
|
|
GetFrameState(&frameState);
|
|
isSelected = (frameState & NS_FRAME_SELECTED_CONTENT) == NS_FRAME_SELECTED_CONTENT;
|
|
if (0 != textLength) {
|
|
ComputeExtraJustificationSpacing(aRenderingContext, aTextStyle, text, textLength, numSpaces);
|
|
if (!displaySelection || !isSelected) {
|
|
// When there is no selection showing, use the fastest and
|
|
// simplest rendering approach
|
|
aRenderingContext.SetColor(aTextStyle.mColor->mColor);
|
|
RenderString(aRenderingContext, aStyleContext, aTextStyle,
|
|
text, textLength, dx, dy, width);
|
|
}
|
|
else
|
|
{
|
|
SelectionDetails *details = nsnull;
|
|
nsCOMPtr<nsIFrameSelection> frameSelection;
|
|
//get the frame selection
|
|
frameSelection = do_QueryInterface(selCon); //this MAY implement
|
|
if (!frameSelection)//if that failed get it from the presshell
|
|
rv = shell->GetFrameSelection(getter_AddRefs(frameSelection));
|
|
if (NS_SUCCEEDED(rv) && frameSelection)
|
|
{
|
|
nsCOMPtr<nsIContent> content;
|
|
PRInt32 offset;
|
|
PRInt32 length;
|
|
|
|
rv = GetContentAndOffsetsForSelection(aPresContext,getter_AddRefs(content),&offset,&length);
|
|
if (NS_SUCCEEDED(rv))
|
|
{
|
|
rv = frameSelection->LookUpSelection(content, mContentOffset,
|
|
mContentLength , &details, 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;
|
|
}
|
|
|
|
DrawSelectionIterator iter(details,text,(PRUint32)textLength,aTextStyle, selectionValue);
|
|
if (!iter.IsDone() && iter.First())
|
|
{
|
|
nscoord currentX = dx;
|
|
nscoord newWidth;//temp
|
|
while (!iter.IsDone())
|
|
{
|
|
PRUnichar *currenttext = iter.CurrentTextUnicharPtr();
|
|
PRUint32 currentlength= iter.CurrentLength();
|
|
//TextStyle ¤tStyle = iter.CurrentStyle();
|
|
nscolor currentFGColor = iter.CurrentForeGroundColor();
|
|
nscolor currentBKColor;
|
|
GetWidth(aRenderingContext,aTextStyle,currenttext, (PRInt32)currentlength,&newWidth);
|
|
if (newWidth)
|
|
{
|
|
if (iter.CurrentBackGroundColor(currentBKColor))
|
|
{//DRAW RECT HERE!!!
|
|
aRenderingContext.SetColor(currentBKColor);
|
|
aRenderingContext.FillRect(currentX, dy, newWidth, mRect.height);
|
|
currentFGColor = EnsureDifferentColors(currentFGColor, currentBKColor);
|
|
}
|
|
}
|
|
else
|
|
newWidth =0;
|
|
|
|
aRenderingContext.SetColor(currentFGColor);
|
|
RenderString(aRenderingContext,aStyleContext, aTextStyle, currenttext,
|
|
currentlength, currentX, dy, width, details);
|
|
//increment twips X start but remember to get ready for next draw by reducing current x by letter spacing amount
|
|
currentX+=newWidth;// + aTextStyle.mLetterSpacing;
|
|
|
|
iter.Next();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
aRenderingContext.SetColor(aTextStyle.mColor->mColor);
|
|
RenderString(aRenderingContext,aStyleContext, aTextStyle, text,
|
|
PRUint32(textLength), dx, dy, width, details);
|
|
}
|
|
sdptr = details;
|
|
if (details){
|
|
while ((sdptr = details->mNext) != nsnull) {
|
|
delete details;
|
|
details = sdptr;
|
|
}
|
|
delete details;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
nsTextFrame::PaintAsciiText(nsIPresContext* aPresContext,
|
|
nsIRenderingContext& aRenderingContext,
|
|
nsIStyleContext* aStyleContext,
|
|
TextStyle& aTextStyle,
|
|
nscoord dx, nscoord dy)
|
|
{
|
|
NS_PRECONDITION(0 == (TEXT_HAS_MULTIBYTE & mState), "text is multi-byte");
|
|
nsCOMPtr<nsIPresShell> shell;
|
|
nsresult rv = aPresContext->GetShell(getter_AddRefs(shell));
|
|
if (NS_FAILED(rv) || !shell)
|
|
return;
|
|
nsCOMPtr<nsISelectionController> selCon;
|
|
rv = GetSelectionController(aPresContext, getter_AddRefs(selCon));
|
|
if (NS_FAILED(rv) || !selCon)
|
|
return;
|
|
|
|
PRInt16 selectionValue;
|
|
PRBool isSelected;
|
|
selCon->GetDisplaySelection(&selectionValue);
|
|
PRBool displaySelection = selectionValue > nsISelectionController::SELECTION_HIDDEN;
|
|
|
|
isSelected = (mState & NS_FRAME_SELECTED_CONTENT) == NS_FRAME_SELECTED_CONTENT;
|
|
|
|
// Get the text fragment
|
|
nsCOMPtr<nsITextContent> tc = do_QueryInterface(mContent);
|
|
const nsTextFragment* frag = nsnull;
|
|
if (tc.get()) {
|
|
tc->GetText(&frag);
|
|
}
|
|
if (!frag) {
|
|
return;
|
|
}
|
|
|
|
// Make enough space to transform
|
|
nsAutoTextBuffer unicodePaintBuffer;
|
|
nsAutoIndexBuffer indexBuffer;
|
|
if (displaySelection) {
|
|
if (NS_FAILED(indexBuffer.GrowTo(mContentLength + 1))) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Construct a text transformer
|
|
nsCOMPtr<nsIDocument> doc;
|
|
shell->GetDocument(getter_AddRefs(doc));
|
|
if (!doc)
|
|
return;
|
|
nsCOMPtr<nsILineBreaker> lb;
|
|
doc->GetLineBreaker(getter_AddRefs(lb));
|
|
nsTextTransformer tx(lb, nsnull, 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;
|
|
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 {
|
|
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");
|
|
}
|
|
|
|
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(aTextStyle.mColor->mColor);
|
|
aRenderingContext.DrawString(text, PRUint32(textLength), dx, dy);
|
|
PaintTextDecorations(aRenderingContext, aStyleContext, aTextStyle,
|
|
dx, dy, width);
|
|
}
|
|
else {
|
|
SelectionDetails *details;
|
|
nsCOMPtr<nsIFrameSelection> frameSelection;
|
|
//get the frame selection
|
|
frameSelection = do_QueryInterface(selCon); //this MAY implement
|
|
if (!frameSelection)//if that failed get it from the presshell
|
|
rv = shell->GetFrameSelection(getter_AddRefs(frameSelection));
|
|
if (NS_SUCCEEDED(rv) && frameSelection){
|
|
nsCOMPtr<nsIContent> content;
|
|
PRInt32 offset;
|
|
PRInt32 length;
|
|
|
|
rv = GetContentAndOffsetsForSelection(aPresContext, getter_AddRefs(content),&offset,&length);
|
|
if (NS_SUCCEEDED(rv)){
|
|
rv = frameSelection->LookUpSelection(content, mContentOffset,
|
|
mContentLength , &details, 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;
|
|
}
|
|
DrawSelectionIterator iter(details,(PRUnichar *)text,(PRUint32)textLength,aTextStyle, selectionValue);//ITS OK TO CAST HERE THE RESULT WE USE WILLNOT DO BAD CONVERSION
|
|
if (!iter.IsDone() && iter.First())
|
|
{
|
|
nscoord currentX = dx;
|
|
nscoord newWidth;//temp
|
|
while (!iter.IsDone())
|
|
{
|
|
char *currenttext = iter.CurrentTextCStrPtr();
|
|
PRUint32 currentlength= iter.CurrentLength();
|
|
//TextStyle ¤tStyle = iter.CurrentStyle();
|
|
nscolor currentFGColor = iter.CurrentForeGroundColor();
|
|
nscolor currentBKColor;
|
|
|
|
if (NS_SUCCEEDED(aRenderingContext.GetWidth(currenttext, currentlength,newWidth)))//ADJUST FOR CHAR SPACING
|
|
{
|
|
if (iter.CurrentBackGroundColor(currentBKColor))
|
|
{//DRAW RECT HERE!!!
|
|
aRenderingContext.SetColor(currentBKColor);
|
|
aRenderingContext.FillRect(currentX, dy, newWidth, mRect.height);
|
|
currentFGColor = EnsureDifferentColors(currentFGColor, currentBKColor);
|
|
}
|
|
}
|
|
else
|
|
newWidth =0;
|
|
|
|
aRenderingContext.SetColor(currentFGColor);
|
|
aRenderingContext.DrawString(currenttext, currentlength, currentX, dy);
|
|
|
|
currentX+=newWidth;//increment twips X start
|
|
|
|
iter.Next();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
aRenderingContext.SetColor(aTextStyle.mColor->mColor);
|
|
aRenderingContext.DrawString(text, PRUint32(textLength), dx, dy);
|
|
}
|
|
PaintTextDecorations(aRenderingContext, aStyleContext,
|
|
aTextStyle, dx, dy, width,
|
|
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;
|
|
}
|
|
}
|
|
|
|
//---------------------------------------------------
|
|
// Uses a binary search for find where the cursor falls in the line of text
|
|
// It also keeps track of the part of the string that has already been measured
|
|
// so it doesn't have to keep measuring the same text over and over
|
|
//
|
|
// Param "aBaseWidth" contains the width in twips of the portion
|
|
// of the text that has already been measured, and aBaseInx contains
|
|
// the index of the text that has already been measured.
|
|
//
|
|
// aTextWidth returns the (in twips) the length of the text that falls before the cursor
|
|
// aIndex contains the index of the text where the cursor falls
|
|
static PRBool
|
|
BinarySearchForPosition(nsIRenderingContext* acx,
|
|
PRUnichar* aText,
|
|
PRInt32 aBaseWidth,
|
|
PRInt32 aBaseInx,
|
|
PRInt32 aStartInx,
|
|
PRInt32 aEndInx,
|
|
PRInt32 aCursorPos,
|
|
PRInt32& aIndex,
|
|
PRInt32& aTextWidth)
|
|
{
|
|
PRInt32 range = aEndInx - aStartInx;
|
|
if (range == 1) {
|
|
aIndex = aStartInx + aBaseInx;
|
|
acx->GetWidth(aText, aIndex, aTextWidth);
|
|
return PR_TRUE;
|
|
}
|
|
PRInt32 inx = aStartInx + (range / 2);
|
|
|
|
PRInt32 textWidth = 0;
|
|
acx->GetWidth(aText, inx, textWidth);
|
|
|
|
PRInt32 fullWidth = aBaseWidth + textWidth;
|
|
if (fullWidth == aCursorPos) {
|
|
aTextWidth = textWidth;
|
|
aIndex = inx;
|
|
return PR_TRUE;
|
|
} else if (aCursorPos < fullWidth) {
|
|
aTextWidth = aBaseWidth;
|
|
if (BinarySearchForPosition(acx, aText, aBaseWidth, aBaseInx, aStartInx, inx, aCursorPos, aIndex, aTextWidth)) {
|
|
return PR_TRUE;
|
|
}
|
|
} else {
|
|
aTextWidth = fullWidth;
|
|
if (BinarySearchForPosition(acx, aText, aBaseWidth, aBaseInx, inx, aEndInx, aCursorPos, aIndex, aTextWidth)) {
|
|
return PR_TRUE;
|
|
}
|
|
}
|
|
return PR_FALSE;
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
// Uses a binary search to find the position of the cursor in the text.
|
|
// The "indices array is used to map from the compressed text back to the
|
|
// un-compressed text, selection is based on the un-compressed text, the visual
|
|
// display of selection is based on the compressed text.
|
|
//---------------------------------------------------------------------------
|
|
NS_IMETHODIMP
|
|
nsTextFrame::GetPosition(nsIPresContext* aCX,
|
|
const nsPoint& aPoint,
|
|
nsIContent ** aNewContent,
|
|
PRInt32& aContentOffset,
|
|
PRInt32& aContentOffsetEnd)
|
|
|
|
{
|
|
// pre-condition tests
|
|
NS_PRECONDITION(aCX && aNewContent, "null arg");
|
|
if (!aCX || !aNewContent) {
|
|
return NS_ERROR_NULL_POINTER;
|
|
}
|
|
// initialize out param
|
|
*aNewContent = nsnull;
|
|
|
|
nsCOMPtr<nsIPresShell> shell;
|
|
nsresult rv = aCX->GetShell(getter_AddRefs(shell));
|
|
if (NS_SUCCEEDED(rv) && shell) {
|
|
nsCOMPtr<nsIRenderingContext> acx;
|
|
rv = shell->CreateRenderingContext(this, getter_AddRefs(acx));
|
|
if (NS_SUCCEEDED(rv)) {
|
|
TextStyle ts(aCX, *acx, mStyleContext);
|
|
if (ts.mSmallCaps || ts.mWordSpacing || ts.mLetterSpacing || ts.mJustifying) {
|
|
nsresult result = GetPositionSlowly(aCX, acx, 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
|
|
nsIStyleContext* styleContext;
|
|
GetStyleContext(&styleContext);
|
|
const nsStyleFont *font = (const nsStyleFont*)
|
|
styleContext->GetStyleData(eStyleStruct_Font);
|
|
NS_RELEASE(styleContext);
|
|
nsCOMPtr<nsIFontMetrics> fm;
|
|
aCX->GetMetricsFor(font->mFont, getter_AddRefs(fm));
|
|
acx->SetFont(fm);
|
|
|
|
// Get the renderable form of the text
|
|
nsCOMPtr<nsIDocument> doc(getter_AddRefs(GetDocument(aCX)));
|
|
nsCOMPtr<nsILineBreaker> lb;
|
|
doc->GetLineBreaker(getter_AddRefs(lb));
|
|
nsTextTransformer tx(lb, nsnull, aCX);
|
|
PRInt32 textLength;
|
|
// no need to worry about justification, that's always on the slow path
|
|
PrepareUnicodeText(tx, &indexBuffer, &paintBuffer, &textLength);
|
|
|
|
if (textLength <=0) {
|
|
//invalid frame to get position on
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
nsPoint origin;
|
|
nsIView * view;
|
|
GetOffsetFromView(aCX, origin, &view);
|
|
|
|
//IF STYLE SAYS TO SELECT TO END OF FRAME HERE...
|
|
nsCOMPtr<nsIPref> prefs( do_GetService(kPrefCID, &rv) );
|
|
PRInt32 prefInt = 0;
|
|
PRBool outofstylehandled = PR_FALSE;
|
|
if (NS_SUCCEEDED(rv) && prefs)
|
|
{
|
|
if (NS_SUCCEEDED(prefs->GetIntPref("browser.drag_out_of_frame_style", &prefInt)) && prefInt)
|
|
{
|
|
if ((aPoint.y - origin.y) < 0)//above rectangle
|
|
{
|
|
aContentOffset = mContentOffset;
|
|
aContentOffsetEnd = aContentOffset;
|
|
outofstylehandled = PR_TRUE;
|
|
}
|
|
else if ((aPoint.y - origin.y) > mRect.height)
|
|
{
|
|
aContentOffset = mContentOffset + mContentLength;
|
|
aContentOffsetEnd = aContentOffset;
|
|
outofstylehandled = PR_TRUE;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!outofstylehandled) //then we need to track based on the X coord only
|
|
{
|
|
//END STYLE IF
|
|
PRInt32* ip = indexBuffer.mBuffer;
|
|
|
|
PRInt32 indx;
|
|
PRInt32 textWidth = 0;
|
|
PRUnichar* text = paintBuffer.mBuffer;
|
|
PRBool found = BinarySearchForPosition(acx, text, origin.x, 0, 0,
|
|
PRInt32(textLength),
|
|
PRInt32(aPoint.x) , //go to local coordinates
|
|
indx, textWidth);
|
|
if (found) {
|
|
PRInt32 charWidth;
|
|
acx->GetWidth(text[indx], charWidth);
|
|
charWidth /= 2;
|
|
|
|
if ((aPoint.x - origin.x) > textWidth+charWidth) {
|
|
indx++;
|
|
}
|
|
}
|
|
|
|
aContentOffset = indx + mContentOffset;
|
|
//reusing wordBufMem
|
|
PRInt32 i;
|
|
for (i = 0;i <= mContentLength; i ++){
|
|
if (ip[i] >= aContentOffset){ //reverse mapping
|
|
aContentOffset = i + mContentOffset;
|
|
break;
|
|
}
|
|
}
|
|
aContentOffsetEnd = aContentOffset;
|
|
NS_ASSERTION(i<= mContentLength, "offset we got from binary search is messed up");
|
|
}
|
|
*aNewContent = mContent;
|
|
if (*aNewContent) {
|
|
(*aNewContent)->AddRef();
|
|
}
|
|
}
|
|
}
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsTextFrame::GetContentAndOffsetsFromPoint(nsIPresContext* aCX,
|
|
const nsPoint& aPoint,
|
|
nsIContent ** aNewContent,
|
|
PRInt32& aContentOffset,
|
|
PRInt32& aContentOffsetEnd,
|
|
PRBool& aBeginFrameContent)
|
|
{
|
|
nsPoint newPoint;
|
|
newPoint.y = aPoint.y;
|
|
if (aPoint.x < 0)
|
|
newPoint.x = 0;
|
|
else
|
|
newPoint.x = aPoint.x;
|
|
|
|
nsresult rv = GetPosition(aCX, newPoint, aNewContent, aContentOffset, aContentOffsetEnd);
|
|
if (aContentOffset == mContentOffset)
|
|
aBeginFrameContent = PR_TRUE;
|
|
else
|
|
aBeginFrameContent = PR_FALSE;
|
|
return rv;
|
|
}
|
|
|
|
|
|
// [HACK] Foward Declarations
|
|
void ForceDrawFrame(nsFrame * aFrame);
|
|
|
|
//null range means the whole thing
|
|
NS_IMETHODIMP
|
|
nsTextFrame::SetSelected(nsIPresContext* aPresContext,
|
|
nsIDOMRange *aRange,
|
|
PRBool aSelected,
|
|
nsSpread aSpread)
|
|
{
|
|
nsresult result;
|
|
if (aSelected && ParentDisablesSelection())
|
|
return NS_OK;
|
|
|
|
nsFrameState frameState;
|
|
GetFrameState(&frameState);
|
|
#if 0
|
|
PRBool isSelected = ((frameState & 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;
|
|
PRBool wholeContentFound = PR_FALSE;//if the entire content we look at is selected.
|
|
if (aRange) {
|
|
//lets see if the range contains us, if so we must redraw!
|
|
nsCOMPtr<nsIDOMNode> endNode;
|
|
PRInt32 endOffset;
|
|
nsCOMPtr<nsIDOMNode> startNode;
|
|
PRInt32 startOffset;
|
|
aRange->GetEndContainer(getter_AddRefs(endNode));
|
|
aRange->GetEndOffset(&endOffset);
|
|
aRange->GetStartContainer(getter_AddRefs(startNode));
|
|
aRange->GetStartOffset(&startOffset);
|
|
nsCOMPtr<nsIContent> content;
|
|
result = GetContent(getter_AddRefs(content));
|
|
nsCOMPtr<nsIDOMNode> thisNode;
|
|
thisNode = do_QueryInterface(content);
|
|
|
|
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;
|
|
wholeContentFound = PR_TRUE;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
found = PR_TRUE;
|
|
}
|
|
}
|
|
else {
|
|
if ( aSelected != (PRBool)(frameState | NS_FRAME_SELECTED_CONTENT) ){
|
|
found = PR_TRUE;
|
|
}
|
|
}
|
|
|
|
if ( aSelected )
|
|
frameState |= NS_FRAME_SELECTED_CONTENT;
|
|
else
|
|
{//we need to see if any other selection available.
|
|
SelectionDetails *details = nsnull;
|
|
nsCOMPtr<nsIPresShell> shell;
|
|
nsCOMPtr<nsIFrameSelection> frameSelection;
|
|
|
|
nsresult rv = aPresContext->GetShell(getter_AddRefs(shell));
|
|
if (NS_SUCCEEDED(rv) && shell){
|
|
nsCOMPtr<nsISelectionController> selCon;
|
|
rv = GetSelectionController(aPresContext, getter_AddRefs(selCon));
|
|
if (NS_SUCCEEDED(rv) && selCon)
|
|
{
|
|
frameSelection = do_QueryInterface(selCon); //this MAY implement
|
|
}
|
|
if (!frameSelection)
|
|
rv = shell->GetFrameSelection(getter_AddRefs(frameSelection));
|
|
if (NS_SUCCEEDED(rv) && frameSelection){
|
|
nsCOMPtr<nsIContent> content;
|
|
PRInt32 offset;
|
|
PRInt32 length;
|
|
|
|
rv = GetContentAndOffsetsForSelection(aPresContext, getter_AddRefs(content),&offset,&length);
|
|
if (NS_SUCCEEDED(rv) && content){
|
|
rv = frameSelection->LookUpSelection(content, offset,
|
|
length , &details, PR_TRUE);
|
|
// PR_TRUE last param used here! we need to see if we are still selected. so no shortcut
|
|
|
|
}
|
|
}
|
|
}
|
|
if (!details)
|
|
frameState &= ~NS_FRAME_SELECTED_CONTENT;
|
|
else
|
|
{
|
|
SelectionDetails *sdptr = details;
|
|
while ((sdptr = details->mNext) != nsnull) {
|
|
delete details;
|
|
details = sdptr;
|
|
}
|
|
delete details;
|
|
}
|
|
}
|
|
SetFrameState(frameState);
|
|
if (found){ //if range contains this frame...
|
|
nsRect frameRect;
|
|
GetRect(frameRect);
|
|
nsRect rect(0, 0, frameRect.width, frameRect.height);
|
|
Invalidate(aPresContext, rect, PR_FALSE);
|
|
// ForceDrawFrame(this);
|
|
}
|
|
if (aSpread == eSpreadDown)
|
|
{
|
|
nsIFrame *frame;
|
|
GetPrevInFlow(&frame);
|
|
while(frame){
|
|
frame->SetSelected(aPresContext, aRange,aSelected,eSpreadNone);
|
|
result = frame->GetPrevInFlow(&frame);
|
|
if (NS_FAILED(result))
|
|
break;
|
|
}
|
|
GetNextInFlow(&frame);
|
|
while (frame){
|
|
frame->SetSelected(aPresContext, aRange,aSelected,eSpreadNone);
|
|
result = frame->GetNextInFlow(&frame);
|
|
if (NS_FAILED(result))
|
|
break;
|
|
}
|
|
}
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsTextFrame::GetPointFromOffset(nsIPresContext* aPresContext,
|
|
nsIRenderingContext* inRendContext,
|
|
PRInt32 inOffset,
|
|
nsPoint* outPoint)
|
|
{
|
|
if (!aPresContext || !inRendContext || !outPoint)
|
|
return NS_ERROR_NULL_POINTER;
|
|
|
|
if (mContentLength <= 0) {
|
|
outPoint->x = 0;
|
|
outPoint->y = 0;
|
|
return NS_OK;
|
|
}
|
|
|
|
inOffset-=mContentOffset;
|
|
if (inOffset < 0){
|
|
NS_ASSERTION(0,"offset less than this frame has in GetPointFromOffset");
|
|
inOffset = 0;
|
|
}
|
|
if (inOffset >= mContentLength)
|
|
inOffset = mContentLength;
|
|
TextStyle ts(aPresContext, *inRendContext, mStyleContext);
|
|
|
|
// 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
|
|
nsCOMPtr<nsIDocument> doc(getter_AddRefs(GetDocument(aPresContext)));
|
|
nsCOMPtr<nsILineBreaker> lb;
|
|
doc->GetLineBreaker(getter_AddRefs(lb));
|
|
nsTextTransformer tx(lb, nsnull, aPresContext);
|
|
PRInt32 textLength;
|
|
PRInt32 numSpaces;
|
|
|
|
numSpaces = PrepareUnicodeText(tx, &indexBuffer, &paintBuffer, &textLength);
|
|
|
|
ComputeExtraJustificationSpacing(*inRendContext, ts, paintBuffer.mBuffer, textLength, numSpaces);
|
|
|
|
|
|
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
|
|
{
|
|
if (ts.mSmallCaps || (0 != ts.mWordSpacing) || (0 != ts.mLetterSpacing) || ts.mJustifying)
|
|
{
|
|
GetWidth(*inRendContext, ts,
|
|
paintBuffer.mBuffer, ip[inOffset]-mContentOffset,
|
|
&width);
|
|
}
|
|
else
|
|
{
|
|
if (inOffset >=0)
|
|
inRendContext->GetWidth(paintBuffer.mBuffer, ip[inOffset]-mContentOffset,width);
|
|
}
|
|
if (inOffset > textLength && (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 spacing!!
|
|
width += ts.mSpaceWidth + ts.mWordSpacing;
|
|
}
|
|
}
|
|
outPoint->x = width;
|
|
outPoint->y = 0;
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
|
|
|
|
NS_IMETHODIMP
|
|
nsTextFrame::GetChildFrameContainingOffset(PRInt32 inContentOffset,
|
|
PRBool inHint,
|
|
PRInt32* outFrameContentOffset,
|
|
nsIFrame **outChildFrame)
|
|
{
|
|
if (nsnull == outChildFrame)
|
|
return NS_ERROR_NULL_POINTER;
|
|
nsresult result;
|
|
PRInt32 contentOffset = inContentOffset;
|
|
|
|
if (contentOffset != -1) //-1 signified the end of the current content
|
|
contentOffset = inContentOffset - mContentOffset;
|
|
|
|
if ((contentOffset > mContentLength) || ((contentOffset == mContentLength) && inHint) )
|
|
{
|
|
//this is not the frame we are looking for.
|
|
nsIFrame *nextInFlow;
|
|
GetNextInFlow(&nextInFlow);
|
|
if (nextInFlow)
|
|
{
|
|
return nextInFlow->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 floaters!
|
|
{
|
|
result = GetPrevInFlow(outChildFrame);
|
|
if (NS_SUCCEEDED(result) && outChildFrame)
|
|
return (*outChildFrame)->GetChildFrameContainingOffset(inContentOffset, inHint,
|
|
outFrameContentOffset,outChildFrame);
|
|
else
|
|
return result;
|
|
}
|
|
|
|
*outFrameContentOffset = contentOffset;
|
|
*outChildFrame = this;
|
|
return NS_OK;
|
|
}
|
|
|
|
|
|
NS_IMETHODIMP
|
|
nsTextFrame::PeekOffset(nsIPresContext* aPresContext, nsPeekOffsetStruct *aPos)
|
|
{
|
|
|
|
if (!aPos || !mContent)
|
|
return NS_ERROR_NULL_POINTER;
|
|
if (aPos->mStartOffset < 0 )
|
|
aPos->mStartOffset = mContentLength + mContentOffset;
|
|
if (aPos->mStartOffset < mContentOffset){
|
|
aPos->mStartOffset = mContentOffset;
|
|
}
|
|
if (aPos->mStartOffset > (mContentOffset + mContentLength)){
|
|
nsIFrame *nextInFlow;
|
|
GetNextInFlow(&nextInFlow);
|
|
if (!nextInFlow){
|
|
NS_ASSERTION(PR_FALSE,"nsTextFrame::PeekOffset no more flow \n");
|
|
return NS_ERROR_INVALID_ARG;
|
|
}
|
|
return nextInFlow->PeekOffset(aPresContext, aPos);
|
|
}
|
|
|
|
if (aPos->mAmount == eSelectLine || aPos->mAmount == eSelectBeginLine
|
|
|| aPos->mAmount == eSelectEndLine || aPos->mAmount == eSelectParagraph)
|
|
{
|
|
return nsFrame::PeekOffset(aPresContext, aPos);
|
|
}
|
|
|
|
nsAutoTextBuffer paintBuffer;
|
|
nsAutoIndexBuffer indexBuffer;
|
|
nsresult rv = indexBuffer.GrowTo(mContentLength + 1);
|
|
if (NS_FAILED(rv)) {
|
|
return rv;
|
|
}
|
|
PRInt32* ip = indexBuffer.mBuffer;
|
|
|
|
PRInt32 textLength;
|
|
nsresult result(NS_ERROR_FAILURE);
|
|
aPos->mResultContent = mContent;//do this right off
|
|
switch (aPos->mAmount){
|
|
case eSelectNoAmount:
|
|
{
|
|
// Transform text from content into renderable form
|
|
nsIDocument* doc;
|
|
result = mContent->GetDocument(doc);
|
|
if (NS_FAILED(result) || !doc) {
|
|
return result;
|
|
}
|
|
nsCOMPtr<nsILineBreaker> lb;
|
|
doc->GetLineBreaker(getter_AddRefs(lb));
|
|
NS_RELEASE(doc);
|
|
|
|
nsTextTransformer tx(lb, nsnull, aPresContext);
|
|
PrepareUnicodeText(tx, &indexBuffer, &paintBuffer, &textLength);
|
|
|
|
if (textLength)//if no renderable length, you cant park here.
|
|
{
|
|
aPos->mContentOffset = aPos->mStartOffset;
|
|
result = NS_OK;
|
|
}
|
|
else
|
|
{
|
|
aPos->mAmount = eSelectDir;//go to "next" or previous frame based on direction not THIS frame
|
|
result = GetFrameFromDirection(aPresContext, aPos);
|
|
if (NS_SUCCEEDED(result) && aPos->mResultFrame && aPos->mResultFrame!= this)
|
|
return aPos->mResultFrame->PeekOffset(aPresContext, aPos);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case eSelectCharacter:
|
|
{
|
|
// Transform text from content into renderable form
|
|
nsIDocument* doc;
|
|
result = mContent->GetDocument(doc);
|
|
if (NS_FAILED(result) || !doc) {
|
|
return result;
|
|
}
|
|
nsCOMPtr<nsILineBreaker> lb;
|
|
doc->GetLineBreaker(getter_AddRefs(lb));
|
|
NS_RELEASE(doc);
|
|
|
|
nsTextTransformer tx(lb, nsnull, aPresContext);
|
|
PrepareUnicodeText(tx, &indexBuffer, &paintBuffer, &textLength);
|
|
|
|
nsIFrame *frameUsed = nsnull;
|
|
PRInt32 start;
|
|
PRBool found = PR_TRUE;
|
|
if (aPos->mDirection == eDirPrevious){
|
|
aPos->mContentOffset = 0;
|
|
PRInt32 i;
|
|
for (i = aPos->mStartOffset -1 - mContentOffset; i >=0; i--){
|
|
if (ip[i] < ip[aPos->mStartOffset - mContentOffset]){
|
|
aPos->mContentOffset = i + mContentOffset;
|
|
break;
|
|
}
|
|
}
|
|
if (i <0){
|
|
found = PR_FALSE;
|
|
GetPrevInFlow(&frameUsed);
|
|
start = mContentOffset;
|
|
aPos->mContentOffset = start;//in case next call fails we stop at this offset
|
|
}
|
|
}
|
|
else if (aPos->mDirection == eDirNext){
|
|
PRInt32 i;
|
|
aPos->mContentOffset = mContentLength;
|
|
for (i = aPos->mStartOffset +1 - mContentOffset; i <= mContentLength; i++){
|
|
if (ip[i] > ip[aPos->mStartOffset - mContentOffset]){
|
|
aPos->mContentOffset = i + mContentOffset;
|
|
break;
|
|
}
|
|
}
|
|
/* if (aStartOffset == 0 && (mState & TEXT_SKIP_LEADING_WS))
|
|
i--; //back up because we just skipped over some white space. why skip over the char also?
|
|
*/
|
|
if (i > mContentLength){
|
|
found = PR_FALSE;
|
|
GetNextInFlow(&frameUsed);
|
|
start = mContentOffset + mContentLength;
|
|
aPos->mContentOffset = start;//in case next call fails we stop at this offset
|
|
}
|
|
}
|
|
|
|
if (!found)
|
|
{
|
|
result = GetFrameFromDirection(aPresContext, aPos);
|
|
if (NS_SUCCEEDED(result) && aPos->mResultFrame && aPos->mResultFrame!= this)
|
|
result = aPos->mResultFrame->PeekOffset(aPresContext, aPos);
|
|
}
|
|
else
|
|
aPos->mResultContent = mContent;
|
|
}
|
|
break;
|
|
|
|
case eSelectWord:
|
|
{
|
|
// Transform text from content into renderable form
|
|
nsIDocument* doc;
|
|
result = mContent->GetDocument(doc);
|
|
if (NS_FAILED(result) || !doc) {
|
|
return result;
|
|
}
|
|
|
|
nsCOMPtr<nsILineBreaker> lb;
|
|
doc->GetLineBreaker(getter_AddRefs(lb));
|
|
nsCOMPtr<nsIWordBreaker> wb;
|
|
doc->GetWordBreaker(getter_AddRefs(wb));
|
|
NS_RELEASE(doc);
|
|
|
|
nsTextTransformer tx(lb, wb, aPresContext);
|
|
|
|
PrepareUnicodeText(tx, &indexBuffer, &paintBuffer, &textLength);
|
|
nsIFrame *frameUsed = nsnull;
|
|
PRBool keepSearching; //if you run out of chars before you hit the end of word, maybe next frame has more text to select?
|
|
PRInt32 start;
|
|
PRBool found = PR_FALSE;
|
|
PRBool isWhitespace, wasTransformed;
|
|
PRInt32 wordLen, contentLen;
|
|
if (aPos->mDirection == eDirPrevious){
|
|
keepSearching = PR_TRUE;
|
|
tx.Init(this, mContent, aPos->mStartOffset);
|
|
aPos->mContentOffset = mContentOffset;//initialize
|
|
if (tx.GetPrevWord(PR_FALSE, &wordLen, &contentLen, &isWhitespace,
|
|
PR_FALSE) &&
|
|
(aPos->mStartOffset - contentLen >= mContentOffset) ){
|
|
if ((aPos->mEatingWS && !isWhitespace) || !aPos->mEatingWS){
|
|
aPos->mContentOffset = aPos->mStartOffset - contentLen;
|
|
//check for whitespace next.
|
|
if (isWhitespace && aPos->mContentOffset <= mContentOffset)
|
|
{
|
|
keepSearching = PR_FALSE;//reached the beginning of a word
|
|
aPos->mEatingWS = PR_FALSE;//if no real word then
|
|
}
|
|
else{
|
|
while (isWhitespace &&
|
|
tx.GetPrevWord(PR_FALSE, &wordLen, &contentLen,
|
|
&isWhitespace, PR_FALSE)){
|
|
aPos->mContentOffset -= contentLen;
|
|
aPos->mEatingWS = PR_TRUE;
|
|
}
|
|
aPos->mEatingWS = !isWhitespace;//nowhite space, just eat chars.
|
|
keepSearching = aPos->mContentOffset <= mContentOffset;
|
|
if (!isWhitespace){
|
|
if (!keepSearching)
|
|
found = PR_TRUE;
|
|
else
|
|
aPos->mEatingWS = PR_TRUE;
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
aPos->mContentOffset = mContentLength + mContentOffset;
|
|
found = PR_TRUE;
|
|
}
|
|
}
|
|
}
|
|
else if (aPos->mDirection == eDirNext) {
|
|
tx.Init(this, mContent, aPos->mStartOffset );
|
|
aPos->mContentOffset = mContentOffset + mContentLength;//initialize
|
|
|
|
if (tx.GetNextWord(PR_FALSE, &wordLen, &contentLen, &isWhitespace, &wasTransformed, PR_TRUE, PR_FALSE) &&
|
|
(aPos->mStartOffset + contentLen <= (mContentLength + mContentOffset))){
|
|
|
|
if ((aPos->mEatingWS && isWhitespace) || !aPos->mEatingWS){
|
|
aPos->mContentOffset = aPos->mStartOffset + contentLen;
|
|
//check for whitespace next.
|
|
keepSearching = PR_TRUE;
|
|
aPos->mEatingWS = PR_TRUE;
|
|
if (!isWhitespace){
|
|
while (tx.GetNextWord(PR_FALSE, &wordLen, &contentLen, &isWhitespace, &wasTransformed, PR_TRUE, PR_FALSE))
|
|
{
|
|
if (aPos->mStartOffset + contentLen > (mContentLength + mContentOffset))
|
|
goto TryNextFrame;
|
|
if (isWhitespace)
|
|
aPos->mContentOffset += contentLen;
|
|
else
|
|
break;
|
|
}
|
|
keepSearching = PR_FALSE;
|
|
found = PR_TRUE;
|
|
}
|
|
else //we just need to jump the space, done here
|
|
{
|
|
while(tx.GetNextWord(PR_FALSE, &wordLen, &contentLen, &isWhitespace, &wasTransformed, PR_TRUE, PR_FALSE))
|
|
{
|
|
if (aPos->mStartOffset + contentLen > (mContentLength + mContentOffset))
|
|
goto TryNextFrame;
|
|
|
|
if (isWhitespace)
|
|
aPos->mContentOffset += contentLen;
|
|
else
|
|
break;
|
|
}
|
|
keepSearching = PR_FALSE;
|
|
found = PR_TRUE;
|
|
}
|
|
}
|
|
else if (aPos->mEatingWS)
|
|
{
|
|
aPos->mContentOffset = mContentOffset;
|
|
found = PR_TRUE;
|
|
}
|
|
if (!isWhitespace){
|
|
aPos->mEatingWS = PR_FALSE;
|
|
}
|
|
else if (!keepSearching) //we have found the "whole" word so just looking for WS
|
|
aPos->mEatingWS = PR_TRUE;
|
|
}
|
|
|
|
TryNextFrame:
|
|
GetNextInFlow(&frameUsed);
|
|
start = 0;
|
|
}
|
|
|
|
if (!found || (aPos->mContentOffset > (mContentOffset + mContentLength)) || (aPos->mContentOffset < mContentOffset))
|
|
{
|
|
aPos->mContentOffset = PR_MIN(aPos->mContentOffset, mContentOffset + mContentLength);
|
|
aPos->mContentOffset = PR_MAX(aPos->mContentOffset, mContentOffset);
|
|
result = GetFrameFromDirection(aPresContext, aPos);
|
|
if (NS_SUCCEEDED(result) && aPos->mResultFrame && aPos->mResultFrame!= this)
|
|
{
|
|
if (NS_SUCCEEDED(result = aPos->mResultFrame->PeekOffset(aPresContext, aPos)))
|
|
return NS_OK;//else fall through
|
|
else if (aPos->mDirection == eDirNext)
|
|
aPos->mContentOffset = mContentOffset + mContentLength;
|
|
else
|
|
aPos->mContentOffset = mContentOffset;
|
|
}
|
|
else
|
|
aPos->mResultContent = mContent;
|
|
}
|
|
else
|
|
{
|
|
aPos->mResultContent = mContent;
|
|
}
|
|
}
|
|
break;
|
|
default:
|
|
result = NS_ERROR_FAILURE; break;
|
|
}
|
|
|
|
aPos->mContentOffsetEnd = aPos->mContentOffset;
|
|
|
|
if (NS_FAILED(result)){
|
|
aPos->mResultContent = mContent;
|
|
//aPos->mContentOffset = aPos->mStartOffset;
|
|
result = NS_OK;
|
|
}
|
|
aPos->mResultFrame = this;
|
|
|
|
return result;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsTextFrame::HandleMultiplePress(nsIPresContext* aPresContext,
|
|
nsGUIEvent* aEvent,
|
|
nsEventStatus* aEventStatus)
|
|
{
|
|
if (DisplaySelection(aPresContext) == nsISelectionController::SELECTION_OFF) {
|
|
return NS_OK;
|
|
}
|
|
|
|
nsMouseEvent *me = (nsMouseEvent *)aEvent;
|
|
if (!me) return NS_OK;
|
|
|
|
// Triple- and greater click counts are handled by nsFrame.
|
|
if (me->clickCount > 2)
|
|
return nsFrame::HandleMultiplePress(aPresContext, aEvent, aEventStatus);
|
|
|
|
// Double-click: word selection, handled here:
|
|
PRInt32 startPos = 0;
|
|
PRInt32 contentOffsetEnd = 0;
|
|
nsCOMPtr<nsIContent> newContent;
|
|
nsresult rv = GetPosition(aPresContext, aEvent->point,
|
|
getter_AddRefs(newContent), startPos,
|
|
contentOffsetEnd);
|
|
if (NS_FAILED(rv))
|
|
return rv;
|
|
|
|
return PeekBackwardAndForward(eSelectWord, eSelectWord, startPos,
|
|
aPresContext, PR_FALSE);
|
|
}
|
|
|
|
|
|
NS_IMETHODIMP
|
|
nsTextFrame::CheckVisibility(nsIPresContext* 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
|
|
nsCOMPtr<nsIPresShell> shell;
|
|
rv = aContext->GetShell(getter_AddRefs(shell));
|
|
if (NS_FAILED(rv) || !shell)
|
|
return rv?rv:NS_ERROR_FAILURE;
|
|
//get the document
|
|
nsCOMPtr<nsIDocument> doc;
|
|
rv = shell->GetDocument(getter_AddRefs(doc));
|
|
if (!doc)
|
|
return rv?rv:NS_ERROR_FAILURE;
|
|
//get the linebreaker
|
|
nsCOMPtr<nsILineBreaker> lb;
|
|
doc->GetLineBreaker(getter_AddRefs(lb));
|
|
//create texttransformer
|
|
nsTextTransformer tx(lb, nsnull, 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
|
|
{
|
|
rv = nextInFlow->GetNextInFlow(&nextInFlow);
|
|
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");
|
|
|
|
mTotalNumChars += aNumChars;
|
|
mBreaks[mNumSegments] = mTotalNumChars;
|
|
mSegments[mNumSegments].mIsWhitespace = aIsWhitespace;
|
|
mTotalContentLen += aContentLen;
|
|
mSegments[mNumSegments].mContentLen = PRUint32(mTotalContentLen);
|
|
mNumSegments++;
|
|
}
|
|
};
|
|
|
|
// Transforms characters in place from ascii to Unicode
|
|
static void
|
|
TransformTextToUnicode(char* aText, PRInt32 aNumChars)
|
|
{
|
|
// Go backwards over the characters and convert them.
|
|
unsigned char* cp1 = (unsigned char*)aText + aNumChars - 1;
|
|
PRUnichar* cp2 = (PRUnichar*)aText + (aNumChars - 1);
|
|
|
|
while (aNumChars-- > 0) {
|
|
*cp2-- = PRUnichar(*cp1--);
|
|
}
|
|
}
|
|
|
|
nsReflowStatus
|
|
nsTextFrame::MeasureText(nsIPresContext* aPresContext,
|
|
const nsHTMLReflowState& aReflowState,
|
|
nsTextTransformer& aTx,
|
|
nsILineBreaker* aLb,
|
|
TextStyle& aTs,
|
|
TextReflowData& aTextData)
|
|
{
|
|
PRBool firstThing = PR_TRUE;
|
|
nscoord maxWidth = aReflowState.availableWidth;
|
|
nsLineLayout& lineLayout = *aReflowState.mLineLayout;
|
|
PRInt32 contentLength = aTx.GetContentLength();
|
|
PRInt32 startingOffset = aTextData.mOffset;
|
|
PRInt32 prevOffset = -1;
|
|
PRInt32 column = mColumn;
|
|
PRInt32 prevColumn = column;
|
|
nscoord prevMaxWordWidth = 0;
|
|
PRInt32 lastWordLen = 0;
|
|
PRInt32 lastWordWidth = 0;
|
|
PRUnichar* lastWordPtr = nsnull;
|
|
PRBool textStartsWithNBSP = PR_FALSE;
|
|
PRBool endsInWhitespace = PR_FALSE;
|
|
PRBool endsInNewline = PR_FALSE;
|
|
PRBool justDidFirstLetter = PR_FALSE;
|
|
#ifdef _WIN32
|
|
PRBool measureTextRuns = !aTextData.mComputeMaxWordWidth && !aTs.mPreformatted &&
|
|
!aTs.mSmallCaps && !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)
|
|
#else
|
|
PRBool measureTextRuns = PR_FALSE;
|
|
#endif
|
|
TextRun textRun;
|
|
PRInt32 estimatedNumChars;
|
|
|
|
// Estimate the number of characters that will fit. Use 105% of the available
|
|
// width divided by the average character width
|
|
estimatedNumChars = (maxWidth - aTextData.mX) / aTs.mAveCharWidth;
|
|
estimatedNumChars += estimatedNumChars / 20;
|
|
|
|
aTextData.mX = 0;
|
|
for (;;firstThing = PR_FALSE) {
|
|
// Get next word/whitespace from the text
|
|
PRBool isWhitespace, wasTransformed;
|
|
PRInt32 wordLen, contentLen;
|
|
union {
|
|
char* bp1;
|
|
PRUnichar* bp2;
|
|
};
|
|
bp2 = aTx.GetNextWord(aTextData.mInWord, &wordLen, &contentLen, &isWhitespace,
|
|
&wasTransformed, textRun.mNumSegments == 0);
|
|
// Remember if the text was transformed
|
|
if (wasTransformed) {
|
|
mState |= TEXT_WAS_TRANSFORMED;
|
|
}
|
|
if (nsnull == bp2) {
|
|
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
|
|
nscoord width;
|
|
PRUnichar firstChar;
|
|
if (aTx.TransformedTextIsAscii()) {
|
|
firstChar = *bp1;
|
|
} else {
|
|
firstChar = *bp2;
|
|
}
|
|
if (isWhitespace) {
|
|
if ('\n' == firstChar) {
|
|
// We hit a newline. Stop looping.
|
|
NS_WARN_IF_FALSE(aTs.mPreformatted, "newline w/o ts.mPreformatted");
|
|
prevOffset = aTextData.mOffset;
|
|
aTextData.mOffset++;
|
|
endsInWhitespace = PR_TRUE;
|
|
endsInNewline = PR_TRUE;
|
|
break;
|
|
}
|
|
if (aTextData.mSkipWhitespace) {
|
|
aTextData.mOffset += contentLen;
|
|
aTextData.mSkipWhitespace = PR_FALSE;
|
|
|
|
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;
|
|
}
|
|
|
|
// NOTE: Even if the textRun absorbs the whitespace below, we still
|
|
// want to remember that we're breakable.
|
|
aTextData.mIsBreakable = 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
|
|
width = (aTs.mSpaceWidth + aTs.mWordSpacing)*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
|
|
width = wordLen*(aTs.mWordSpacing + aTs.mSpaceWidth);// XXX simplistic
|
|
}
|
|
|
|
if (aTextData.mMeasureText) {
|
|
// See if there is room for the text
|
|
if ((0 != aTextData.mX) && aTextData.mWrapping && (aTextData.mX + width > maxWidth)) {
|
|
// The text will not fit.
|
|
break;
|
|
}
|
|
aTextData.mX += width;
|
|
}
|
|
prevColumn = column;
|
|
column += wordLen;
|
|
endsInWhitespace = PR_TRUE;
|
|
prevOffset = aTextData.mOffset;
|
|
aTextData.mOffset += contentLen;
|
|
} else {
|
|
// See if the first thing in the section of text is a
|
|
// non-breaking space (html nbsp entity). If it is then make
|
|
// note of that fact for the line layout logic.
|
|
if (aTextData.mWrapping && firstThing && (firstChar == ' ')) {
|
|
textStartsWithNBSP = PR_TRUE;
|
|
}
|
|
aTextData.mSkipWhitespace = PR_FALSE;
|
|
|
|
if (aTextData.mFirstLetterOK) {
|
|
// XXX need a lookup function here; plus look ahead using the
|
|
// text-runs
|
|
if ((firstChar == '\'') || (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, aTs, bp2, wordLen, &width);
|
|
}
|
|
else {
|
|
// Measure just the one word
|
|
if (aTx.TransformedTextIsAscii()) {
|
|
aReflowState.rendContext->GetWidth(bp1, wordLen, width);
|
|
} else {
|
|
aReflowState.rendContext->GetWidth(bp2, wordLen, width);
|
|
}
|
|
if (aTs.mLetterSpacing) {
|
|
width += aTs.mLetterSpacing * wordLen;
|
|
}
|
|
}
|
|
lastWordWidth = width;
|
|
|
|
// See if there is room for the text
|
|
if ((0 != aTextData.mX) && aTextData.mWrapping && (aTextData.mX + width > maxWidth)) {
|
|
// The text will not fit.
|
|
break;
|
|
}
|
|
aTextData.mX += width;
|
|
prevMaxWordWidth = aTextData.mMaxWordWidth;
|
|
if (width > aTextData.mMaxWordWidth) {
|
|
aTextData.mMaxWordWidth = width;
|
|
}
|
|
|
|
prevColumn = column;
|
|
column += wordLen;
|
|
endsInWhitespace = PR_FALSE;
|
|
prevOffset = aTextData.mOffset;
|
|
aTextData.mOffset += contentLen;
|
|
if (justDidFirstLetter) {
|
|
// Time to stop
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
// We didn't measure the text, but we need to update our state
|
|
prevColumn = column;
|
|
column += wordLen;
|
|
endsInWhitespace = PR_FALSE;
|
|
prevOffset = aTextData.mOffset;
|
|
aTextData.mOffset += contentLen;
|
|
if (justDidFirstLetter) {
|
|
// Time to stop
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
continue;
|
|
|
|
MeasureTextRun:
|
|
#ifdef _WIN32
|
|
PRInt32 numCharsFit;
|
|
// These calls can return numCharsFit not positioned at a break in the textRun. Beware.
|
|
if (aTx.TransformedTextIsAscii()) {
|
|
aReflowState.rendContext->GetWidth((char*)aTx.GetWordBuffer(), textRun.mTotalNumChars,
|
|
maxWidth - aTextData.mX,
|
|
textRun.mBreaks, textRun.mNumSegments,
|
|
width, numCharsFit);
|
|
} else {
|
|
aReflowState.rendContext->GetWidth(aTx.GetWordBuffer(), textRun.mTotalNumChars,
|
|
maxWidth - aTextData.mX,
|
|
textRun.mBreaks, textRun.mNumSegments,
|
|
width, numCharsFit);
|
|
}
|
|
// See how much of the text fit
|
|
if ((0 != aTextData.mX) && aTextData.mWrapping && (aTextData.mX + width > maxWidth)) {
|
|
// None of the text fits
|
|
break;
|
|
}
|
|
|
|
// Find the index of the last segment that fit
|
|
PRInt32 lastSegment;
|
|
if (numCharsFit == textRun.mTotalNumChars) { // fast path, normal case
|
|
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;
|
|
} else */
|
|
if (lastSegment == 0) {
|
|
// Only one segment fit
|
|
prevColumn = column;
|
|
prevOffset = aTextData.mOffset;
|
|
} else {
|
|
// The previous state is for the next to last word
|
|
prevColumn = textRun.mBreaks[lastSegment - 1];
|
|
prevOffset = textRun.mSegments[lastSegment - 1].ContentLen();
|
|
// NOTE: The textRun data are relative to the last updated column and offset!
|
|
prevColumn = column + textRun.mBreaks[lastSegment - 1];
|
|
prevOffset = aTextData.mOffset + textRun.mSegments[lastSegment - 1].ContentLen();
|
|
}
|
|
|
|
aTextData.mX += width;
|
|
column += numCharsFit;
|
|
aTextData.mOffset += textRun.mSegments[lastSegment].ContentLen();
|
|
endsInWhitespace = textRun.mSegments[lastSegment].IsWhitespace();
|
|
// Since we measure multiple words we don't know what the last word
|
|
// width is
|
|
lastWordWidth = -1;
|
|
|
|
// If all the text didn't fit, then we're done
|
|
if (numCharsFit != textRun.mTotalNumChars) {
|
|
break;
|
|
}
|
|
|
|
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 = (maxWidth - aTextData.mX) / aTs.mAveCharWidth;
|
|
estimatedNumChars += estimatedNumChars / 20;
|
|
#else
|
|
int unused = -1;
|
|
#endif
|
|
}
|
|
|
|
// If we didn't actually measure any text, then make sure it looks
|
|
// like we did
|
|
if (!aTextData.mMeasureText) {
|
|
aTextData.mX = mRect.width;
|
|
if (mState & TEXT_TRIMMED_WS) {
|
|
// Add back in the width of a space since it was trimmed away last time
|
|
// NOTE: Trailing whitespace includes word spacing!
|
|
aTextData.mX += aTs.mSpaceWidth + aTs.mWordSpacing;
|
|
}
|
|
}
|
|
|
|
// Post processing logic to deal with word-breaking that spans
|
|
// multiple frames.
|
|
if (lineLayout.InWord()) {
|
|
// We are already in a word. This means a text frame prior to this
|
|
// one had a fragment of a nbword that is joined with this
|
|
// frame. It also means that the prior frame already found this
|
|
// frame and recorded it as part of the word.
|
|
#ifdef DEBUG_WORD_WRAPPING
|
|
ListTag(stdout);
|
|
printf(": in word; skipping\n");
|
|
#endif
|
|
lineLayout.ForgetWordFrame(this);
|
|
}
|
|
|
|
if (!lineLayout.InWord()) {
|
|
// There is no currently active word. This frame may contain the
|
|
// start of one.
|
|
if (endsInWhitespace) {
|
|
// Nope, this frame doesn't start a word.
|
|
lineLayout.ForgetWordFrames();
|
|
}
|
|
else if ((aTextData.mOffset == contentLength) && (prevOffset >= 0)) {
|
|
// Force breakable to false when we aren't wrapping (this
|
|
// guarantees that the combined word will stay together)
|
|
if (!aTextData.mWrapping) {
|
|
aTextData.mIsBreakable = PR_FALSE;
|
|
}
|
|
|
|
// This frame does start a word. However, there is no point
|
|
// messing around with it if we are already out of room. We
|
|
// always have room if we are not breakable.
|
|
if (!aTextData.mIsBreakable || (aTextData.mX <= maxWidth)) {
|
|
// There is room for this word fragment. It's possible that
|
|
// this word fragment is the end of the text-run. If it's not
|
|
// then we continue with the look-ahead processing.
|
|
nsIFrame* next = lineLayout.FindNextText(aPresContext, this);
|
|
if (nsnull != next) {
|
|
#ifdef DEBUG_WORD_WRAPPING
|
|
nsAutoString tmp(aTx.GetWordBuffer(), lastWordLen);
|
|
ListTag(stdout);
|
|
printf(": start='");
|
|
fputs(tmp, stdout);
|
|
printf("' lastWordLen=%d baseWidth=%d prevOffset=%d offset=%d next=",
|
|
lastWordLen, lastWordWidth, prevOffset, aTextData.mOffset);
|
|
ListTag(stdout, next);
|
|
printf("\n");
|
|
#endif
|
|
PRUnichar* pWordBuf = lastWordPtr;
|
|
PRUint32 wordBufLen = aTx.GetWordBufferLength() -
|
|
(lastWordPtr - aTx.GetWordBuffer());
|
|
|
|
if (aTx.TransformedTextIsAscii()) {
|
|
// The text transform buffer contains ascii characters, so
|
|
// transform it to Unicode
|
|
NS_ASSERTION(wordBufLen >= PRUint32(lastWordLen), "no room to transform in place");
|
|
TransformTextToUnicode((char*)lastWordPtr, lastWordLen);
|
|
}
|
|
|
|
// Look ahead in the text-run and compute the final word
|
|
// width, taking into account any style changes and stopping
|
|
// at the first breakable point.
|
|
if (!aTextData.mMeasureText || (lastWordWidth == -1)) {
|
|
// We either didn't measure any text or we measured multiple words
|
|
// at once so either way we don't know lastWordWidth. We'll have to
|
|
// compute it now
|
|
if (prevOffset == startingOffset) {
|
|
// There's only one word, so we don't have to measure after all
|
|
lastWordWidth = aTextData.mX;
|
|
}
|
|
else if (aTs.mSmallCaps) {
|
|
MeasureSmallCapsText(aReflowState, aTs, pWordBuf,
|
|
lastWordLen, &lastWordWidth);
|
|
}
|
|
else {
|
|
aReflowState.rendContext->GetWidth(pWordBuf, lastWordLen, lastWordWidth);
|
|
if (aTs.mLetterSpacing) {
|
|
lastWordWidth += aTs.mLetterSpacing * lastWordLen;
|
|
}
|
|
}
|
|
}
|
|
nscoord wordWidth = ComputeTotalWordWidth(aPresContext, aLb,
|
|
lineLayout,
|
|
aReflowState, next,
|
|
lastWordWidth,
|
|
pWordBuf,
|
|
lastWordLen,
|
|
wordBufLen);
|
|
if (!aTextData.mIsBreakable || (aTextData.mX - lastWordWidth + wordWidth <= maxWidth)) {
|
|
// The fully joined word has fit. Account for the joined
|
|
// word's affect on the max-element-size here (since the
|
|
// joined word is large than it's pieces, the right effect
|
|
// will occur from the perspective of the container
|
|
// reflowing this frame)
|
|
if (wordWidth > aTextData.mMaxWordWidth) {
|
|
aTextData.mMaxWordWidth = wordWidth;
|
|
}
|
|
}
|
|
else {
|
|
#ifdef NOISY_REFLOW
|
|
ListTag(stdout);
|
|
printf(": look-ahead (didn't fit) x=%d wordWidth=%d lastWordWidth=%d\n",
|
|
aTextData.mX, wordWidth, lastWordWidth);
|
|
#endif
|
|
// The fully joined word won't fit. We need to reduce our
|
|
// size by the lastWordWidth.
|
|
aTextData.mX -= lastWordWidth;
|
|
aTextData.mMaxWordWidth = prevMaxWordWidth;
|
|
aTextData.mOffset = prevOffset;
|
|
column = prevColumn;
|
|
#ifdef DEBUG_WORD_WRAPPING
|
|
printf(" x=%d maxWordWidth=%d len=%d\n", aTextData.mX, aTextData.mMaxWordWidth,
|
|
aTextData.mOffset - startingOffset);
|
|
#endif
|
|
lineLayout.ForgetWordFrames();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Inform line layout of how this piece of text ends in whitespace
|
|
// (only text objects do this). Note that if x is zero then this
|
|
// text object collapsed into nothingness which means it shouldn't
|
|
// effect the current setting of the ends-in-whitespace flag.
|
|
lineLayout.SetColumn(column);
|
|
lineLayout.SetUnderstandsWhiteSpace(PR_TRUE);
|
|
lineLayout.SetTextStartsWithNBSP(textStartsWithNBSP);
|
|
if (0 != aTextData.mX) {
|
|
lineLayout.SetEndsInWhiteSpace(endsInWhitespace);
|
|
}
|
|
if (justDidFirstLetter) {
|
|
lineLayout.SetFirstLetterFrame(this);
|
|
lineLayout.SetFirstLetterStyleOK(PR_FALSE);
|
|
mState |= TEXT_FIRST_LETTER;
|
|
}
|
|
|
|
// Return our reflow status
|
|
nsReflowStatus rs = (aTextData.mOffset == contentLength)
|
|
? NS_FRAME_COMPLETE
|
|
: NS_FRAME_NOT_COMPLETE;
|
|
if (endsInNewline) {
|
|
rs = NS_INLINE_LINE_BREAK_AFTER(rs);
|
|
}
|
|
else if ((aTextData.mOffset != contentLength) && (aTextData.mOffset == startingOffset)) {
|
|
// Break-before a long-word that doesn't fit here
|
|
rs = NS_INLINE_LINE_BREAK_BEFORE();
|
|
}
|
|
|
|
return rs;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsTextFrame::Reflow(nsIPresContext* aPresContext,
|
|
nsHTMLReflowMetrics& aMetrics,
|
|
const nsHTMLReflowState& aReflowState,
|
|
nsReflowStatus& aStatus)
|
|
{
|
|
DO_GLOBAL_REFLOW_COUNT("nsTextFrame", aReflowState.reason);
|
|
#ifdef NOISY_REFLOW
|
|
ListTag(stdout);
|
|
printf(": BeginReflow: availableSize=%d,%d\n",
|
|
aReflowState.availableWidth, aReflowState.availableHeight);
|
|
#endif
|
|
|
|
// XXX If there's no line layout, we shouldn't even have created this
|
|
// frame. This may happen if, for example, this is text inside a table
|
|
// but not inside a cell. For now, just don't reflow.
|
|
if (nsnull == aReflowState.mLineLayout) {
|
|
// XXX Add a method to aMetrics that does this; we do it several places
|
|
aMetrics.width = 0;
|
|
aMetrics.height = 0;
|
|
aMetrics.ascent = 0;
|
|
aMetrics.descent = 0;
|
|
if (nsnull != aMetrics.maxElementSize) {
|
|
aMetrics.maxElementSize->width = 0;
|
|
aMetrics.maxElementSize->height = 0;
|
|
}
|
|
#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(&prevInFlow);
|
|
if (nsnull != prevInFlow) {
|
|
nsTextFrame* prev = (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;
|
|
TextStyle ts(aPresContext, *aReflowState.rendContext, mStyleContext);
|
|
|
|
// 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 (ts.mFont->mFont.decorations & NS_STYLE_TEXT_DECORATION_BLINK) {
|
|
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 = (NS_STYLE_WHITESPACE_NORMAL == ts.mText->mWhiteSpace) ||
|
|
(NS_STYLE_WHITESPACE_MOZ_PRE_WRAP == ts.mText->mWhiteSpace);
|
|
|
|
// 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
|
|
nsCOMPtr<nsIDocument> doc;
|
|
mContent->GetDocument(*getter_AddRefs(doc));
|
|
if (NS_WARN_IF_FALSE(doc, "Content has no document.")) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
nsCOMPtr<nsILineBreaker> lb;
|
|
doc->GetLineBreaker(getter_AddRefs(lb));
|
|
nsTextTransformer tx(lb, nsnull, 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, !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() || ((nsnull != prevInFlow) && (((nsTextFrame*)prevInFlow)->mState & TEXT_FIRST_LETTER));
|
|
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:
|
|
// - this is a resize reflow
|
|
// - we're not dirty (see ContentChanged() function)
|
|
// - 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)
|
|
if ((eReflowReason_Resize == aReflowState.reason) &&
|
|
(0 == (mState & NS_FRAME_IS_DIRTY))) {
|
|
|
|
nscoord realWidth = mRect.width;
|
|
if (mState & TEXT_TRIMMED_WS) {
|
|
// NOTE: Trailing whitespace includes word spacing!
|
|
realWidth += ts.mSpaceWidth + ts.mWordSpacing;
|
|
}
|
|
if (!mNextInFlow &&
|
|
(mState & TEXT_OPTIMIZE_RESIZE) &&
|
|
!aMetrics.maxElementSize &&
|
|
(lastTimeWeSkippedLeadingWS == skipWhitespace) &&
|
|
((wrapping && (maxWidth >= realWidth)) ||
|
|
(!wrapping && (prevColumn == column))) &&
|
|
!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(), nsnull != aMetrics.maxElementSize);
|
|
|
|
// Measure the text
|
|
aStatus = MeasureText(aPresContext, aReflowState, tx, lb, ts, textData);
|
|
|
|
if (tx.HasMultibyte()) {
|
|
mState |= TEXT_HAS_MULTIBYTE;
|
|
}
|
|
mState &= ~TEXT_TRIMMED_WS;
|
|
|
|
// 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;
|
|
aMetrics.descent = 0;
|
|
}
|
|
else {
|
|
ts.mNormalFont->GetHeight(aMetrics.height);
|
|
ts.mNormalFont->GetMaxAscent(aMetrics.ascent);
|
|
ts.mNormalFont->GetMaxDescent(aMetrics.descent);
|
|
}
|
|
if (!wrapping) {
|
|
textData.mMaxWordWidth = textData.mX;
|
|
}
|
|
if (nsnull != aMetrics.maxElementSize) {
|
|
aMetrics.maxElementSize->width = textData.mMaxWordWidth;
|
|
aMetrics.maxElementSize->height = aMetrics.height;
|
|
}
|
|
|
|
// Set content offset and length
|
|
mContentOffset = startingOffset;
|
|
mContentLength = textData.mOffset - startingOffset;
|
|
|
|
// Compute space and letter counts for justification, if required
|
|
if (ts.mJustifying) {
|
|
PRInt32 numSpaces;
|
|
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.
|
|
numSpaces = PrepareUnicodeText(tx, nsnull, nsnull, &textLength);
|
|
lineLayout.SetTextJustificationWeights(numSpaces, textLength - numSpaces);
|
|
}
|
|
|
|
|
|
#ifdef MOZ_MATHML
|
|
// Simple minded code to also return the bounding metrics if the caller wants it...
|
|
// More consolidation is needed -- a better approach is to follow what is done by
|
|
// the other routines that are doing measurements.
|
|
if (NS_REFLOW_CALC_BOUNDING_METRICS & aMetrics.mFlags) {
|
|
rv = NS_ERROR_UNEXPECTED; // initialize with an error; it is reset if things turn out well
|
|
aMetrics.mBoundingMetrics.Clear();
|
|
// Get the text to measure.
|
|
nsCOMPtr<nsIDOMText> domText(do_QueryInterface(mContent));
|
|
if (domText.get()) {
|
|
nsAutoString aData;
|
|
domText->GetData(aData);
|
|
|
|
// Extract the piece of text relevant to us -- XXX whitespace cause a mess here
|
|
nsAutoString aText;
|
|
aData.Mid(aText, mContentOffset, mContentLength);
|
|
|
|
// Set the font
|
|
nsStyleFont font;
|
|
mStyleContext->GetStyle(eStyleStruct_Font, font);
|
|
aReflowState.rendContext->SetFont(font.mFont);
|
|
|
|
// Now get the exact bounding metrics of the text
|
|
nsBoundingMetrics bm;
|
|
rv = aReflowState.rendContext->GetBoundingMetrics(aText.GetUnicode(), PRUint32(mContentLength), bm);
|
|
if (NS_SUCCEEDED(rv)) aMetrics.mBoundingMetrics = bm;
|
|
}
|
|
if (NS_FAILED(rv)) {
|
|
// Things didn't turn out well, just return the reflow metrics.
|
|
aMetrics.mBoundingMetrics.ascent = aMetrics.ascent;
|
|
aMetrics.mBoundingMetrics.descent = aMetrics.descent;
|
|
aMetrics.mBoundingMetrics.width = aMetrics.width;
|
|
#ifdef MOZ_MATHML_BOUNDINGMETRICS
|
|
printf("nsTextFrame: could not perform GetBoundingMetrics()\n");
|
|
#endif
|
|
}
|
|
}
|
|
#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_COMPLETE == 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) {
|
|
maxFrameWidth = PR_MAX(maxFrameWidth, mRect.width);
|
|
maxFrameHeight = PR_MAX(maxFrameHeight, mRect.height);
|
|
Invalidate(aPresContext, nsRect(0,0,maxFrameWidth,maxFrameHeight));
|
|
}
|
|
|
|
|
|
#ifdef NOISY_REFLOW
|
|
ListTag(stdout);
|
|
printf(": desiredSize=%d,%d(a=%d/d=%d) status=%x\n",
|
|
aMetrics.width, aMetrics.height, aMetrics.ascent, aMetrics.descent,
|
|
aStatus);
|
|
#endif
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsTextFrame::CanContinueTextRun(PRBool& aContinueTextRun) const
|
|
{
|
|
// We can continue a text run through a text frame
|
|
aContinueTextRun = PR_TRUE;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsTextFrame::AdjustFrameSize(nscoord aExtraSpace, nscoord& aUsedSpace)
|
|
{
|
|
aUsedSpace = 0;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsTextFrame::TrimTrailingWhiteSpace(nsIPresContext* aPresContext,
|
|
nsIRenderingContext& aRC,
|
|
nscoord& aDeltaWidth)
|
|
{
|
|
nscoord dw = 0;
|
|
const nsStyleText* textStyle = (const nsStyleText*)
|
|
mStyleContext->GetStyleData(eStyleStruct_Text);
|
|
if (mContentLength &&
|
|
(NS_STYLE_WHITESPACE_PRE != textStyle->mWhiteSpace) &&
|
|
(NS_STYLE_WHITESPACE_MOZ_PRE_WRAP != textStyle->mWhiteSpace)) {
|
|
|
|
// Get the text fragments that make up our content
|
|
nsCOMPtr<nsITextContent> tc = do_QueryInterface(mContent);
|
|
if (tc) {
|
|
const nsTextFragment* frag;
|
|
tc->GetText(&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.
|
|
const nsStyleFont* fontStyle = (const nsStyleFont*)
|
|
mStyleContext->GetStyleData(eStyleStruct_Font);
|
|
aRC.SetFont(fontStyle->mFont);
|
|
aRC.GetWidth(' ', dw);
|
|
// NOTE: Trailing whitespace includes word spacing!
|
|
PRIntn unit = textStyle->mWordSpacing.GetUnit();
|
|
if (eStyleUnit_Coord == unit) {
|
|
dw += textStyle->mWordSpacing.GetCoordValue();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
#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;
|
|
}
|
|
|
|
static void
|
|
RevertSpacesToNBSP(PRUnichar* aBuffer, PRInt32 aWordLen)
|
|
{
|
|
PRUnichar* end = aBuffer + aWordLen;
|
|
for (; aBuffer < end; aBuffer++) {
|
|
PRUnichar ch = *aBuffer;
|
|
if (ch == ' ') {
|
|
*aBuffer = CH_NBSP;
|
|
}
|
|
}
|
|
}
|
|
|
|
nscoord
|
|
nsTextFrame::ComputeTotalWordWidth(nsIPresContext* aPresContext,
|
|
nsILineBreaker* aLineBreaker,
|
|
nsLineLayout& aLineLayout,
|
|
const nsHTMLReflowState& aReflowState,
|
|
nsIFrame* aNextFrame,
|
|
nscoord aBaseWidth,
|
|
PRUnichar* aWordBuf,
|
|
PRUint32 aWordLen,
|
|
PRUint32 aWordBufSize)
|
|
{
|
|
// Before we get going, convert any spaces in the current word back
|
|
// to nbsp's. This keeps the breaking logic happy.
|
|
RevertSpacesToNBSP(aWordBuf, (PRInt32) aWordLen);
|
|
|
|
nscoord addedWidth = 0;
|
|
while (nsnull != aNextFrame) {
|
|
nsIContent* content = nsnull;
|
|
if ((NS_OK == aNextFrame->GetContent(&content)) && (nsnull != content)) {
|
|
#ifdef DEBUG_WORD_WRAPPING
|
|
printf(" next textRun=");
|
|
nsFrame::ListTag(stdout, aNextFrame);
|
|
printf("\n");
|
|
#endif
|
|
nsITextContent* tc;
|
|
if (NS_OK == content->QueryInterface(kITextContentIID, (void**)&tc)) {
|
|
PRBool stop = PR_FALSE;
|
|
nscoord moreWidth = ComputeWordFragmentWidth(aPresContext,
|
|
aLineBreaker,
|
|
aLineLayout,
|
|
aReflowState,
|
|
aNextFrame, content, tc,
|
|
&stop,
|
|
aWordBuf,
|
|
aWordLen,
|
|
aWordBufSize);
|
|
NS_RELEASE(tc);
|
|
NS_RELEASE(content);
|
|
addedWidth += moreWidth;
|
|
#ifdef DEBUG_WORD_WRAPPING
|
|
printf(" moreWidth=%d (addedWidth=%d) stop=%c\n", moreWidth,
|
|
addedWidth, stop?'T':'F');
|
|
#endif
|
|
if (stop) {
|
|
goto done;
|
|
}
|
|
}
|
|
else {
|
|
// It claimed it was text but it doesn't implement the
|
|
// nsITextContent API. Therefore I don't know what to do with it
|
|
// and can't look inside it. Oh well.
|
|
NS_RELEASE(content);
|
|
goto done;
|
|
}
|
|
}
|
|
|
|
// Move on to the next frame in the text-run
|
|
aNextFrame = aLineLayout.FindNextText(aPresContext, aNextFrame);
|
|
}
|
|
|
|
done:;
|
|
#ifdef DEBUG_WORD_WRAPPING
|
|
printf(" total word width=%d\n", aBaseWidth + addedWidth);
|
|
#endif
|
|
return aBaseWidth + addedWidth;
|
|
}
|
|
|
|
nscoord
|
|
nsTextFrame::ComputeWordFragmentWidth(nsIPresContext* aPresContext,
|
|
nsILineBreaker* aLineBreaker,
|
|
nsLineLayout& aLineLayout,
|
|
const nsHTMLReflowState& aReflowState,
|
|
nsIFrame* aTextFrame,
|
|
nsIContent* aContent,
|
|
nsITextContent* aText,
|
|
PRBool* aStop,
|
|
const PRUnichar* aWordBuf,
|
|
PRUint32& aRunningWordLen,
|
|
PRUint32 aWordBufSize)
|
|
{
|
|
nsTextTransformer tx(aLineBreaker, nsnull, aPresContext);
|
|
tx.Init(aTextFrame, aContent, 0);
|
|
PRBool isWhitespace, wasTransformed;
|
|
PRInt32 wordLen, contentLen;
|
|
PRUnichar* bp = tx.GetNextWord(PR_TRUE, &wordLen, &contentLen, &isWhitespace, &wasTransformed);
|
|
if (!bp || isWhitespace) {
|
|
// Don't bother measuring nothing
|
|
*aStop = PR_TRUE;
|
|
return 0;
|
|
}
|
|
*aStop = contentLen < tx.GetContentLength();
|
|
|
|
// Convert any spaces in the current word back to nbsp's. This keeps
|
|
// the breaking logic happy.
|
|
RevertSpacesToNBSP(bp, wordLen);
|
|
|
|
// We need to adjust the length by look at the two pieces together
|
|
// XXX this should grow aWordBuf if necessary
|
|
PRUint32 copylen = sizeof(PRUnichar) *
|
|
( ((wordLen + aRunningWordLen) > aWordBufSize)
|
|
? (aWordBufSize - aRunningWordLen)
|
|
: wordLen
|
|
);
|
|
if((aWordBufSize > aRunningWordLen) && (copylen > 0))
|
|
{
|
|
nsCRT::memcpy((void*)&(aWordBuf[aRunningWordLen]), bp, copylen);
|
|
|
|
PRUint32 breakP=0;
|
|
PRBool needMore=PR_TRUE;
|
|
nsresult lres = aLineBreaker->Next(aWordBuf, aRunningWordLen+wordLen,
|
|
0, &breakP, &needMore);
|
|
if(NS_SUCCEEDED(lres))
|
|
{
|
|
// when we look at two pieces text together, we might decide to break
|
|
// eariler than if we only look at the 2nd pieces of text
|
|
if(!needMore && (breakP < (aRunningWordLen + wordLen)))
|
|
{
|
|
wordLen = breakP - aRunningWordLen;
|
|
if(wordLen < 0)
|
|
wordLen = 0;
|
|
*aStop = PR_TRUE;
|
|
}
|
|
}
|
|
|
|
// if we don't stop, we need to extend the buf so the next one can
|
|
// see this part otherwise, it does not matter since we will stop
|
|
// anyway
|
|
if(! *aStop)
|
|
aRunningWordLen += wordLen;
|
|
}
|
|
if((*aStop) && (wordLen == 0))
|
|
return 0;
|
|
|
|
nsCOMPtr<nsIStyleContext> sc;
|
|
aTextFrame->GetStyleContext(getter_AddRefs(sc));
|
|
if (sc) {
|
|
// Measure the piece of text. Note that we have to select the
|
|
// appropriate font into the text first because the rendering
|
|
// context has our font in it, not the font that aText is using.
|
|
nscoord width;
|
|
nsIRenderingContext& rc = *aReflowState.rendContext;
|
|
nsCOMPtr<nsIFontMetrics> oldfm;
|
|
rc.GetFontMetrics(*getter_AddRefs(oldfm));
|
|
|
|
TextStyle ts(aLineLayout.mPresContext, rc, sc);
|
|
if (ts.mSmallCaps) {
|
|
MeasureSmallCapsText(aReflowState, ts, bp, wordLen, &width);
|
|
}
|
|
else {
|
|
rc.GetWidth(bp, wordLen, width);
|
|
// NOTE: Don't forget to add letter spacing for the word fragment!
|
|
width += wordLen*ts.mLetterSpacing;
|
|
}
|
|
rc.SetFont(oldfm);
|
|
|
|
#ifdef DEBUG_WORD_WRAPPING
|
|
nsAutoString tmp(bp, wordLen);
|
|
printf(" fragment='");
|
|
fputs(tmp, stdout);
|
|
printf("' width=%d [wordLen=%d contentLen=%d ContentLength=%d]\n",
|
|
width, wordLen, contentLen, tx.GetContentLength());
|
|
#endif
|
|
|
|
// Remember the text frame for later so that we don't bother doing
|
|
// the word look ahead.
|
|
aLineLayout.RecordWordFrame(aTextFrame);
|
|
return width;
|
|
}
|
|
|
|
*aStop = PR_TRUE;
|
|
return 0;
|
|
}
|
|
|
|
// Translate the mapped content into a string that's printable
|
|
void
|
|
nsTextFrame::ToCString(nsString& aBuf, PRInt32* aTotalContentLength) const
|
|
{
|
|
const nsTextFragment* frag;
|
|
|
|
// Get the frames text content
|
|
nsITextContent* tc;
|
|
if (NS_OK != mContent->QueryInterface(kITextContentIID, (void**) &tc)) {
|
|
return;
|
|
}
|
|
tc->GetText(&frag);
|
|
NS_RELEASE(tc);
|
|
|
|
// 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.AppendWithConversion("\\r");
|
|
} else if (ch == '\n') {
|
|
aBuf.AppendWithConversion("\\n");
|
|
} else if (ch == '\t') {
|
|
aBuf.AppendWithConversion("\\t");
|
|
} else if ((ch < ' ') || (ch >= 127)) {
|
|
aBuf.AppendWithConversion("\\0");
|
|
aBuf.AppendInt((PRInt32)ch, 8);
|
|
} else {
|
|
aBuf.Append(ch);
|
|
}
|
|
}
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsTextFrame::GetFrameType(nsIAtom** aType) const
|
|
{
|
|
NS_PRECONDITION(nsnull != aType, "null OUT parameter pointer");
|
|
*aType = nsLayoutAtoms::textFrame;
|
|
NS_ADDREF(*aType);
|
|
return NS_OK;
|
|
}
|
|
|
|
#ifdef DEBUG
|
|
NS_IMETHODIMP
|
|
nsTextFrame::SizeOf(nsISizeOfHandler* aHandler, PRUint32* aResult) const
|
|
{
|
|
if (!aResult) {
|
|
return NS_ERROR_NULL_POINTER;
|
|
}
|
|
*aResult = sizeof(*this);
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsTextFrame::GetFrameName(nsString& aResult) const
|
|
{
|
|
return MakeFrameName("Text", aResult);
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsTextFrame::List(nsIPresContext* aPresContext, FILE* out, PRInt32 aIndent) const
|
|
{
|
|
// Output the tag
|
|
IndentBy(out, aIndent);
|
|
ListTag(out);
|
|
#ifdef DEBUG_waterson
|
|
fprintf(out, " [parent=%p]", mParent);
|
|
#endif
|
|
nsIView* view;
|
|
GetView(aPresContext, &view);
|
|
if (nsnull != view) {
|
|
fprintf(out, " [view=%p]", view);
|
|
}
|
|
|
|
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", mNextSibling);
|
|
}
|
|
nsIFrame* prevInFlow;
|
|
GetPrevInFlow(&prevInFlow);
|
|
if (nsnull != prevInFlow) {
|
|
fprintf(out, " prev-in-flow=%p", prevInFlow);
|
|
}
|
|
if (nsnull != mNextInFlow) {
|
|
fprintf(out, " next-in-flow=%p", mNextInFlow);
|
|
}
|
|
|
|
// Output the rect and state
|
|
fprintf(out, " {%d,%d,%d,%d}", mRect.x, mRect.y, mRect.width, mRect.height);
|
|
if (0 != mState) {
|
|
fprintf(out, " [state=%08x]", mState);
|
|
}
|
|
fprintf(out, " sc=%p<\n", mStyleContext);
|
|
|
|
// Output the text
|
|
aIndent++;
|
|
|
|
IndentBy(out, aIndent);
|
|
fputs("\"", out);
|
|
fputs(tmp, out);
|
|
fputs("\"\n", out);
|
|
|
|
aIndent--;
|
|
IndentBy(out, aIndent);
|
|
fputs(">\n", out);
|
|
|
|
return NS_OK;
|
|
}
|
|
#endif
|