1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* ***** BEGIN LICENSE BLOCK *****
3 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
5 * The contents of this file are subject to the Mozilla Public License Version
6 * 1.1 (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 * http://www.mozilla.org/MPL/
10 * Software distributed under the License is distributed on an "AS IS" basis,
11 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
12 * for the specific language governing rights and limitations under the
15 * The Original Code is mozilla.org code.
17 * The Initial Developer of the Original Code is
18 * Netscape Communications Corporation.
19 * Portions created by the Initial Developer are Copyright (C) 1998
20 * the Initial Developer. All Rights Reserved.
23 * Robert O'Callahan <robert@ocallahan.org>
24 * Roger B. Sidje <rbs@maths.uq.edu.au>
25 * Pierre Phaneuf <pp@ludusdesign.com>
26 * Prabhat Hegde <prabhat.hegde@sun.com>
27 * Tomi Leppikangas <tomi.leppikangas@oulu.fi>
28 * Roland Mainz <roland.mainz@informatik.med.uni-giessen.de>
29 * Daniel Glazman <glazman@netscape.com>
30 * Neil Deakin <neil@mozdevgroup.com>
31 * Masayuki Nakano <masayuki@d-toybox.com>
32 * Mats Palmgren <mats.palmgren@bredband.net>
33 * Uri Bernstein <uriber@gmail.com>
34 * Stephen Blackheath <entangled.mooched.stephen@blacksapphire.com>
35 * Michael Ventnor <m.ventnor@gmail.com>
37 * Alternatively, the contents of this file may be used under the terms of
38 * either of the GNU General Public License Version 2 or later (the "GPL"),
39 * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
40 * in which case the provisions of the GPL or the LGPL are applicable instead
41 * of those above. If you wish to allow use of your version of this file only
42 * under the terms of either the GPL or the LGPL, and not to allow others to
43 * use your version of this file under the terms of the MPL, indicate your
44 * decision by deleting the provisions above and replace them with the notice
45 * and other provisions required by the GPL or the LGPL. If you do not delete
46 * the provisions above, a recipient may use your version of this file under
47 * the terms of any one of the MPL, the GPL or the LGPL.
49 * ***** END LICENSE BLOCK ***** */
51 /* rendering object for textual content of elements */
54 #include "nsHTMLParts.h"
56 #include "nsSplittableFrame.h"
57 #include "nsLineLayout.h"
59 #include "nsUnicharUtils.h"
60 #include "nsPresContext.h"
61 #include "nsIContent.h"
62 #include "nsStyleConsts.h"
63 #include "nsStyleContext.h"
65 #include "nsIFontMetrics.h"
66 #include "nsIRenderingContext.h"
67 #include "nsIPresShell.h"
69 #include "nsVoidArray.h"
70 #include "nsIDOMText.h"
71 #include "nsIDocument.h"
72 #include "nsIDeviceContext.h"
73 #include "nsCSSPseudoElements.h"
74 #include "nsCompatibility.h"
75 #include "nsCSSColorUtils.h"
76 #include "nsLayoutUtils.h"
77 #include "nsDisplayList.h"
79 #include "nsTextFrameUtils.h"
80 #include "nsTextRunTransformations.h"
81 #include "nsFrameManager.h"
82 #include "nsTextFrameTextRunCache.h"
83 #include "nsExpirationTracker.h"
84 #include "nsTextFrame.h"
85 #include "nsICaseConversion.h"
86 #include "nsIUGenCategory.h"
87 #include "nsUnicharUtilCIID.h"
89 #include "nsTextFragment.h"
90 #include "nsGkAtoms.h"
91 #include "nsFrameSelection.h"
92 #include "nsISelection.h"
93 #include "nsIDOMRange.h"
94 #include "nsILookAndFeel.h"
95 #include "nsCSSRendering.h"
96 #include "nsContentUtils.h"
97 #include "nsLineBreaker.h"
98 #include "nsIWordBreaker.h"
100 #include "nsILineIterator.h"
102 #include "nsIServiceManager.h"
104 #include "nsIAccessible.h"
105 #include "nsIAccessibilityService.h"
107 #include "nsAutoPtr.h"
108 #include "nsStyleSet.h"
110 #include "nsBidiFrames.h"
111 #include "nsBidiPresUtils.h"
112 #include "nsBidiUtils.h"
114 #include "nsIThebesFontMetrics.h"
116 #include "gfxContext.h"
117 #include "gfxTextRunWordCache.h"
118 #include "gfxImageSurface.h"
130 // The following flags are set during reflow
132 // This bit is set on the first frame in a continuation indicating
133 // that it was chopped short because of :first-letter style.
134 #define TEXT_FIRST_LETTER 0x00100000
135 // This bit is set on frames that are logically adjacent to the start of the
136 // line (i.e. no prior frame on line with actual displayed in-flow content).
137 #define TEXT_START_OF_LINE 0x00200000
138 // This bit is set on frames that are logically adjacent to the end of the
139 // line (i.e. no following on line with actual displayed in-flow content).
140 #define TEXT_END_OF_LINE 0x00400000
141 // This bit is set on frames that end with a hyphenated break.
142 #define TEXT_HYPHEN_BREAK 0x00800000
143 // This bit is set on frames that trimmed trailing whitespace characters when
144 // calculating their width during reflow.
145 #define TEXT_TRIMMED_TRAILING_WHITESPACE 0x01000000
146 // This bit is set on frames that have justification enabled. We record
147 // this in a state bit because we don't always have the containing block
148 // easily available to check text-align on.
149 #define TEXT_JUSTIFICATION_ENABLED 0x02000000
150 // Set this bit if the textframe has overflow area for IME/spellcheck underline.
151 #define TEXT_SELECTION_UNDERLINE_OVERFLOWED 0x04000000
153 #define TEXT_REFLOW_FLAGS \
154 (TEXT_FIRST_LETTER|TEXT_START_OF_LINE|TEXT_END_OF_LINE|TEXT_HYPHEN_BREAK| \
155 TEXT_TRIMMED_TRAILING_WHITESPACE|TEXT_JUSTIFICATION_ENABLED| \
156 TEXT_HAS_NONCOLLAPSED_CHARACTERS|TEXT_SELECTION_UNDERLINE_OVERFLOWED)
158 // Cache bits for IsEmpty().
159 // Set this bit if the textframe is known to be only collapsible whitespace.
160 #define TEXT_IS_ONLY_WHITESPACE 0x08000000
161 // Set this bit if the textframe is known to be not only collapsible whitespace.
162 #define TEXT_ISNOT_ONLY_WHITESPACE 0x10000000
164 #define TEXT_WHITESPACE_FLAGS 0x18000000
165 // This bit is set while the frame is registered as a blinking frame.
166 #define TEXT_BLINK_ON 0x20000000
169 // #define TEXT_HAS_NONCOLLAPSED_CHARACTERS 0x80000000
174 * Text frames delegate work to gfxTextRun objects. The gfxTextRun object
175 * transforms text to positioned glyphs. It can report the geometry of the
176 * glyphs and paint them. Text frames configure gfxTextRuns by providing text,
177 * spacing, language, and other information.
179 * A gfxTextRun can cover more than one DOM text node. This is necessary to
180 * get kerning, ligatures and shaping for text that spans multiple text nodes
181 * but is all the same font. The userdata for a gfxTextRun object is a
182 * TextRunUserData* or an nsIFrame*.
184 * We go to considerable effort to make sure things work even if in-flow
185 * siblings have different style contexts (i.e., first-letter and first-line).
187 * Our convention is that unsigned integer character offsets are offsets into
188 * the transformed string. Signed integer character offsets are offsets into
191 * XXX currently we don't handle hyphenated breaks between text frames where the
192 * hyphen occurs at the end of the first text frame, e.g.
197 * We use an array of these objects to record which text frames
198 * are associated with the textrun. mStartFrame is the start of a list of
199 * text frames. Some sequence of its continuations are covered by the textrun.
200 * A content textnode can have at most one TextRunMappedFlow associated with it
201 * for a given textrun.
203 * mDOMOffsetToBeforeTransformOffset is added to DOM offsets for those frames to obtain
204 * the offset into the before-transformation text of the textrun. It can be
205 * positive (when a text node starts in the middle of a text run) or
206 * negative (when a text run starts in the middle of a text node). Of course
207 * it can also be zero.
209 struct TextRunMappedFlow
{
210 nsTextFrame
* mStartFrame
;
211 PRInt32 mDOMOffsetToBeforeTransformOffset
;
212 // The text mapped starts at mStartFrame->GetContentOffset() and is this long
213 PRUint32 mContentLength
;
217 * This is our user data for the textrun, when textRun->GetFlags() does not
218 * have TEXT_SIMPLE_FLOW set. When TEXT_SIMPLE_FLOW is set, there is just one
219 * flow, the textrun's user data pointer is a pointer to mStartFrame
220 * for that flow, mDOMOffsetToBeforeTransformOffset is zero, and mContentLength
221 * is the length of the text node.
223 struct TextRunUserData
{
224 TextRunMappedFlow
* mMappedFlows
;
225 PRInt32 mMappedFlowCount
;
227 PRUint32 mLastFlowIndex
;
231 * This helper object computes colors used for painting, and also IME
232 * underline information. The data is computed lazily and cached as necessary.
233 * These live for just the duration of one paint operation.
235 class nsTextPaintStyle
{
237 nsTextPaintStyle(nsTextFrame
* aFrame
);
239 nscolor
GetTextColor();
241 * Compute the colors for normally-selected text. Returns false if
242 * the normal selection is not being displayed.
244 PRBool
GetSelectionColors(nscolor
* aForeColor
,
245 nscolor
* aBackColor
);
246 void GetHighlightColors(nscolor
* aForeColor
,
247 nscolor
* aBackColor
);
248 void GetIMESelectionColors(PRInt32 aIndex
,
250 nscolor
* aBackColor
);
251 // if this returns PR_FALSE, we don't need to draw underline.
252 PRBool
GetIMEUnderline(PRInt32 aIndex
,
254 float* aRelativeSize
,
257 nsPresContext
* PresContext() { return mPresContext
; }
268 nsPresContext
* mPresContext
;
269 PRPackedBool mInitCommonColors
;
270 PRPackedBool mInitSelectionColors
;
274 PRInt16 mSelectionStatus
; // see nsIDocument.h SetDisplaySelection()
275 nscolor mSelectionTextColor
;
276 nscolor mSelectionBGColor
;
280 PRInt32 mSufficientContrast
;
281 nscolor mFrameBackgroundColor
;
283 // IME selection colors and underline info
288 nscolor mUnderlineColor
;
289 PRUint8 mUnderlineStyle
;
291 nsIMEStyle mIMEStyle
[4];
293 float mIMEUnderlineRelativeSize
;
295 // Color initializations
296 void InitCommonColors();
297 PRBool
InitSelectionColors();
299 nsIMEStyle
* GetIMEStyle(PRInt32 aIndex
);
300 void InitIMEStyle(PRInt32 aIndex
);
302 PRBool
EnsureSufficientContrast(nscolor
*aForeColor
, nscolor
*aBackColor
);
304 nscolor
GetResolvedForeColor(nscolor aColor
, nscolor aDefaultForeColor
,
309 DestroyUserData(void* aUserData
)
311 TextRunUserData
* userData
= static_cast<TextRunUserData
*>(aUserData
);
313 nsMemory::Free(userData
);
317 // Remove the textrun from the frame continuation chain starting at aFrame,
318 // which should be marked as a textrun owner.
320 ClearAllTextRunReferences(nsTextFrame
* aFrame
, gfxTextRun
* aTextRun
)
323 if (aFrame
->GetTextRun() != aTextRun
)
325 aFrame
->SetTextRun(nsnull
);
326 aFrame
= static_cast<nsTextFrame
*>(aFrame
->GetNextContinuation());
330 // Figure out which frames
332 UnhookTextRunFromFrames(gfxTextRun
* aTextRun
)
334 if (!aTextRun
->GetUserData())
337 // Kill all references to the textrun. It could be referenced by any of its
338 // owners, and all their in-flows.
339 if (aTextRun
->GetFlags() & nsTextFrameUtils::TEXT_IS_SIMPLE_FLOW
) {
340 nsIFrame
* firstInFlow
= static_cast<nsIFrame
*>(aTextRun
->GetUserData());
341 ClearAllTextRunReferences(static_cast<nsTextFrame
*>(firstInFlow
), aTextRun
);
343 TextRunUserData
* userData
=
344 static_cast<TextRunUserData
*>(aTextRun
->GetUserData());
346 for (i
= 0; i
< userData
->mMappedFlowCount
; ++i
) {
347 ClearAllTextRunReferences(userData
->mMappedFlows
[i
].mStartFrame
, aTextRun
);
349 DestroyUserData(userData
);
351 aTextRun
->SetUserData(nsnull
);
354 class FrameTextRunCache
;
356 static FrameTextRunCache
*gTextRuns
= nsnull
;
359 * Cache textruns and expire them after 3*10 seconds of no use.
361 class FrameTextRunCache
: public nsExpirationTracker
<gfxTextRun
,3> {
363 enum { TIMEOUT_SECONDS
= 10 };
365 : nsExpirationTracker
<gfxTextRun
,3>(TIMEOUT_SECONDS
*1000) {}
366 ~FrameTextRunCache() {
370 void RemoveFromCache(gfxTextRun
* aTextRun
) {
371 if (aTextRun
->GetExpirationState()->IsTracked()) {
372 RemoveObject(aTextRun
);
374 if (aTextRun
->GetFlags() & gfxTextRunWordCache::TEXT_IN_CACHE
) {
375 gfxTextRunWordCache::RemoveTextRun(aTextRun
);
379 // This gets called when the timeout has expired on a gfxTextRun
380 virtual void NotifyExpired(gfxTextRun
* aTextRun
) {
381 UnhookTextRunFromFrames(aTextRun
);
382 RemoveFromCache(aTextRun
);
388 MakeTextRun(const PRUnichar
*aText
, PRUint32 aLength
,
389 gfxFontGroup
*aFontGroup
, const gfxFontGroup::Parameters
* aParams
,
392 nsAutoPtr
<gfxTextRun
> textRun
;
394 textRun
= aFontGroup
->MakeEmptyTextRun(aParams
, aFlags
);
395 } else if (aLength
== 1 && aText
[0] == ' ') {
396 textRun
= aFontGroup
->MakeSpaceTextRun(aParams
, aFlags
);
398 textRun
= gfxTextRunWordCache::MakeTextRun(aText
, aLength
, aFontGroup
,
403 nsresult rv
= gTextRuns
->AddObject(textRun
);
405 gTextRuns
->RemoveFromCache(textRun
);
408 return textRun
.forget();
412 MakeTextRun(const PRUint8
*aText
, PRUint32 aLength
,
413 gfxFontGroup
*aFontGroup
, const gfxFontGroup::Parameters
* aParams
,
416 nsAutoPtr
<gfxTextRun
> textRun
;
418 textRun
= aFontGroup
->MakeEmptyTextRun(aParams
, aFlags
);
419 } else if (aLength
== 1 && aText
[0] == ' ') {
420 textRun
= aFontGroup
->MakeSpaceTextRun(aParams
, aFlags
);
422 textRun
= gfxTextRunWordCache::MakeTextRun(aText
, aLength
, aFontGroup
,
427 nsresult rv
= gTextRuns
->AddObject(textRun
);
429 gTextRuns
->RemoveFromCache(textRun
);
432 return textRun
.forget();
436 nsTextFrameTextRunCache::Init() {
437 gTextRuns
= new FrameTextRunCache();
438 return gTextRuns
? NS_OK
: NS_ERROR_OUT_OF_MEMORY
;
442 nsTextFrameTextRunCache::Shutdown() {
447 PRInt32
nsTextFrame::GetContentEnd() const {
448 nsTextFrame
* next
= static_cast<nsTextFrame
*>(GetNextContinuation());
449 return next
? next
->GetContentOffset() : mContent
->GetText()->GetLength();
452 PRInt32
nsTextFrame::GetInFlowContentLength() {
454 nsTextFrame
* nextBidi
= nsnull
;
455 PRInt32 start
= -1, end
;
457 if (mState
& NS_FRAME_IS_BIDI
) {
458 nextBidi
= static_cast<nsTextFrame
*>(GetLastInFlow()->GetNextContinuation());
460 nextBidi
->GetOffsets(start
, end
);
461 return start
- mContentOffset
;
465 return mContent
->TextLength() - mContentOffset
;
468 // Smarter versions of XP_IS_SPACE.
469 // Unicode is really annoying; sometimes a space character isn't whitespace ---
470 // when it combines with another character
471 // So we have several versions of IsSpace for use in different contexts.
473 static PRBool
IsSpaceCombiningSequenceTail(const nsTextFragment
* aFrag
, PRUint32 aPos
)
475 NS_ASSERTION(aPos
<= aFrag
->GetLength(), "Bad offset");
478 return nsTextFrameUtils::IsSpaceCombiningSequenceTail(
479 aFrag
->Get2b() + aPos
, aFrag
->GetLength() - aPos
);
482 // Check whether aPos is a space for CSS 'word-spacing' purposes
483 static PRBool
IsCSSWordSpacingSpace(const nsTextFragment
* aFrag
,
486 NS_ASSERTION(aPos
< aFrag
->GetLength(), "No text for IsSpace!");
488 PRUnichar ch
= aFrag
->CharAt(aPos
);
489 if (ch
== ' ' || ch
== CH_CJKSP
)
490 return !IsSpaceCombiningSequenceTail(aFrag
, aPos
+ 1);
491 return ch
== '\t' || ch
== '\f' || ch
== '\n';
494 // Check whether the string aChars/aLength starts with space that's
495 // trimmable according to CSS 'white-space:normal/nowrap'.
496 static PRBool
IsTrimmableSpace(const PRUnichar
* aChars
, PRUint32 aLength
)
498 NS_ASSERTION(aLength
> 0, "No text for IsSpace!");
500 PRUnichar ch
= *aChars
;
502 return !nsTextFrameUtils::IsSpaceCombiningSequenceTail(aChars
+ 1, aLength
- 1);
503 return ch
== '\t' || ch
== '\f' || ch
== '\n';
506 // Check whether the character aCh is trimmable according to CSS
507 // 'white-space:normal/nowrap'
508 static PRBool
IsTrimmableSpace(char aCh
)
510 return aCh
== ' ' || aCh
== '\t' || aCh
== '\f' || aCh
== '\n';
513 static PRBool
IsTrimmableSpace(const nsTextFragment
* aFrag
, PRUint32 aPos
,
514 const nsStyleText
* aStyleText
)
516 NS_ASSERTION(aPos
< aFrag
->GetLength(), "No text for IsSpace!");
518 switch (aFrag
->CharAt(aPos
)) {
519 case ' ': return !aStyleText
->WhiteSpaceIsSignificant() &&
520 !IsSpaceCombiningSequenceTail(aFrag
, aPos
+ 1);
521 case '\n': return !aStyleText
->NewlineIsSignificant();
523 case '\f': return !aStyleText
->WhiteSpaceIsSignificant();
524 default: return PR_FALSE
;
528 static PRBool
IsSelectionSpace(const nsTextFragment
* aFrag
, PRUint32 aPos
)
530 NS_ASSERTION(aPos
< aFrag
->GetLength(), "No text for IsSpace!");
531 PRUnichar ch
= aFrag
->CharAt(aPos
);
532 if (ch
== ' ' || ch
== CH_NBSP
)
533 return !IsSpaceCombiningSequenceTail(aFrag
, aPos
+ 1);
534 return ch
== '\t' || ch
== '\n' || ch
== '\f';
537 // Count the amount of trimmable whitespace (as per CSS
538 // 'white-space:normal/nowrap') in a text fragment. The first
539 // character is at offset aStartOffset; the maximum number of characters
540 // to check is aLength. aDirection is -1 or 1 depending on whether we should
541 // progress backwards or forwards.
543 GetTrimmableWhitespaceCount(const nsTextFragment
* aFrag
,
544 PRInt32 aStartOffset
, PRInt32 aLength
,
549 const PRUnichar
* str
= aFrag
->Get2b() + aStartOffset
;
550 PRInt32 fragLen
= aFrag
->GetLength() - aStartOffset
;
551 for (; count
< aLength
; ++count
) {
552 if (!IsTrimmableSpace(str
, fragLen
))
555 fragLen
-= aDirection
;
558 const char* str
= aFrag
->Get1b() + aStartOffset
;
559 for (; count
< aLength
; ++count
) {
560 if (!IsTrimmableSpace(*str
))
569 IsAllWhitespace(const nsTextFragment
* aFrag
, PRBool aAllowNewline
)
573 PRInt32 len
= aFrag
->GetLength();
574 const char* str
= aFrag
->Get1b();
575 for (PRInt32 i
= 0; i
< len
; ++i
) {
577 if (ch
== ' ' || ch
== '\t' || (ch
== '\n' && aAllowNewline
))
585 * This class accumulates state as we scan a paragraph of text. It detects
586 * textrun boundaries (changes from text to non-text, hard
587 * line breaks, and font changes) and builds a gfxTextRun at each boundary.
588 * It also detects linebreaker run boundaries (changes from text to non-text,
589 * and hard line breaks) and at each boundary runs the linebreaker to compute
590 * potential line breaks. It also records actual line breaks to store them in
593 class BuildTextRunsScanner
{
595 BuildTextRunsScanner(nsPresContext
* aPresContext
, gfxContext
* aContext
,
596 nsIFrame
* aLineContainer
) :
597 mCurrentFramesAllSameTextRun(nsnull
),
599 mLineContainer(aLineContainer
),
600 mBidiEnabled(aPresContext
->BidiEnabled()),
601 mTrimNextRunLeadingWhitespace(PR_FALSE
),
602 mSkipIncompleteTextRuns(PR_FALSE
) {
606 void SetAtStartOfLine() {
607 mStartOfLine
= PR_TRUE
;
608 mCanStopOnThisLine
= PR_FALSE
;
610 void SetSkipIncompleteTextRuns(PRBool aSkip
) {
611 mSkipIncompleteTextRuns
= aSkip
;
613 void SetCommonAncestorWithLastFrame(nsIFrame
* aFrame
) {
614 mCommonAncestorWithLastFrame
= aFrame
;
616 PRBool
CanStopOnThisLine() {
617 return mCanStopOnThisLine
;
619 nsIFrame
* GetCommonAncestorWithLastFrame() {
620 return mCommonAncestorWithLastFrame
;
622 void LiftCommonAncestorWithLastFrameToParent(nsIFrame
* aFrame
) {
623 if (mCommonAncestorWithLastFrame
&&
624 mCommonAncestorWithLastFrame
->GetParent() == aFrame
) {
625 mCommonAncestorWithLastFrame
= aFrame
;
628 void ScanFrame(nsIFrame
* aFrame
);
629 PRBool
IsTextRunValidForMappedFlows(gfxTextRun
* aTextRun
);
630 void FlushFrames(PRBool aFlushLineBreaks
, PRBool aSuppressTrailingBreak
);
631 void ResetRunInfo() {
633 mMappedFlows
.Clear();
634 mLineBreakBeforeFrames
.Clear();
636 mDoubleByteText
= PR_FALSE
;
638 void ResetLineBreaker() {
639 PRBool trailingBreak
;
640 mLineBreaker
.Reset(&trailingBreak
);
642 void AccumulateRunInfo(nsTextFrame
* aFrame
);
644 * @return null to indicate either textrun construction failed or
645 * we constructed just a partial textrun to set up linebreaker and other
646 * state for following textruns.
648 gfxTextRun
* BuildTextRunForFrames(void* aTextBuffer
);
649 void AssignTextRun(gfxTextRun
* aTextRun
);
650 nsTextFrame
* GetNextBreakBeforeFrame(PRUint32
* aIndex
);
651 void SetupBreakSinksForTextRun(gfxTextRun
* aTextRun
, PRBool aIsExistingTextRun
,
652 PRBool aSuppressSink
);
653 struct FindBoundaryState
{
654 nsIFrame
* mStopAtFrame
;
655 nsTextFrame
* mFirstTextFrame
;
656 nsTextFrame
* mLastTextFrame
;
657 PRPackedBool mSeenTextRunBoundaryOnLaterLine
;
658 PRPackedBool mSeenTextRunBoundaryOnThisLine
;
659 PRPackedBool mSeenSpaceForLineBreakingOnThisLine
;
661 enum FindBoundaryResult
{
663 FB_STOPPED_AT_STOP_FRAME
,
664 FB_FOUND_VALID_TEXTRUN_BOUNDARY
666 FindBoundaryResult
FindBoundaries(nsIFrame
* aFrame
, FindBoundaryState
* aState
);
668 PRBool
ContinueTextRunAcrossFrames(nsTextFrame
* aFrame1
, nsTextFrame
* aFrame2
);
670 // Like TextRunMappedFlow but with some differences. mStartFrame to mEndFrame
671 // (exclusive) are a sequence of in-flow frames (if mEndFrame is null, then
672 // continuations starting from mStartFrame are a sequence of in-flow frames).
674 nsTextFrame
* mStartFrame
;
675 nsTextFrame
* mEndFrame
;
676 // When we consider breaking between elements, the nearest common
677 // ancestor of the elements containing the characters is the one whose
678 // CSS 'white-space' property governs. So this records the nearest common
679 // ancestor of mStartFrame and the previous text frame, or null if there
680 // was no previous text frame on this line.
681 nsIFrame
* mAncestorControllingInitialBreak
;
683 PRInt32
GetContentEnd() {
684 return mEndFrame
? mEndFrame
->GetContentOffset()
685 : mStartFrame
->GetContent()->GetText()->GetLength();
689 class BreakSink
: public nsILineBreakSink
{
691 BreakSink(gfxTextRun
* aTextRun
, gfxContext
* aContext
, PRUint32 aOffsetIntoTextRun
,
692 PRBool aExistingTextRun
) :
693 mTextRun(aTextRun
), mContext(aContext
),
694 mOffsetIntoTextRun(aOffsetIntoTextRun
),
695 mChangedBreaks(PR_FALSE
), mExistingTextRun(aExistingTextRun
) {}
697 virtual void SetBreaks(PRUint32 aOffset
, PRUint32 aLength
,
698 PRPackedBool
* aBreakBefore
) {
699 if (mTextRun
->SetPotentialLineBreaks(aOffset
+ mOffsetIntoTextRun
, aLength
,
700 aBreakBefore
, mContext
)) {
701 mChangedBreaks
= PR_TRUE
;
702 // Be conservative and assume that some breaks have been set
703 mTextRun
->ClearFlagBits(nsTextFrameUtils::TEXT_NO_BREAKS
);
707 virtual void SetCapitalization(PRUint32 aOffset
, PRUint32 aLength
,
708 PRPackedBool
* aCapitalize
) {
709 NS_ASSERTION(mTextRun
->GetFlags() & nsTextFrameUtils::TEXT_IS_TRANSFORMED
,
710 "Text run should be transformed!");
711 nsTransformedTextRun
* transformedTextRun
=
712 static_cast<nsTransformedTextRun
*>(mTextRun
);
713 transformedTextRun
->SetCapitalization(aOffset
+ mOffsetIntoTextRun
, aLength
,
714 aCapitalize
, mContext
);
717 gfxTextRun
* mTextRun
;
718 gfxContext
* mContext
;
719 PRUint32 mOffsetIntoTextRun
;
720 PRPackedBool mChangedBreaks
;
721 PRPackedBool mExistingTextRun
;
725 nsAutoTArray
<MappedFlow
,10> mMappedFlows
;
726 nsAutoTArray
<nsTextFrame
*,50> mLineBreakBeforeFrames
;
727 nsAutoTArray
<nsAutoPtr
<BreakSink
>,10> mBreakSinks
;
728 nsLineBreaker mLineBreaker
;
729 gfxTextRun
* mCurrentFramesAllSameTextRun
;
730 gfxContext
* mContext
;
731 nsIFrame
* mLineContainer
;
732 nsTextFrame
* mLastFrame
;
733 // The common ancestor of the current frame and the previous leaf frame
734 // on the line, or null if there was no previous leaf frame.
735 nsIFrame
* mCommonAncestorWithLastFrame
;
736 // mMaxTextLength is an upper bound on the size of the text in all mapped frames
737 PRUint32 mMaxTextLength
;
738 PRPackedBool mDoubleByteText
;
739 PRPackedBool mBidiEnabled
;
740 PRPackedBool mStartOfLine
;
741 PRPackedBool mTrimNextRunLeadingWhitespace
;
742 PRPackedBool mCurrentRunTrimLeadingWhitespace
;
743 PRPackedBool mSkipIncompleteTextRuns
;
744 PRPackedBool mCanStopOnThisLine
;
748 FindLineContainer(nsIFrame
* aFrame
)
750 while (aFrame
&& aFrame
->CanContinueTextRun()) {
751 aFrame
= aFrame
->GetParent();
757 TextContainsLineBreakerWhiteSpace(const void* aText
, PRUint32 aLength
,
758 PRBool aIsDoubleByte
)
762 const PRUnichar
* chars
= static_cast<const PRUnichar
*>(aText
);
763 for (i
= 0; i
< aLength
; ++i
) {
764 if (nsLineBreaker::IsSpace(chars
[i
]))
769 const PRUint8
* chars
= static_cast<const PRUint8
*>(aText
);
770 for (i
= 0; i
< aLength
; ++i
) {
771 if (nsLineBreaker::IsSpace(chars
[i
]))
778 struct FrameTextTraversal
{
779 nsIFrame
* mFrameToDescendInto
;
780 PRPackedBool mDescendIntoFrameSiblings
;
781 PRPackedBool mLineBreakerCanCrossFrameBoundary
;
782 PRPackedBool mTextRunCanCrossFrameBoundary
;
785 static FrameTextTraversal
786 CanTextCrossFrameBoundary(nsIFrame
* aFrame
, nsIAtom
* aType
)
788 NS_ASSERTION(aType
== aFrame
->GetType(), "Wrong type");
790 FrameTextTraversal result
;
792 PRBool continuesTextRun
= aFrame
->CanContinueTextRun();
793 if (aType
== nsGkAtoms::placeholderFrame
) {
794 // placeholders are "invisible", so a text run should be able to span
795 // across one. But don't descend into the out-of-flow.
796 result
.mLineBreakerCanCrossFrameBoundary
= PR_TRUE
;
797 if (continuesTextRun
) {
798 // ... Except for first-letter floats, which are really in-flow
799 // from the point of view of capitalization etc, so we'd better
800 // descend into them. But we actually need to break the textrun for
801 // first-letter floats since things look bad if, say, we try to make a
802 // ligature across the float boundary.
803 result
.mFrameToDescendInto
=
804 (static_cast<nsPlaceholderFrame
*>(aFrame
))->GetOutOfFlowFrame();
805 result
.mDescendIntoFrameSiblings
= PR_FALSE
;
806 result
.mTextRunCanCrossFrameBoundary
= PR_FALSE
;
808 result
.mFrameToDescendInto
= nsnull
;
809 result
.mTextRunCanCrossFrameBoundary
= PR_TRUE
;
812 if (continuesTextRun
) {
813 result
.mFrameToDescendInto
= aFrame
->GetFirstChild(nsnull
);
814 result
.mDescendIntoFrameSiblings
= PR_TRUE
;
815 result
.mTextRunCanCrossFrameBoundary
= PR_TRUE
;
816 result
.mLineBreakerCanCrossFrameBoundary
= PR_TRUE
;
818 result
.mFrameToDescendInto
= nsnull
;
819 result
.mTextRunCanCrossFrameBoundary
= PR_FALSE
;
820 result
.mLineBreakerCanCrossFrameBoundary
= PR_FALSE
;
826 BuildTextRunsScanner::FindBoundaryResult
827 BuildTextRunsScanner::FindBoundaries(nsIFrame
* aFrame
, FindBoundaryState
* aState
)
829 nsIAtom
* frameType
= aFrame
->GetType();
830 nsTextFrame
* textFrame
= frameType
== nsGkAtoms::textFrame
831 ? static_cast<nsTextFrame
*>(aFrame
) : nsnull
;
833 if (aState
->mLastTextFrame
&&
834 textFrame
!= aState
->mLastTextFrame
->GetNextInFlow() &&
835 !ContinueTextRunAcrossFrames(aState
->mLastTextFrame
, textFrame
)) {
836 aState
->mSeenTextRunBoundaryOnThisLine
= PR_TRUE
;
837 if (aState
->mSeenSpaceForLineBreakingOnThisLine
)
838 return FB_FOUND_VALID_TEXTRUN_BOUNDARY
;
840 if (!aState
->mFirstTextFrame
) {
841 aState
->mFirstTextFrame
= textFrame
;
843 aState
->mLastTextFrame
= textFrame
;
846 if (aFrame
== aState
->mStopAtFrame
)
847 return FB_STOPPED_AT_STOP_FRAME
;
850 if (!aState
->mSeenSpaceForLineBreakingOnThisLine
) {
851 const nsTextFragment
* frag
= textFrame
->GetContent()->GetText();
852 PRUint32 start
= textFrame
->GetContentOffset();
853 const void* text
= frag
->Is2b()
854 ? static_cast<const void*>(frag
->Get2b() + start
)
855 : static_cast<const void*>(frag
->Get1b() + start
);
856 if (TextContainsLineBreakerWhiteSpace(text
, textFrame
->GetContentLength(),
858 aState
->mSeenSpaceForLineBreakingOnThisLine
= PR_TRUE
;
859 if (aState
->mSeenTextRunBoundaryOnLaterLine
)
860 return FB_FOUND_VALID_TEXTRUN_BOUNDARY
;
866 FrameTextTraversal traversal
=
867 CanTextCrossFrameBoundary(aFrame
, frameType
);
868 if (!traversal
.mTextRunCanCrossFrameBoundary
) {
869 aState
->mSeenTextRunBoundaryOnThisLine
= PR_TRUE
;
870 if (aState
->mSeenSpaceForLineBreakingOnThisLine
)
871 return FB_FOUND_VALID_TEXTRUN_BOUNDARY
;
874 for (nsIFrame
* f
= traversal
.mFrameToDescendInto
; f
;
875 f
= f
->GetNextSibling()) {
876 FindBoundaryResult result
= FindBoundaries(f
, aState
);
877 if (result
!= FB_CONTINUE
)
879 if (!traversal
.mDescendIntoFrameSiblings
)
883 if (!traversal
.mTextRunCanCrossFrameBoundary
) {
884 aState
->mSeenTextRunBoundaryOnThisLine
= PR_TRUE
;
885 if (aState
->mSeenSpaceForLineBreakingOnThisLine
)
886 return FB_FOUND_VALID_TEXTRUN_BOUNDARY
;
892 // build text runs for the 200 lines following aForFrame, and stop after that
893 // when we get a chance.
894 #define NUM_LINES_TO_BUILD_TEXT_RUNS 200
897 * General routine for building text runs. This is hairy because of the need
898 * to build text runs that span content nodes.
900 * @param aForFrameLine the line containing aForFrame; if null, we'll figure
901 * out the line (slowly)
902 * @param aBlockFrame the block containing aForFrame; if null, we'll figure
903 * out the block (slowly)
906 BuildTextRuns(gfxContext
* aContext
, nsTextFrame
* aForFrame
,
907 nsIFrame
* aLineContainer
, const nsLineList::iterator
* aForFrameLine
)
909 NS_ASSERTION(aForFrame
|| (aForFrameLine
&& aLineContainer
),
910 "One of aForFrame or aForFrameLine+aLineContainer must be set!");
912 if (!aLineContainer
|| !aForFrameLine
) {
913 aLineContainer
= FindLineContainer(aForFrame
);
915 NS_ASSERTION(!aForFrame
|| aLineContainer
== FindLineContainer(aForFrame
), "Wrong line container hint");
918 nsPresContext
* presContext
= aLineContainer
->PresContext();
919 BuildTextRunsScanner
scanner(presContext
, aContext
, aLineContainer
);
921 nsBlockFrame
* block
= nsLayoutUtils::GetAsBlock(aLineContainer
);
924 NS_ASSERTION(!aLineContainer
->GetPrevInFlow() && !aLineContainer
->GetNextInFlow(),
925 "Breakable non-block line containers not supported");
926 // Just loop through all the children of the linecontainer ... it's really
928 scanner
.SetAtStartOfLine();
929 scanner
.SetCommonAncestorWithLastFrame(nsnull
);
930 nsIFrame
* child
= aLineContainer
->GetFirstChild(nsnull
);
932 scanner
.ScanFrame(child
);
933 child
= child
->GetNextSibling();
935 // Set mStartOfLine so FlushFrames knows its textrun ends a line
936 scanner
.SetAtStartOfLine();
937 scanner
.FlushFrames(PR_TRUE
, PR_FALSE
);
941 // Find the line containing aForFrame
943 PRBool isValid
= PR_TRUE
;
944 nsBlockInFlowLineIterator
backIterator(block
, &isValid
);
946 backIterator
= nsBlockInFlowLineIterator(block
, *aForFrameLine
, PR_FALSE
);
948 backIterator
= nsBlockInFlowLineIterator(block
, aForFrame
, &isValid
);
949 NS_ASSERTION(isValid
, "aForFrame not found in block, someone lied to us");
950 NS_ASSERTION(backIterator
.GetContainer() == block
,
951 "Someone lied to us about the block");
953 nsBlockFrame::line_iterator startLine
= backIterator
.GetLine();
955 // Find a line where we can start building text runs. We choose the last line
957 // -- there is a textrun boundary between the start of the line and the
958 // start of aForFrame
959 // -- there is a space between the start of the line and the textrun boundary
960 // (this is so we can be sure the line breaks will be set properly
961 // on the textruns we construct).
962 // The possibly-partial text runs up to and including the first space
963 // are not reconstructed. We construct partial text runs for that text ---
964 // for the sake of simplifying the code and feeding the linebreaker ---
965 // but we discard them instead of assigning them to frames.
966 // This is a little awkward because we traverse lines in the reverse direction
967 // but we traverse the frames in each line in the forward direction.
968 nsBlockInFlowLineIterator forwardIterator
= backIterator
;
969 nsTextFrame
* stopAtFrame
= aForFrame
;
970 nsTextFrame
* nextLineFirstTextFrame
= nsnull
;
971 PRBool seenTextRunBoundaryOnLaterLine
= PR_FALSE
;
972 PRBool mayBeginInTextRun
= PR_TRUE
;
974 forwardIterator
= backIterator
;
975 nsBlockFrame::line_iterator line
= backIterator
.GetLine();
976 if (!backIterator
.Prev() || backIterator
.GetLine()->IsBlock()) {
977 mayBeginInTextRun
= PR_FALSE
;
981 BuildTextRunsScanner::FindBoundaryState state
= { stopAtFrame
, nsnull
, nsnull
,
982 seenTextRunBoundaryOnLaterLine
, PR_FALSE
, PR_FALSE
};
983 nsIFrame
* child
= line
->mFirstChild
;
984 PRBool foundBoundary
= PR_FALSE
;
986 for (i
= line
->GetChildCount() - 1; i
>= 0; --i
) {
987 BuildTextRunsScanner::FindBoundaryResult result
=
988 scanner
.FindBoundaries(child
, &state
);
989 if (result
== BuildTextRunsScanner::FB_FOUND_VALID_TEXTRUN_BOUNDARY
) {
990 foundBoundary
= PR_TRUE
;
992 } else if (result
== BuildTextRunsScanner::FB_STOPPED_AT_STOP_FRAME
) {
995 child
= child
->GetNextSibling();
999 if (!stopAtFrame
&& state
.mLastTextFrame
&& nextLineFirstTextFrame
&&
1000 !scanner
.ContinueTextRunAcrossFrames(state
.mLastTextFrame
, nextLineFirstTextFrame
)) {
1001 // Found a usable textrun boundary at the end of the line
1002 if (state
.mSeenSpaceForLineBreakingOnThisLine
)
1004 seenTextRunBoundaryOnLaterLine
= PR_TRUE
;
1005 } else if (state
.mSeenTextRunBoundaryOnThisLine
) {
1006 seenTextRunBoundaryOnLaterLine
= PR_TRUE
;
1008 stopAtFrame
= nsnull
;
1009 if (state
.mFirstTextFrame
) {
1010 nextLineFirstTextFrame
= state
.mFirstTextFrame
;
1013 scanner
.SetSkipIncompleteTextRuns(mayBeginInTextRun
);
1015 // Now iterate over all text frames starting from the current line. First-in-flow
1016 // text frames will be accumulated into textRunFrames as we go. When a
1017 // text run boundary is required we flush textRunFrames ((re)building their
1018 // gfxTextRuns as necessary).
1019 PRBool seenStartLine
= PR_FALSE
;
1020 PRUint32 linesAfterStartLine
= 0;
1022 nsBlockFrame::line_iterator line
= forwardIterator
.GetLine();
1023 if (line
->IsBlock())
1025 line
->SetInvalidateTextRuns(PR_FALSE
);
1026 scanner
.SetAtStartOfLine();
1027 scanner
.SetCommonAncestorWithLastFrame(nsnull
);
1028 nsIFrame
* child
= line
->mFirstChild
;
1030 for (i
= line
->GetChildCount() - 1; i
>= 0; --i
) {
1031 scanner
.ScanFrame(child
);
1032 child
= child
->GetNextSibling();
1034 if (line
.get() == startLine
.get()) {
1035 seenStartLine
= PR_TRUE
;
1037 if (seenStartLine
) {
1038 ++linesAfterStartLine
;
1039 if (linesAfterStartLine
>= NUM_LINES_TO_BUILD_TEXT_RUNS
&& scanner
.CanStopOnThisLine()) {
1040 // Don't flush; we may be in the middle of a textrun that we can't
1041 // end here. That's OK, we just won't build it.
1042 // Note that we must already have finished the textrun for aForFrame,
1043 // because we've seen the end of a textrun in a line after the line
1044 // containing aForFrame.
1045 scanner
.ResetLineBreaker();
1049 } while (forwardIterator
.Next());
1051 // Set mStartOfLine so FlushFrames knows its textrun ends a line
1052 scanner
.SetAtStartOfLine();
1053 scanner
.FlushFrames(PR_TRUE
, PR_FALSE
);
1057 ExpandBuffer(PRUnichar
* aDest
, PRUint8
* aSrc
, PRUint32 aCount
)
1068 PRBool
BuildTextRunsScanner::IsTextRunValidForMappedFlows(gfxTextRun
* aTextRun
)
1070 if (aTextRun
->GetFlags() & nsTextFrameUtils::TEXT_IS_SIMPLE_FLOW
)
1071 return mMappedFlows
.Length() == 1 &&
1072 mMappedFlows
[0].mStartFrame
== static_cast<nsTextFrame
*>(aTextRun
->GetUserData()) &&
1073 mMappedFlows
[0].mEndFrame
== nsnull
;
1075 TextRunUserData
* userData
= static_cast<TextRunUserData
*>(aTextRun
->GetUserData());
1076 if (userData
->mMappedFlowCount
!= PRInt32(mMappedFlows
.Length()))
1079 for (i
= 0; i
< mMappedFlows
.Length(); ++i
) {
1080 if (userData
->mMappedFlows
[i
].mStartFrame
!= mMappedFlows
[i
].mStartFrame
||
1081 PRInt32(userData
->mMappedFlows
[i
].mContentLength
) !=
1082 mMappedFlows
[i
].GetContentEnd() - mMappedFlows
[i
].mStartFrame
->GetContentOffset())
1089 * This gets called when we need to make a text run for the current list of
1092 void BuildTextRunsScanner::FlushFrames(PRBool aFlushLineBreaks
, PRBool aSuppressTrailingBreak
)
1094 if (mMappedFlows
.Length() == 0)
1097 gfxTextRun
* textRun
;
1098 if (!mSkipIncompleteTextRuns
&& mCurrentFramesAllSameTextRun
&&
1099 ((mCurrentFramesAllSameTextRun
->GetFlags() & nsTextFrameUtils::TEXT_INCOMING_WHITESPACE
) != 0) ==
1100 mCurrentRunTrimLeadingWhitespace
&&
1101 IsTextRunValidForMappedFlows(mCurrentFramesAllSameTextRun
)) {
1102 // Optimization: We do not need to (re)build the textrun.
1103 textRun
= mCurrentFramesAllSameTextRun
;
1105 // Feed this run's text into the linebreaker to provide context. This also
1106 // updates mTrimNextRunLeadingWhitespace appropriately.
1107 SetupBreakSinksForTextRun(textRun
, PR_TRUE
, PR_FALSE
);
1108 mTrimNextRunLeadingWhitespace
=
1109 (textRun
->GetFlags() & nsTextFrameUtils::TEXT_TRAILING_WHITESPACE
) != 0;
1111 nsAutoTArray
<PRUint8
,BIG_TEXT_NODE_SIZE
> buffer
;
1112 if (!buffer
.AppendElements(mMaxTextLength
*(mDoubleByteText
? 2 : 1)))
1114 textRun
= BuildTextRunForFrames(buffer
.Elements());
1117 if (aFlushLineBreaks
) {
1118 PRBool trailingLineBreak
;
1119 nsresult rv
= mLineBreaker
.Reset(&trailingLineBreak
);
1120 // textRun may be null for various reasons, including because we constructed
1121 // a partial textrun just to get the linebreaker and other state set up
1122 // to build the next textrun.
1123 if (NS_SUCCEEDED(rv
) && trailingLineBreak
&& textRun
&& !aSuppressTrailingBreak
) {
1124 textRun
->SetFlagBits(nsTextFrameUtils::TEXT_HAS_TRAILING_BREAK
);
1127 for (i
= 0; i
< mBreakSinks
.Length(); ++i
) {
1128 if (!mBreakSinks
[i
]->mExistingTextRun
|| mBreakSinks
[i
]->mChangedBreaks
) {
1129 // TODO cause frames associated with the textrun to be reflowed, if they
1130 // aren't being reflowed already!
1133 mBreakSinks
.Clear();
1136 mCanStopOnThisLine
= PR_TRUE
;
1140 void BuildTextRunsScanner::AccumulateRunInfo(nsTextFrame
* aFrame
)
1142 NS_ASSERTION(mMaxTextLength
<= mMaxTextLength
+ aFrame
->GetContentLength(), "integer overflow");
1143 mMaxTextLength
+= aFrame
->GetContentLength();
1144 mDoubleByteText
|= aFrame
->GetContent()->GetText()->Is2b();
1145 mLastFrame
= aFrame
;
1146 mCommonAncestorWithLastFrame
= aFrame
->GetParent();
1148 MappedFlow
* mappedFlow
= &mMappedFlows
[mMappedFlows
.Length() - 1];
1149 NS_ASSERTION(mappedFlow
->mStartFrame
== aFrame
||
1150 mappedFlow
->GetContentEnd() == aFrame
->GetContentOffset(),
1151 "Overlapping or discontiguous frames => BAD");
1152 mappedFlow
->mEndFrame
= static_cast<nsTextFrame
*>(aFrame
->GetNextContinuation());
1153 if (mCurrentFramesAllSameTextRun
!= aFrame
->GetTextRun()) {
1154 mCurrentFramesAllSameTextRun
= nsnull
;
1158 mLineBreakBeforeFrames
.AppendElement(aFrame
);
1159 mStartOfLine
= PR_FALSE
;
1163 static nscoord
StyleToCoord(const nsStyleCoord
& aCoord
)
1165 if (eStyleUnit_Coord
== aCoord
.GetUnit()) {
1166 return aCoord
.GetCoordValue();
1173 HasTerminalNewline(const nsTextFrame
* aFrame
)
1175 if (aFrame
->GetContentLength() == 0)
1177 const nsTextFragment
* frag
= aFrame
->GetContent()->GetText();
1178 return frag
->CharAt(aFrame
->GetContentEnd() - 1) == '\n';
1182 BuildTextRunsScanner::ContinueTextRunAcrossFrames(nsTextFrame
* aFrame1
, nsTextFrame
* aFrame2
)
1185 NS_GET_EMBEDDING_LEVEL(aFrame1
) != NS_GET_EMBEDDING_LEVEL(aFrame2
))
1188 nsStyleContext
* sc1
= aFrame1
->GetStyleContext();
1189 const nsStyleText
* textStyle1
= sc1
->GetStyleText();
1190 // If the first frame ends in a preformatted newline, then we end the textrun
1191 // here. This avoids creating giant textruns for an entire plain text file.
1192 // Note that we create a single text frame for a preformatted text node,
1193 // even if it has newlines in it, so typically we won't see trailing newlines
1194 // until after reflow has broken up the frame into one (or more) frames per
1195 // line. That's OK though.
1196 if (textStyle1
->NewlineIsSignificant() && HasTerminalNewline(aFrame1
))
1199 if (aFrame1
->GetContent() == aFrame2
->GetContent() &&
1200 aFrame1
->GetNextInFlow() != aFrame2
) {
1201 // aFrame2 must be a non-fluid continuation of aFrame1. This can happen
1202 // sometimes when the unicode-bidi property is used; the bidi resolver
1203 // breaks text into different frames even though the text has the same
1204 // direction. We can't allow these two frames to share the same textrun
1205 // because that would violate our invariant that two flows in the same
1206 // textrun have different content elements.
1210 nsStyleContext
* sc2
= aFrame2
->GetStyleContext();
1213 const nsStyleFont
* fontStyle1
= sc1
->GetStyleFont();
1214 const nsStyleFont
* fontStyle2
= sc2
->GetStyleFont();
1215 const nsStyleText
* textStyle2
= sc2
->GetStyleText();
1216 return fontStyle1
->mFont
.BaseEquals(fontStyle2
->mFont
) &&
1217 sc1
->GetStyleVisibility()->mLangGroup
== sc2
->GetStyleVisibility()->mLangGroup
&&
1218 nsLayoutUtils::GetTextRunFlagsForStyle(sc1
, textStyle1
, fontStyle1
) ==
1219 nsLayoutUtils::GetTextRunFlagsForStyle(sc2
, textStyle2
, fontStyle2
);
1222 void BuildTextRunsScanner::ScanFrame(nsIFrame
* aFrame
)
1224 // First check if we can extend the current mapped frame block. This is common.
1225 if (mMappedFlows
.Length() > 0) {
1226 MappedFlow
* mappedFlow
= &mMappedFlows
[mMappedFlows
.Length() - 1];
1227 if (mappedFlow
->mEndFrame
== aFrame
&&
1228 (aFrame
->GetStateBits() & NS_FRAME_IS_FLUID_CONTINUATION
)) {
1229 NS_ASSERTION(aFrame
->GetType() == nsGkAtoms::textFrame
,
1230 "Flow-sibling of a text frame is not a text frame?");
1232 // Don't do this optimization if mLastFrame has a terminal newline...
1233 // it's quite likely preformatted and we might want to end the textrun here.
1234 // This is almost always true:
1235 if (mLastFrame
->GetStyleContext() == aFrame
->GetStyleContext() &&
1236 !HasTerminalNewline(mLastFrame
)) {
1237 AccumulateRunInfo(static_cast<nsTextFrame
*>(aFrame
));
1243 nsIAtom
* frameType
= aFrame
->GetType();
1244 // Now see if we can add a new set of frames to the current textrun
1245 if (frameType
== nsGkAtoms::textFrame
) {
1246 nsTextFrame
* frame
= static_cast<nsTextFrame
*>(aFrame
);
1249 if (!ContinueTextRunAcrossFrames(mLastFrame
, frame
)) {
1250 FlushFrames(PR_FALSE
, PR_FALSE
);
1252 if (mLastFrame
->GetContent() == frame
->GetContent()) {
1253 AccumulateRunInfo(frame
);
1259 MappedFlow
* mappedFlow
= mMappedFlows
.AppendElement();
1263 mappedFlow
->mStartFrame
= frame
;
1264 mappedFlow
->mAncestorControllingInitialBreak
= mCommonAncestorWithLastFrame
;
1266 AccumulateRunInfo(frame
);
1267 if (mMappedFlows
.Length() == 1) {
1268 mCurrentFramesAllSameTextRun
= frame
->GetTextRun();
1269 mCurrentRunTrimLeadingWhitespace
= mTrimNextRunLeadingWhitespace
;
1274 FrameTextTraversal traversal
=
1275 CanTextCrossFrameBoundary(aFrame
, frameType
);
1276 PRBool isBR
= frameType
== nsGkAtoms::brFrame
;
1277 if (!traversal
.mLineBreakerCanCrossFrameBoundary
) {
1278 // BR frames are special. We do not need or want to record a break opportunity
1279 // before a BR frame.
1280 FlushFrames(PR_TRUE
, isBR
);
1281 mCommonAncestorWithLastFrame
= aFrame
;
1282 mTrimNextRunLeadingWhitespace
= PR_FALSE
;
1283 mStartOfLine
= PR_FALSE
;
1284 } else if (!traversal
.mTextRunCanCrossFrameBoundary
) {
1285 FlushFrames(PR_FALSE
, PR_FALSE
);
1288 for (nsIFrame
* f
= traversal
.mFrameToDescendInto
; f
;
1289 f
= f
->GetNextSibling()) {
1291 if (!traversal
.mDescendIntoFrameSiblings
)
1295 if (!traversal
.mLineBreakerCanCrossFrameBoundary
) {
1296 // Really if we're a BR frame this is unnecessary since descendInto will be
1297 // false. In fact this whole "if" statement should move into the descendInto.
1298 FlushFrames(PR_TRUE
, isBR
);
1299 mCommonAncestorWithLastFrame
= aFrame
;
1300 mTrimNextRunLeadingWhitespace
= PR_FALSE
;
1301 } else if (!traversal
.mTextRunCanCrossFrameBoundary
) {
1302 FlushFrames(PR_FALSE
, PR_FALSE
);
1305 LiftCommonAncestorWithLastFrameToParent(aFrame
->GetParent());
1309 BuildTextRunsScanner::GetNextBreakBeforeFrame(PRUint32
* aIndex
)
1311 PRUint32 index
= *aIndex
;
1312 if (index
>= mLineBreakBeforeFrames
.Length())
1314 *aIndex
= index
+ 1;
1315 return static_cast<nsTextFrame
*>(mLineBreakBeforeFrames
.ElementAt(index
));
1319 GetSpacingFlags(const nsStyleCoord
& aStyleCoord
)
1321 nscoord spacing
= StyleToCoord(aStyleCoord
);
1325 return gfxTextRunFactory::TEXT_ENABLE_SPACING
;
1326 return gfxTextRunFactory::TEXT_ENABLE_SPACING
|
1327 gfxTextRunFactory::TEXT_ENABLE_NEGATIVE_SPACING
;
1330 static gfxFontGroup
*
1331 GetFontGroupForFrame(nsIFrame
* aFrame
,
1332 nsIFontMetrics
** aOutFontMetrics
= nsnull
)
1334 if (aOutFontMetrics
)
1335 *aOutFontMetrics
= nsnull
;
1337 nsCOMPtr
<nsIFontMetrics
> metrics
;
1338 nsLayoutUtils::GetFontMetricsForFrame(aFrame
, getter_AddRefs(metrics
));
1343 nsIFontMetrics
* metricsRaw
= metrics
;
1344 if (aOutFontMetrics
) {
1345 *aOutFontMetrics
= metricsRaw
;
1346 NS_ADDREF(*aOutFontMetrics
);
1348 nsIThebesFontMetrics
* fm
= static_cast<nsIThebesFontMetrics
*>(metricsRaw
);
1349 // XXX this is a bit bogus, we're releasing 'metrics' so the returned font-group
1350 // might actually be torn down, although because of the way the device context
1351 // caches font metrics, this seems to not actually happen. But we should fix
1353 return fm
->GetThebesFontGroup();
1356 static already_AddRefed
<gfxContext
>
1357 GetReferenceRenderingContext(nsTextFrame
* aTextFrame
, nsIRenderingContext
* aRC
)
1359 nsCOMPtr
<nsIRenderingContext
> tmp
= aRC
;
1361 nsresult rv
= aTextFrame
->PresContext()->PresShell()->
1362 CreateRenderingContext(aTextFrame
, getter_AddRefs(tmp
));
1367 gfxContext
* ctx
= tmp
->ThebesContext();
1373 * The returned textrun must be released via gfxTextRunCache::ReleaseTextRun
1374 * or gfxTextRunCache::AutoTextRun.
1377 GetHyphenTextRun(gfxTextRun
* aTextRun
, gfxContext
* aContext
, nsTextFrame
* aTextFrame
)
1379 nsRefPtr
<gfxContext
> ctx
= aContext
;
1381 ctx
= GetReferenceRenderingContext(aTextFrame
, nsnull
);
1386 gfxFontGroup
* fontGroup
= aTextRun
->GetFontGroup();
1387 PRUint32 flags
= gfxFontGroup::TEXT_IS_PERSISTENT
;
1389 static const PRUnichar unicodeHyphen
= 0x2010;
1390 gfxTextRun
* textRun
=
1391 gfxTextRunCache::MakeTextRun(&unicodeHyphen
, 1, fontGroup
, ctx
,
1392 aTextRun
->GetAppUnitsPerDevUnit(), flags
);
1393 if (textRun
&& textRun
->CountMissingGlyphs() == 0)
1396 gfxTextRunCache::ReleaseTextRun(textRun
);
1398 static const PRUint8 dash
= '-';
1399 return gfxTextRunCache::MakeTextRun(&dash
, 1, fontGroup
, ctx
,
1400 aTextRun
->GetAppUnitsPerDevUnit(),
1404 static gfxFont::Metrics
1405 GetFirstFontMetrics(gfxFontGroup
* aFontGroup
)
1408 return gfxFont::Metrics();
1409 gfxFont
* font
= aFontGroup
->GetFontAt(0);
1411 return gfxFont::Metrics();
1412 return font
->GetMetrics();
1415 PR_STATIC_ASSERT(NS_STYLE_WHITESPACE_NORMAL
== 0);
1416 PR_STATIC_ASSERT(NS_STYLE_WHITESPACE_PRE
== 1);
1417 PR_STATIC_ASSERT(NS_STYLE_WHITESPACE_NOWRAP
== 2);
1418 PR_STATIC_ASSERT(NS_STYLE_WHITESPACE_PRE_WRAP
== 3);
1419 PR_STATIC_ASSERT(NS_STYLE_WHITESPACE_PRE_LINE
== 4);
1421 static const nsTextFrameUtils::CompressionMode CSSWhitespaceToCompressionMode
[] =
1423 nsTextFrameUtils::COMPRESS_WHITESPACE_NEWLINE
, // normal
1424 nsTextFrameUtils::COMPRESS_NONE
, // pre
1425 nsTextFrameUtils::COMPRESS_WHITESPACE_NEWLINE
, // nowrap
1426 nsTextFrameUtils::COMPRESS_NONE
, // pre-wrap
1427 nsTextFrameUtils::COMPRESS_WHITESPACE
// pre-line
1431 BuildTextRunsScanner::BuildTextRunForFrames(void* aTextBuffer
)
1433 gfxSkipCharsBuilder builder
;
1435 const void* textPtr
= aTextBuffer
;
1436 PRBool anySmallcapsStyle
= PR_FALSE
;
1437 PRBool anyTextTransformStyle
= PR_FALSE
;
1438 nsIContent
* lastContent
= nsnull
;
1439 PRInt32 endOfLastContent
= 0;
1440 PRUint32 textFlags
= nsTextFrameUtils::TEXT_NO_BREAKS
;
1442 if (mCurrentRunTrimLeadingWhitespace
) {
1443 textFlags
|= nsTextFrameUtils::TEXT_INCOMING_WHITESPACE
;
1446 nsAutoTArray
<PRInt32
,50> textBreakPoints
;
1447 TextRunUserData dummyData
;
1448 TextRunMappedFlow dummyMappedFlow
;
1450 TextRunUserData
* userData
;
1451 // If the situation is particularly simple (and common) we don't need to
1452 // allocate userData.
1453 if (mMappedFlows
.Length() == 1 && !mMappedFlows
[0].mEndFrame
&&
1454 mMappedFlows
[0].mStartFrame
->GetContentOffset() == 0) {
1455 userData
= &dummyData
;
1456 dummyData
.mMappedFlows
= &dummyMappedFlow
;
1458 userData
= static_cast<TextRunUserData
*>
1459 (nsMemory::Alloc(sizeof(TextRunUserData
) + mMappedFlows
.Length()*sizeof(TextRunMappedFlow
)));
1460 userData
->mMappedFlows
= reinterpret_cast<TextRunMappedFlow
*>(userData
+ 1);
1462 userData
->mMappedFlowCount
= mMappedFlows
.Length();
1463 userData
->mLastFlowIndex
= 0;
1465 PRUint32 currentTransformedTextOffset
= 0;
1467 PRUint32 nextBreakIndex
= 0;
1468 nsTextFrame
* nextBreakBeforeFrame
= GetNextBreakBeforeFrame(&nextBreakIndex
);
1469 PRBool enabledJustification
= mLineContainer
&&
1470 mLineContainer
->GetStyleText()->mTextAlign
== NS_STYLE_TEXT_ALIGN_JUSTIFY
;
1473 const nsStyleText
* textStyle
= nsnull
;
1474 const nsStyleFont
* fontStyle
= nsnull
;
1475 nsStyleContext
* lastStyleContext
= nsnull
;
1476 for (i
= 0; i
< mMappedFlows
.Length(); ++i
) {
1477 MappedFlow
* mappedFlow
= &mMappedFlows
[i
];
1478 nsTextFrame
* f
= mappedFlow
->mStartFrame
;
1480 lastStyleContext
= f
->GetStyleContext();
1481 // Detect use of text-transform or font-variant anywhere in the run
1482 textStyle
= f
->GetStyleText();
1483 if (NS_STYLE_TEXT_TRANSFORM_NONE
!= textStyle
->mTextTransform
) {
1484 anyTextTransformStyle
= PR_TRUE
;
1486 textFlags
|= GetSpacingFlags(textStyle
->mLetterSpacing
);
1487 textFlags
|= GetSpacingFlags(textStyle
->mWordSpacing
);
1488 nsTextFrameUtils::CompressionMode compression
=
1489 CSSWhitespaceToCompressionMode
[textStyle
->mWhiteSpace
];
1490 if (enabledJustification
&& !textStyle
->WhiteSpaceIsSignificant()) {
1491 textFlags
|= gfxTextRunFactory::TEXT_ENABLE_SPACING
;
1493 fontStyle
= f
->GetStyleFont();
1494 if (NS_STYLE_FONT_VARIANT_SMALL_CAPS
== fontStyle
->mFont
.variant
) {
1495 anySmallcapsStyle
= PR_TRUE
;
1498 // Figure out what content is included in this flow.
1499 nsIContent
* content
= f
->GetContent();
1500 const nsTextFragment
* frag
= content
->GetText();
1501 PRInt32 contentStart
= mappedFlow
->mStartFrame
->GetContentOffset();
1502 PRInt32 contentEnd
= mappedFlow
->GetContentEnd();
1503 PRInt32 contentLength
= contentEnd
- contentStart
;
1505 TextRunMappedFlow
* newFlow
= &userData
->mMappedFlows
[i
];
1506 newFlow
->mStartFrame
= mappedFlow
->mStartFrame
;
1507 newFlow
->mDOMOffsetToBeforeTransformOffset
= builder
.GetCharCount() -
1508 mappedFlow
->mStartFrame
->GetContentOffset();
1509 newFlow
->mContentLength
= contentLength
;
1511 while (nextBreakBeforeFrame
&& nextBreakBeforeFrame
->GetContent() == content
) {
1512 textBreakPoints
.AppendElement(
1513 nextBreakBeforeFrame
->GetContentOffset() + newFlow
->mDOMOffsetToBeforeTransformOffset
);
1514 nextBreakBeforeFrame
= GetNextBreakBeforeFrame(&nextBreakIndex
);
1517 PRUint32 analysisFlags
;
1519 NS_ASSERTION(mDoubleByteText
, "Wrong buffer char size!");
1520 PRUnichar
* bufStart
= static_cast<PRUnichar
*>(aTextBuffer
);
1521 PRUnichar
* bufEnd
= nsTextFrameUtils::TransformText(
1522 frag
->Get2b() + contentStart
, contentLength
, bufStart
,
1523 compression
, &mTrimNextRunLeadingWhitespace
, &builder
, &analysisFlags
);
1524 aTextBuffer
= bufEnd
;
1526 if (mDoubleByteText
) {
1527 // Need to expand the text. First transform it into a temporary buffer,
1529 nsAutoTArray
<PRUint8
,BIG_TEXT_NODE_SIZE
> tempBuf
;
1530 if (!tempBuf
.AppendElements(contentLength
)) {
1531 DestroyUserData(userData
);
1534 PRUint8
* bufStart
= tempBuf
.Elements();
1535 PRUint8
* end
= nsTextFrameUtils::TransformText(
1536 reinterpret_cast<const PRUint8
*>(frag
->Get1b()) + contentStart
, contentLength
,
1537 bufStart
, compression
, &mTrimNextRunLeadingWhitespace
,
1538 &builder
, &analysisFlags
);
1539 aTextBuffer
= ExpandBuffer(static_cast<PRUnichar
*>(aTextBuffer
),
1540 tempBuf
.Elements(), end
- tempBuf
.Elements());
1542 PRUint8
* bufStart
= static_cast<PRUint8
*>(aTextBuffer
);
1543 PRUint8
* end
= nsTextFrameUtils::TransformText(
1544 reinterpret_cast<const PRUint8
*>(frag
->Get1b()) + contentStart
, contentLength
,
1546 compression
, &mTrimNextRunLeadingWhitespace
, &builder
, &analysisFlags
);
1550 textFlags
|= analysisFlags
;
1552 currentTransformedTextOffset
=
1553 (static_cast<const PRUint8
*>(aTextBuffer
) - static_cast<const PRUint8
*>(textPtr
)) >> mDoubleByteText
;
1555 lastContent
= content
;
1556 endOfLastContent
= contentEnd
;
1559 // Check for out-of-memory in gfxSkipCharsBuilder
1560 if (!builder
.IsOK()) {
1561 DestroyUserData(userData
);
1565 void* finalUserData
;
1566 if (userData
== &dummyData
) {
1567 textFlags
|= nsTextFrameUtils::TEXT_IS_SIMPLE_FLOW
;
1569 finalUserData
= mMappedFlows
[0].mStartFrame
;
1571 finalUserData
= userData
;
1574 PRUint32 transformedLength
= currentTransformedTextOffset
;
1576 // Disable this because it breaks the word cache. Disable at least until
1577 // we have a CharacterDataWillChange notification.
1579 // if (!(textFlags & nsTextFrameUtils::TEXT_WAS_TRANSFORMED) &&
1580 // mMappedFlows.Length() == 1) {
1581 // // The textrun maps one continuous, unmodified run of DOM text. It can
1582 // // point to the DOM text directly.
1583 // const nsTextFragment* frag = lastContent->GetText();
1584 // if (frag->Is2b()) {
1585 // textPtr = frag->Get2b() + mMappedFlows[0].mContentOffset;
1587 // textPtr = frag->Get1b() + mMappedFlows[0].mContentOffset;
1589 // textFlags |= gfxTextRunFactory::TEXT_IS_PERSISTENT;
1592 // Now build the textrun
1593 nsTextFrame
* firstFrame
= mMappedFlows
[0].mStartFrame
;
1594 gfxFontGroup
* fontGroup
= GetFontGroupForFrame(firstFrame
);
1596 DestroyUserData(userData
);
1600 if (textFlags
& nsTextFrameUtils::TEXT_HAS_TAB
) {
1601 textFlags
|= gfxTextRunFactory::TEXT_ENABLE_SPACING
;
1603 if (textFlags
& nsTextFrameUtils::TEXT_HAS_SHY
) {
1604 textFlags
|= gfxTextRunFactory::TEXT_ENABLE_HYPHEN_BREAKS
;
1606 if (mBidiEnabled
&& (NS_GET_EMBEDDING_LEVEL(firstFrame
) & 1)) {
1607 textFlags
|= gfxTextRunFactory::TEXT_IS_RTL
;
1609 if (mTrimNextRunLeadingWhitespace
) {
1610 textFlags
|= nsTextFrameUtils::TEXT_TRAILING_WHITESPACE
;
1612 // ContinueTextRunAcrossFrames guarantees that it doesn't matter which
1613 // frame's style is used, so use the last frame's
1614 textFlags
|= nsLayoutUtils::GetTextRunFlagsForStyle(lastStyleContext
,
1615 textStyle
, fontStyle
);
1616 // XXX this is a bit of a hack. For performance reasons, if we're favouring
1617 // performance over quality, don't try to get accurate glyph extents.
1618 if (!(textFlags
& gfxTextRunFactory::TEXT_OPTIMIZE_SPEED
)) {
1619 textFlags
|= gfxTextRunFactory::TEXT_NEED_BOUNDING_BOX
;
1622 gfxSkipChars skipChars
;
1623 skipChars
.TakeFrom(&builder
);
1624 // Convert linebreak coordinates to transformed string offsets
1625 NS_ASSERTION(nextBreakIndex
== mLineBreakBeforeFrames
.Length(),
1626 "Didn't find all the frames to break-before...");
1627 gfxSkipCharsIterator
iter(skipChars
);
1628 nsAutoTArray
<PRUint32
,50> textBreakPointsAfterTransform
;
1629 for (i
= 0; i
< textBreakPoints
.Length(); ++i
) {
1630 nsTextFrameUtils::AppendLineBreakOffset(&textBreakPointsAfterTransform
,
1631 iter
.ConvertOriginalToSkipped(textBreakPoints
[i
]));
1634 nsTextFrameUtils::AppendLineBreakOffset(&textBreakPointsAfterTransform
,
1638 // Setup factory chain
1639 nsAutoPtr
<nsTransformingTextRunFactory
> transformingFactory
;
1640 if (anySmallcapsStyle
) {
1641 transformingFactory
= new nsFontVariantTextRunFactory();
1643 if (anyTextTransformStyle
) {
1644 transformingFactory
=
1645 new nsCaseTransformTextRunFactory(transformingFactory
.forget());
1647 nsTArray
<nsStyleContext
*> styles
;
1648 if (transformingFactory
) {
1649 iter
.SetOriginalOffset(0);
1650 for (i
= 0; i
< mMappedFlows
.Length(); ++i
) {
1651 MappedFlow
* mappedFlow
= &mMappedFlows
[i
];
1653 for (f
= mappedFlow
->mStartFrame
; f
!= mappedFlow
->mEndFrame
;
1654 f
= static_cast<nsTextFrame
*>(f
->GetNextContinuation())) {
1655 PRUint32 offset
= iter
.GetSkippedOffset();
1656 iter
.AdvanceOriginal(f
->GetContentLength());
1657 PRUint32 end
= iter
.GetSkippedOffset();
1658 nsStyleContext
* sc
= f
->GetStyleContext();
1660 for (j
= offset
; j
< end
; ++j
) {
1661 styles
.AppendElement(sc
);
1665 textFlags
|= nsTextFrameUtils::TEXT_IS_TRANSFORMED
;
1666 NS_ASSERTION(iter
.GetSkippedOffset() == transformedLength
,
1667 "We didn't cover all the characters in the text run!");
1670 gfxTextRun
* textRun
;
1671 gfxTextRunFactory::Parameters params
=
1672 { mContext
, finalUserData
, &skipChars
,
1673 textBreakPointsAfterTransform
.Elements(), textBreakPointsAfterTransform
.Length(),
1674 firstFrame
->PresContext()->AppUnitsPerDevPixel() };
1676 if (mDoubleByteText
) {
1677 const PRUnichar
* text
= static_cast<const PRUnichar
*>(textPtr
);
1678 if (transformingFactory
) {
1679 textRun
= transformingFactory
->MakeTextRun(text
, transformedLength
, ¶ms
,
1680 fontGroup
, textFlags
, styles
.Elements());
1682 // ownership of the factory has passed to the textrun
1683 transformingFactory
.forget();
1686 textRun
= MakeTextRun(text
, transformedLength
, fontGroup
, ¶ms
, textFlags
);
1689 const PRUint8
* text
= static_cast<const PRUint8
*>(textPtr
);
1690 textFlags
|= gfxFontGroup::TEXT_IS_8BIT
;
1691 if (transformingFactory
) {
1692 textRun
= transformingFactory
->MakeTextRun(text
, transformedLength
, ¶ms
,
1693 fontGroup
, textFlags
, styles
.Elements());
1695 // ownership of the factory has passed to the textrun
1696 transformingFactory
.forget();
1699 textRun
= MakeTextRun(text
, transformedLength
, fontGroup
, ¶ms
, textFlags
);
1703 DestroyUserData(userData
);
1707 // We have to set these up after we've created the textrun, because
1708 // the breaks may be stored in the textrun during this very call.
1709 // This is a bit annoying because it requires another loop over the frames
1710 // making up the textrun, but I don't see a way to avoid this.
1711 SetupBreakSinksForTextRun(textRun
, PR_FALSE
, mSkipIncompleteTextRuns
);
1713 if (mSkipIncompleteTextRuns
) {
1714 mSkipIncompleteTextRuns
= !TextContainsLineBreakerWhiteSpace(textPtr
,
1715 transformedLength
, mDoubleByteText
);
1718 gTextRuns
->RemoveFromCache(textRun
);
1720 DestroyUserData(userData
);
1724 // Actually wipe out the textruns associated with the mapped frames and associate
1725 // those frames with this text run.
1726 AssignTextRun(textRun
);
1731 HasCompressedLeadingWhitespace(nsTextFrame
* aFrame
, const nsStyleText
* aStyleText
,
1732 PRInt32 aContentEndOffset
,
1733 const gfxSkipCharsIterator
& aIterator
)
1735 if (!aIterator
.IsOriginalCharSkipped())
1738 gfxSkipCharsIterator iter
= aIterator
;
1739 PRInt32 frameContentOffset
= aFrame
->GetContentOffset();
1740 const nsTextFragment
* frag
= aFrame
->GetContent()->GetText();
1741 while (frameContentOffset
< aContentEndOffset
&& iter
.IsOriginalCharSkipped()) {
1742 if (IsTrimmableSpace(frag
, frameContentOffset
, aStyleText
))
1744 ++frameContentOffset
;
1745 iter
.AdvanceOriginal(1);
1751 BuildTextRunsScanner::SetupBreakSinksForTextRun(gfxTextRun
* aTextRun
,
1752 PRBool aIsExistingTextRun
,
1753 PRBool aSuppressSink
)
1755 // textruns have uniform language
1756 nsIAtom
* lang
= mMappedFlows
[0].mStartFrame
->GetStyleVisibility()->mLangGroup
;
1757 // We keep this pointed at the skip-chars data for the current mappedFlow.
1758 // This lets us cheaply check whether the flow has compressed initial
1760 gfxSkipCharsIterator
iter(aTextRun
->GetSkipChars());
1763 for (i
= 0; i
< mMappedFlows
.Length(); ++i
) {
1764 MappedFlow
* mappedFlow
= &mMappedFlows
[i
];
1765 PRUint32 offset
= iter
.GetSkippedOffset();
1766 gfxSkipCharsIterator iterNext
= iter
;
1767 iterNext
.AdvanceOriginal(mappedFlow
->GetContentEnd() -
1768 mappedFlow
->mStartFrame
->GetContentOffset());
1770 nsAutoPtr
<BreakSink
>* breakSink
= mBreakSinks
.AppendElement(
1771 new BreakSink(aTextRun
, mContext
, offset
, aIsExistingTextRun
));
1772 if (!breakSink
|| !*breakSink
)
1775 PRUint32 length
= iterNext
.GetSkippedOffset() - offset
;
1777 nsIFrame
* initialBreakController
= mappedFlow
->mAncestorControllingInitialBreak
;
1778 if (!initialBreakController
) {
1779 initialBreakController
= mLineContainer
;
1781 if (!initialBreakController
->GetStyleText()->WhiteSpaceCanWrap()) {
1782 flags
|= nsLineBreaker::BREAK_SUPPRESS_INITIAL
;
1784 nsTextFrame
* startFrame
= mappedFlow
->mStartFrame
;
1785 const nsStyleText
* textStyle
= startFrame
->GetStyleText();
1786 if (!textStyle
->WhiteSpaceCanWrap()) {
1787 flags
|= nsLineBreaker::BREAK_SUPPRESS_INSIDE
;
1789 if (aTextRun
->GetFlags() & nsTextFrameUtils::TEXT_NO_BREAKS
) {
1790 flags
|= nsLineBreaker::BREAK_SKIP_SETTING_NO_BREAKS
;
1792 if (textStyle
->mTextTransform
== NS_STYLE_TEXT_TRANSFORM_CAPITALIZE
) {
1793 flags
|= nsLineBreaker::BREAK_NEED_CAPITALIZATION
;
1796 if (HasCompressedLeadingWhitespace(startFrame
, textStyle
,
1797 mappedFlow
->GetContentEnd(), iter
)) {
1798 mLineBreaker
.AppendInvisibleWhitespace(flags
);
1802 BreakSink
* sink
= aSuppressSink
? nsnull
: (*breakSink
).get();
1803 if (aTextRun
->GetFlags() & gfxFontGroup::TEXT_IS_8BIT
) {
1804 mLineBreaker
.AppendText(lang
, aTextRun
->GetText8Bit() + offset
,
1805 length
, flags
, sink
);
1807 mLineBreaker
.AppendText(lang
, aTextRun
->GetTextUnicode() + offset
,
1808 length
, flags
, sink
);
1817 BuildTextRunsScanner::AssignTextRun(gfxTextRun
* aTextRun
)
1819 nsIContent
* lastContent
= nsnull
;
1821 for (i
= 0; i
< mMappedFlows
.Length(); ++i
) {
1822 MappedFlow
* mappedFlow
= &mMappedFlows
[i
];
1823 nsTextFrame
* startFrame
= mappedFlow
->mStartFrame
;
1824 nsTextFrame
* endFrame
= mappedFlow
->mEndFrame
;
1826 for (f
= startFrame
; f
!= endFrame
;
1827 f
= static_cast<nsTextFrame
*>(f
->GetNextContinuation())) {
1829 if (f
->GetTextRun()) {
1830 gfxTextRun
* textRun
= f
->GetTextRun();
1831 if (textRun
->GetFlags() & nsTextFrameUtils::TEXT_IS_SIMPLE_FLOW
) {
1832 if (mMappedFlows
[0].mStartFrame
!= static_cast<nsTextFrame
*>(textRun
->GetUserData())) {
1833 NS_WARNING("REASSIGNING SIMPLE FLOW TEXT RUN!");
1836 TextRunUserData
* userData
=
1837 static_cast<TextRunUserData
*>(textRun
->GetUserData());
1839 if (PRUint32(userData
->mMappedFlowCount
) >= mMappedFlows
.Length() ||
1840 userData
->mMappedFlows
[userData
->mMappedFlowCount
- 1].mStartFrame
!=
1841 mMappedFlows
[userData
->mMappedFlowCount
- 1].mStartFrame
) {
1842 NS_WARNING("REASSIGNING MULTIFLOW TEXT RUN (not append)!");
1848 f
->SetTextRun(aTextRun
);
1850 // BuildTextRunForFrames mashes together mapped flows for the same element,
1851 // so we do that here too.
1852 lastContent
= startFrame
->GetContent();
1856 gfxSkipCharsIterator
1857 nsTextFrame::EnsureTextRun(gfxContext
* aReferenceContext
, nsIFrame
* aLineContainer
,
1858 const nsLineList::iterator
* aLine
,
1859 PRUint32
* aFlowEndInTextRun
)
1861 if (mTextRun
&& (!aLine
|| !(*aLine
)->GetInvalidateTextRuns())) {
1862 if (mTextRun
->GetExpirationState()->IsTracked()) {
1863 gTextRuns
->MarkUsed(mTextRun
);
1866 nsRefPtr
<gfxContext
> ctx
= aReferenceContext
;
1868 ctx
= GetReferenceRenderingContext(this, nsnull
);
1871 BuildTextRuns(ctx
, this, aLineContainer
, aLine
);
1874 // A text run was not constructed for this frame. This is bad. The caller
1875 // will check mTextRun.
1876 static const gfxSkipChars emptySkipChars
;
1877 return gfxSkipCharsIterator(emptySkipChars
, 0);
1881 if (mTextRun
->GetFlags() & nsTextFrameUtils::TEXT_IS_SIMPLE_FLOW
) {
1882 if (aFlowEndInTextRun
) {
1883 *aFlowEndInTextRun
= mTextRun
->GetLength();
1885 return gfxSkipCharsIterator(mTextRun
->GetSkipChars(), 0, mContentOffset
);
1888 TextRunUserData
* userData
= static_cast<TextRunUserData
*>(mTextRun
->GetUserData());
1889 // Find the flow that contains us
1891 PRInt32 startAt
= userData
->mLastFlowIndex
;
1892 // Search first forward and then backward from the current position
1893 for (direction
= 1; direction
>= -1; direction
-= 2) {
1895 for (i
= startAt
; 0 <= i
&& i
< userData
->mMappedFlowCount
; i
+= direction
) {
1896 TextRunMappedFlow
* flow
= &userData
->mMappedFlows
[i
];
1897 if (flow
->mStartFrame
->GetContent() == mContent
) {
1898 // Since textruns can only contain one flow for a given content element,
1899 // this must be our flow.
1900 userData
->mLastFlowIndex
= i
;
1901 gfxSkipCharsIterator
iter(mTextRun
->GetSkipChars(),
1902 flow
->mDOMOffsetToBeforeTransformOffset
, mContentOffset
);
1903 if (aFlowEndInTextRun
) {
1904 if (i
+ 1 < userData
->mMappedFlowCount
) {
1905 gfxSkipCharsIterator
end(mTextRun
->GetSkipChars());
1906 *aFlowEndInTextRun
= end
.ConvertOriginalToSkipped(
1907 flow
[1].mStartFrame
->GetContentOffset() + flow
[1].mDOMOffsetToBeforeTransformOffset
);
1909 *aFlowEndInTextRun
= mTextRun
->GetLength();
1916 startAt
= userData
->mLastFlowIndex
- 1;
1918 NS_ERROR("Can't find flow containing this frame???");
1919 static const gfxSkipChars emptySkipChars
;
1920 return gfxSkipCharsIterator(emptySkipChars
, 0);
1924 GetEndOfTrimmedText(const nsTextFragment
* aFrag
, const nsStyleText
* aStyleText
,
1925 PRUint32 aStart
, PRUint32 aEnd
,
1926 gfxSkipCharsIterator
* aIterator
)
1928 aIterator
->SetSkippedOffset(aEnd
);
1929 while (aIterator
->GetSkippedOffset() > aStart
) {
1930 aIterator
->AdvanceSkipped(-1);
1931 if (!IsTrimmableSpace(aFrag
, aIterator
->GetOriginalOffset(), aStyleText
))
1932 return aIterator
->GetSkippedOffset() + 1;
1937 nsTextFrame::TrimmedOffsets
1938 nsTextFrame::GetTrimmedOffsets(const nsTextFragment
* aFrag
,
1941 NS_ASSERTION(mTextRun
, "Need textrun here");
1942 // This should not be used during reflow. We need our TEXT_REFLOW_FLAGS
1943 // to be set correctly.
1944 NS_ASSERTION(!(GetStateBits() & NS_FRAME_FIRST_REFLOW
),
1945 "Can only call this on frames that have been reflowed");
1946 NS_ASSERTION(!(GetStateBits() & NS_FRAME_IN_REFLOW
),
1947 "Can only call this on frames that are not being reflowed");
1949 TrimmedOffsets offsets
= { GetContentOffset(), GetContentLength() };
1950 const nsStyleText
* textStyle
= GetStyleText();
1951 // Note that pre-line newlines should still allow us to trim spaces
1953 if (textStyle
->WhiteSpaceIsSignificant())
1956 if (GetStateBits() & TEXT_START_OF_LINE
) {
1957 PRInt32 whitespaceCount
=
1958 GetTrimmableWhitespaceCount(aFrag
,
1959 offsets
.mStart
, offsets
.mLength
, 1);
1960 offsets
.mStart
+= whitespaceCount
;
1961 offsets
.mLength
-= whitespaceCount
;
1964 if (aTrimAfter
&& (GetStateBits() & TEXT_END_OF_LINE
)) {
1965 // This treats a trailing 'pre-line' newline as trimmable. That's fine,
1966 // it's actually what we want since we want whitespace before it to
1968 PRInt32 whitespaceCount
=
1969 GetTrimmableWhitespaceCount(aFrag
,
1970 offsets
.GetEnd() - 1, offsets
.mLength
, -1);
1971 offsets
.mLength
-= whitespaceCount
;
1977 * Currently only Unicode characters below 0x10000 have their spacing modified
1978 * by justification. If characters above 0x10000 turn out to need
1979 * justification spacing, that will require extra work. Currently,
1980 * this function must not include 0xd800 to 0xdbff because these characters
1983 static PRBool
IsJustifiableCharacter(const nsTextFragment
* aFrag
, PRInt32 aPos
,
1986 PRUnichar ch
= aFrag
->CharAt(aPos
);
1987 if (ch
== '\n' || ch
== '\t')
1990 // Don't justify spaces that are combined with diacriticals
1993 return !nsTextFrameUtils::IsSpaceCombiningSequenceTail(
1994 aFrag
->Get2b() + aPos
+ 1, aFrag
->GetLength() - (aPos
+ 1));
1999 (0x2150u
<= ch
&& ch
<= 0x22ffu
) || // Number Forms, Arrows, Mathematical Operators
2000 (0x2460u
<= ch
&& ch
<= 0x24ffu
) || // Enclosed Alphanumerics
2001 (0x2580u
<= ch
&& ch
<= 0x27bfu
) || // Block Elements, Geometric Shapes, Miscellaneous Symbols, Dingbats
2002 (0x27f0u
<= ch
&& ch
<= 0x2bffu
) || // Supplemental Arrows-A, Braille Patterns, Supplemental Arrows-B,
2003 // Miscellaneous Mathematical Symbols-B, Supplemental Mathematical Operators,
2004 // Miscellaneous Symbols and Arrows
2005 (0x2e80u
<= ch
&& ch
<= 0x312fu
) || // CJK Radicals Supplement, CJK Radicals Supplement,
2006 // Ideographic Description Characters, CJK Symbols and Punctuation,
2007 // Hiragana, Katakana, Bopomofo
2008 (0x3190u
<= ch
&& ch
<= 0xabffu
) || // Kanbun, Bopomofo Extended, Katakana Phonetic Extensions,
2009 // Enclosed CJK Letters and Months, CJK Compatibility,
2010 // CJK Unified Ideographs Extension A, Yijing Hexagram Symbols,
2011 // CJK Unified Ideographs, Yi Syllables, Yi Radicals
2012 (0xf900u
<= ch
&& ch
<= 0xfaffu
) || // CJK Compatibility Ideographs
2013 (0xff5eu
<= ch
&& ch
<= 0xff9fu
) // Halfwidth and Fullwidth Forms(a part)
2019 static void ClearMetrics(nsHTMLReflowMetrics
& aMetrics
)
2022 aMetrics
.height
= 0;
2023 aMetrics
.ascent
= 0;
2026 static PRInt32
FindChar(const nsTextFragment
* frag
,
2027 PRInt32 aOffset
, PRInt32 aLength
, PRUnichar ch
)
2031 const PRUnichar
* str
= frag
->Get2b() + aOffset
;
2032 for (; i
< aLength
; ++i
) {
2038 if (PRUint16(ch
) <= 0xFF) {
2039 const char* str
= frag
->Get1b() + aOffset
;
2040 const void* p
= memchr(str
, ch
, aLength
);
2042 return (static_cast<const char*>(p
) - str
) + aOffset
;
2048 static PRBool
IsChineseJapaneseLangGroup(nsIFrame
* aFrame
)
2050 nsIAtom
* langGroup
= aFrame
->GetStyleVisibility()->mLangGroup
;
2051 return langGroup
== nsGkAtoms::Japanese
2052 || langGroup
== nsGkAtoms::Chinese
2053 || langGroup
== nsGkAtoms::Taiwanese
2054 || langGroup
== nsGkAtoms::HongKongChinese
;
2058 static PRBool
IsInBounds(const gfxSkipCharsIterator
& aStart
, PRInt32 aContentLength
,
2059 PRUint32 aOffset
, PRUint32 aLength
) {
2060 if (aStart
.GetSkippedOffset() > aOffset
)
2062 if (aContentLength
== PR_INT32_MAX
)
2064 gfxSkipCharsIterator
iter(aStart
);
2065 iter
.AdvanceOriginal(aContentLength
);
2066 return iter
.GetSkippedOffset() >= aOffset
+ aLength
;
2070 class NS_STACK_CLASS PropertyProvider
: public gfxTextRun::PropertyProvider
{
2073 * Use this constructor for reflow, when we don't know what text is
2074 * really mapped by the frame and we have a lot of other data around.
2076 * @param aLength can be PR_INT32_MAX to indicate we cover all the text
2077 * associated with aFrame up to where its flow chain ends in the given
2078 * textrun. If PR_INT32_MAX is passed, justification and hyphen-related methods
2079 * cannot be called, nor can GetOriginalLength().
2081 PropertyProvider(gfxTextRun
* aTextRun
, const nsStyleText
* aTextStyle
,
2082 const nsTextFragment
* aFrag
, nsTextFrame
* aFrame
,
2083 const gfxSkipCharsIterator
& aStart
, PRInt32 aLength
,
2084 nsIFrame
* aLineContainer
,
2085 nscoord aOffsetFromBlockOriginForTabs
)
2086 : mTextRun(aTextRun
), mFontGroup(nsnull
),
2087 mTextStyle(aTextStyle
), mFrag(aFrag
),
2088 mLineContainer(aLineContainer
),
2089 mFrame(aFrame
), mStart(aStart
), mTempIterator(aStart
),
2090 mTabWidths(nsnull
), mLength(aLength
),
2091 mWordSpacing(StyleToCoord(mTextStyle
->mWordSpacing
)),
2092 mLetterSpacing(StyleToCoord(mTextStyle
->mLetterSpacing
)),
2093 mJustificationSpacing(0),
2095 mOffsetFromBlockOriginForTabs(aOffsetFromBlockOriginForTabs
),
2098 NS_ASSERTION(mStart
.IsInitialized(), "Start not initialized?");
2102 * Use this constructor after the frame has been reflowed and we don't
2103 * have other data around. Gets everything from the frame. EnsureTextRun
2104 * *must* be called before this!!!
2106 PropertyProvider(nsTextFrame
* aFrame
, const gfxSkipCharsIterator
& aStart
)
2107 : mTextRun(aFrame
->GetTextRun()), mFontGroup(nsnull
),
2108 mTextStyle(aFrame
->GetStyleText()),
2109 mFrag(aFrame
->GetContent()->GetText()),
2110 mLineContainer(nsnull
),
2111 mFrame(aFrame
), mStart(aStart
), mTempIterator(aStart
),
2113 mLength(aFrame
->GetContentLength()),
2114 mWordSpacing(StyleToCoord(mTextStyle
->mWordSpacing
)),
2115 mLetterSpacing(StyleToCoord(mTextStyle
->mLetterSpacing
)),
2116 mJustificationSpacing(0),
2118 mOffsetFromBlockOriginForTabs(0),
2119 mReflowing(PR_FALSE
)
2121 NS_ASSERTION(mTextRun
, "Textrun not initialized!");
2124 // Call this after construction if you're not going to reflow the text
2125 void InitializeForDisplay(PRBool aTrimAfter
);
2127 virtual void GetSpacing(PRUint32 aStart
, PRUint32 aLength
, Spacing
* aSpacing
);
2128 virtual gfxFloat
GetHyphenWidth();
2129 virtual void GetHyphenationBreaks(PRUint32 aStart
, PRUint32 aLength
,
2130 PRPackedBool
* aBreakBefore
);
2132 void GetSpacingInternal(PRUint32 aStart
, PRUint32 aLength
, Spacing
* aSpacing
,
2133 PRBool aIgnoreTabs
);
2136 * Count the number of justifiable characters in the given DOM range
2138 PRUint32
ComputeJustifiableCharacters(PRInt32 aOffset
, PRInt32 aLength
);
2140 * Find the start and end of the justifiable characters. Does not depend on the
2141 * position of aStart or aEnd, although it's most efficient if they are near the
2142 * start and end of the text frame.
2144 void FindJustificationRange(gfxSkipCharsIterator
* aStart
,
2145 gfxSkipCharsIterator
* aEnd
);
2147 const nsStyleText
* GetStyleText() { return mTextStyle
; }
2148 nsTextFrame
* GetFrame() { return mFrame
; }
2149 // This may not be equal to the frame offset/length in because we may have
2150 // adjusted for whitespace trimming according to the state bits set in the frame
2151 // (for the static provider)
2152 const gfxSkipCharsIterator
& GetStart() { return mStart
; }
2153 // May return PR_INT32_MAX if that was given to the constructor
2154 PRUint32
GetOriginalLength() {
2155 NS_ASSERTION(mLength
!= PR_INT32_MAX
, "Length not known");
2158 const nsTextFragment
* GetFragment() { return mFrag
; }
2160 gfxFontGroup
* GetFontGroup() {
2162 InitFontGroupAndFontMetrics();
2166 nsIFontMetrics
* GetFontMetrics() {
2168 InitFontGroupAndFontMetrics();
2169 return mFontMetrics
;
2172 gfxFloat
* GetTabWidths(PRUint32 aTransformedStart
, PRUint32 aTransformedLength
);
2174 const gfxSkipCharsIterator
& GetEndHint() { return mTempIterator
; }
2177 void SetupJustificationSpacing();
2179 void InitFontGroupAndFontMetrics() {
2180 mFontGroup
= GetFontGroupForFrame(mFrame
, getter_AddRefs(mFontMetrics
));
2183 gfxTextRun
* mTextRun
;
2184 gfxFontGroup
* mFontGroup
;
2185 nsCOMPtr
<nsIFontMetrics
> mFontMetrics
;
2186 const nsStyleText
* mTextStyle
;
2187 const nsTextFragment
* mFrag
;
2188 nsIFrame
* mLineContainer
;
2189 nsTextFrame
* mFrame
;
2190 gfxSkipCharsIterator mStart
; // Offset in original and transformed string
2191 gfxSkipCharsIterator mTempIterator
;
2193 // Widths for each transformed string character, 0 for non-tab characters.
2194 // Either null, or pointing to the frame's tabWidthProperty.
2195 nsTArray
<gfxFloat
>* mTabWidths
;
2197 PRInt32 mLength
; // DOM string length, may be PR_INT32_MAX
2198 gfxFloat mWordSpacing
; // space for each whitespace char
2199 gfxFloat mLetterSpacing
; // space for each letter
2200 gfxFloat mJustificationSpacing
;
2201 gfxFloat mHyphenWidth
;
2202 gfxFloat mOffsetFromBlockOriginForTabs
;
2203 PRPackedBool mReflowing
;
2207 PropertyProvider::ComputeJustifiableCharacters(PRInt32 aOffset
, PRInt32 aLength
)
2209 // Scan non-skipped characters and count justifiable chars.
2210 nsSkipCharsRunIterator
2211 run(mStart
, nsSkipCharsRunIterator::LENGTH_INCLUDES_SKIPPED
, aLength
);
2212 run
.SetOriginalOffset(aOffset
);
2213 PRUint32 justifiableChars
= 0;
2214 PRBool isCJK
= IsChineseJapaneseLangGroup(mFrame
);
2215 while (run
.NextRun()) {
2217 for (i
= 0; i
< run
.GetRunLength(); ++i
) {
2219 IsJustifiableCharacter(mFrag
, run
.GetOriginalOffset() + i
, isCJK
);
2222 return justifiableChars
;
2226 * Finds the offset of the first character of the cluster containing aPos
2228 static void FindClusterStart(gfxTextRun
* aTextRun
,
2229 gfxSkipCharsIterator
* aPos
)
2231 while (aPos
->GetOriginalOffset() > 0) {
2232 if (aPos
->IsOriginalCharSkipped() ||
2233 aTextRun
->IsClusterStart(aPos
->GetSkippedOffset())) {
2236 aPos
->AdvanceOriginal(-1);
2241 * Finds the offset of the last character of the cluster containing aPos
2243 static void FindClusterEnd(gfxTextRun
* aTextRun
, PRInt32 aOriginalEnd
,
2244 gfxSkipCharsIterator
* aPos
)
2246 NS_PRECONDITION(aPos
->GetOriginalOffset() < aOriginalEnd
,
2247 "character outside string");
2248 aPos
->AdvanceOriginal(1);
2249 while (aPos
->GetOriginalOffset() < aOriginalEnd
) {
2250 if (aPos
->IsOriginalCharSkipped() ||
2251 aTextRun
->IsClusterStart(aPos
->GetSkippedOffset())) {
2254 aPos
->AdvanceOriginal(1);
2256 aPos
->AdvanceOriginal(-1);
2259 // aStart, aLength in transformed string offsets
2261 PropertyProvider::GetSpacing(PRUint32 aStart
, PRUint32 aLength
,
2264 GetSpacingInternal(aStart
, aLength
, aSpacing
,
2265 (mTextRun
->GetFlags() & nsTextFrameUtils::TEXT_HAS_TAB
) == 0);
2269 CanAddSpacingAfter(gfxTextRun
* aTextRun
, PRUint32 aOffset
)
2271 if (aOffset
+ 1 >= aTextRun
->GetLength())
2273 return aTextRun
->IsClusterStart(aOffset
+ 1) &&
2274 aTextRun
->IsLigatureGroupStart(aOffset
+ 1);
2278 PropertyProvider::GetSpacingInternal(PRUint32 aStart
, PRUint32 aLength
,
2279 Spacing
* aSpacing
, PRBool aIgnoreTabs
)
2281 NS_PRECONDITION(IsInBounds(mStart
, mLength
, aStart
, aLength
), "Range out of bounds");
2284 for (index
= 0; index
< aLength
; ++index
) {
2285 aSpacing
[index
].mBefore
= 0.0;
2286 aSpacing
[index
].mAfter
= 0.0;
2289 // Find our offset into the original+transformed string
2290 gfxSkipCharsIterator
start(mStart
);
2291 start
.SetSkippedOffset(aStart
);
2293 // First, compute the word and letter spacing
2294 if (mWordSpacing
|| mLetterSpacing
) {
2295 // Iterate over non-skipped characters
2296 nsSkipCharsRunIterator
2297 run(start
, nsSkipCharsRunIterator::LENGTH_UNSKIPPED_ONLY
, aLength
);
2298 while (run
.NextRun()) {
2299 PRUint32 runOffsetInSubstring
= run
.GetSkippedOffset() - aStart
;
2301 gfxSkipCharsIterator iter
= run
.GetPos();
2302 for (i
= 0; i
< run
.GetRunLength(); ++i
) {
2303 if (CanAddSpacingAfter(mTextRun
, run
.GetSkippedOffset() + i
)) {
2304 // End of a cluster, not in a ligature: put letter-spacing after it
2305 aSpacing
[runOffsetInSubstring
+ i
].mAfter
+= mLetterSpacing
;
2307 if (IsCSSWordSpacingSpace(mFrag
, i
+ run
.GetOriginalOffset())) {
2308 // It kinda sucks, but space characters can be part of clusters,
2309 // and even still be whitespace (I think!)
2310 iter
.SetSkippedOffset(run
.GetSkippedOffset() + i
);
2311 FindClusterEnd(mTextRun
, run
.GetOriginalOffset() + run
.GetRunLength(),
2313 aSpacing
[iter
.GetSkippedOffset() - aStart
].mAfter
+= mWordSpacing
;
2319 // Now add tab spacing, if there is any
2321 gfxFloat
* tabs
= GetTabWidths(aStart
, aLength
);
2323 for (index
= 0; index
< aLength
; ++index
) {
2324 aSpacing
[index
].mAfter
+= tabs
[index
];
2329 // Now add in justification spacing
2330 if (mJustificationSpacing
) {
2331 gfxFloat halfJustificationSpace
= mJustificationSpacing
/2;
2332 // Scan non-skipped characters and adjust justifiable chars, adding
2333 // justification space on either side of the cluster
2334 PRBool isCJK
= IsChineseJapaneseLangGroup(mFrame
);
2335 gfxSkipCharsIterator
justificationStart(mStart
), justificationEnd(mStart
);
2336 FindJustificationRange(&justificationStart
, &justificationEnd
);
2338 nsSkipCharsRunIterator
2339 run(start
, nsSkipCharsRunIterator::LENGTH_UNSKIPPED_ONLY
, aLength
);
2340 while (run
.NextRun()) {
2342 gfxSkipCharsIterator iter
= run
.GetPos();
2343 for (i
= 0; i
< run
.GetRunLength(); ++i
) {
2344 PRInt32 originalOffset
= run
.GetOriginalOffset() + i
;
2345 if (IsJustifiableCharacter(mFrag
, originalOffset
, isCJK
)) {
2346 iter
.SetOriginalOffset(originalOffset
);
2347 FindClusterStart(mTextRun
, &iter
);
2348 PRUint32 clusterFirstChar
= iter
.GetSkippedOffset();
2349 FindClusterEnd(mTextRun
, run
.GetOriginalOffset() + run
.GetRunLength(), &iter
);
2350 PRUint32 clusterLastChar
= iter
.GetSkippedOffset();
2351 // Only apply justification to characters before justificationEnd
2352 if (clusterFirstChar
>= justificationStart
.GetSkippedOffset() &&
2353 clusterLastChar
< justificationEnd
.GetSkippedOffset()) {
2354 aSpacing
[clusterFirstChar
- aStart
].mBefore
+= halfJustificationSpace
;
2355 aSpacing
[clusterLastChar
- aStart
].mAfter
+= halfJustificationSpace
;
2363 static void TabWidthDestructor(void* aObject
, nsIAtom
* aProp
, void* aValue
,
2366 delete static_cast<nsTArray
<gfxFloat
>*>(aValue
);
2370 ComputeTabWidthAppUnits(nsIFrame
* aLineContainer
, gfxTextRun
* aTextRun
)
2372 // Round the space width when converting to appunits the same way
2374 gfxFloat spaceWidthAppUnits
=
2375 NS_roundf(GetFirstFontMetrics(
2376 GetFontGroupForFrame(aLineContainer
)).spaceWidth
*
2377 aTextRun
->GetAppUnitsPerDevUnit());
2378 return 8*spaceWidthAppUnits
;
2381 // aX and the result are in whole appunits.
2383 AdvanceToNextTab(gfxFloat aX
, nsIFrame
* aLineContainer
,
2384 gfxTextRun
* aTextRun
, gfxFloat
* aCachedTabWidth
)
2386 if (*aCachedTabWidth
< 0) {
2387 *aCachedTabWidth
= ComputeTabWidthAppUnits(aLineContainer
, aTextRun
);
2390 // Advance aX to the next multiple of *aCachedTabWidth. We must advance
2391 // by at least 1 appunit.
2392 // XXX should we make this 1 CSS pixel?
2393 return NS_ceil((aX
+ 1)/(*aCachedTabWidth
))*(*aCachedTabWidth
);
2397 PropertyProvider::GetTabWidths(PRUint32 aStart
, PRUint32 aLength
)
2401 mTabWidths
= static_cast<nsTArray
<gfxFloat
>*>
2402 (mFrame
->GetProperty(nsGkAtoms::tabWidthProperty
));
2404 NS_WARNING("We need precomputed tab widths, but they're not here...");
2408 if (!mLineContainer
) {
2409 // Intrinsic width computation does its own tab processing. We
2410 // just don't do anything here.
2414 nsAutoPtr
<nsTArray
<gfxFloat
> > tabs(new nsTArray
<gfxFloat
>());
2417 nsresult rv
= mFrame
->SetProperty(nsGkAtoms::tabWidthProperty
, tabs
,
2418 TabWidthDestructor
, nsnull
);
2421 mTabWidths
= tabs
.forget();
2425 PRUint32 startOffset
= mStart
.GetSkippedOffset();
2426 PRUint32 tabsEnd
= startOffset
+ mTabWidths
->Length();
2427 if (tabsEnd
< aStart
+ aLength
) {
2429 NS_WARNING("We need precomputed tab widths, but we don't have enough...");
2433 if (!mTabWidths
->AppendElements(aStart
+ aLength
- tabsEnd
))
2436 gfxFloat tabWidth
= -1;
2437 for (PRUint32 i
= tabsEnd
; i
< aStart
+ aLength
; ++i
) {
2439 GetSpacingInternal(i
, 1, &spacing
, PR_TRUE
);
2440 mOffsetFromBlockOriginForTabs
+= spacing
.mBefore
;
2442 if (mTextRun
->GetChar(i
) != '\t') {
2443 (*mTabWidths
)[i
- startOffset
] = 0;
2444 if (mTextRun
->IsClusterStart(i
)) {
2445 PRUint32 clusterEnd
= i
+ 1;
2446 while (clusterEnd
< mTextRun
->GetLength() &&
2447 !mTextRun
->IsClusterStart(clusterEnd
)) {
2450 mOffsetFromBlockOriginForTabs
+=
2451 mTextRun
->GetAdvanceWidth(i
, clusterEnd
- i
, nsnull
);
2454 double nextTab
= AdvanceToNextTab(mOffsetFromBlockOriginForTabs
,
2455 mLineContainer
, mTextRun
, &tabWidth
);
2456 (*mTabWidths
)[i
- startOffset
] = nextTab
- mOffsetFromBlockOriginForTabs
;
2457 mOffsetFromBlockOriginForTabs
= nextTab
;
2460 mOffsetFromBlockOriginForTabs
+= spacing
.mAfter
;
2464 return mTabWidths
->Elements() + aStart
- startOffset
;
2468 PropertyProvider::GetHyphenWidth()
2470 if (mHyphenWidth
< 0) {
2471 gfxTextRunCache::AutoTextRun
hyphenTextRun(GetHyphenTextRun(mTextRun
, nsnull
, mFrame
));
2472 mHyphenWidth
= mLetterSpacing
;
2473 if (hyphenTextRun
.get()) {
2474 mHyphenWidth
+= hyphenTextRun
->GetAdvanceWidth(0, hyphenTextRun
->GetLength(), nsnull
);
2477 return mHyphenWidth
;
2481 PropertyProvider::GetHyphenationBreaks(PRUint32 aStart
, PRUint32 aLength
,
2482 PRPackedBool
* aBreakBefore
)
2484 NS_PRECONDITION(IsInBounds(mStart
, mLength
, aStart
, aLength
), "Range out of bounds");
2485 NS_PRECONDITION(mLength
!= PR_INT32_MAX
, "Can't call this with undefined length");
2487 if (!mTextStyle
->WhiteSpaceCanWrap()) {
2488 memset(aBreakBefore
, PR_FALSE
, aLength
);
2492 // Iterate through the original-string character runs
2493 nsSkipCharsRunIterator
2494 run(mStart
, nsSkipCharsRunIterator::LENGTH_UNSKIPPED_ONLY
, aLength
);
2495 run
.SetSkippedOffset(aStart
);
2496 // We need to visit skipped characters so that we can detect SHY
2497 run
.SetVisitSkipped();
2499 PRInt32 prevTrailingCharOffset
= run
.GetPos().GetOriginalOffset() - 1;
2500 PRBool allowHyphenBreakBeforeNextChar
=
2501 prevTrailingCharOffset
>= mStart
.GetOriginalOffset() &&
2502 prevTrailingCharOffset
< mStart
.GetOriginalOffset() + mLength
&&
2503 mFrag
->CharAt(prevTrailingCharOffset
) == CH_SHY
;
2505 while (run
.NextRun()) {
2506 NS_ASSERTION(run
.GetRunLength() > 0, "Shouldn't return zero-length runs");
2507 if (run
.IsSkipped()) {
2508 // Check if there's a soft hyphen which would let us hyphenate before
2509 // the next non-skipped character. Don't look at soft hyphens followed
2510 // by other skipped characters, we won't use them.
2511 allowHyphenBreakBeforeNextChar
=
2512 mFrag
->CharAt(run
.GetOriginalOffset() + run
.GetRunLength() - 1) == CH_SHY
;
2514 PRInt32 runOffsetInSubstring
= run
.GetSkippedOffset() - aStart
;
2515 memset(aBreakBefore
+ runOffsetInSubstring
, 0, run
.GetRunLength());
2516 // Don't allow hyphen breaks at the start of the line
2517 aBreakBefore
[runOffsetInSubstring
] = allowHyphenBreakBeforeNextChar
&&
2518 (!(mFrame
->GetStateBits() & TEXT_START_OF_LINE
) ||
2519 run
.GetSkippedOffset() > mStart
.GetSkippedOffset());
2520 allowHyphenBreakBeforeNextChar
= PR_FALSE
;
2526 PropertyProvider::InitializeForDisplay(PRBool aTrimAfter
)
2528 nsTextFrame::TrimmedOffsets trimmed
=
2529 mFrame
->GetTrimmedOffsets(mFrag
, aTrimAfter
);
2530 mStart
.SetOriginalOffset(trimmed
.mStart
);
2531 mLength
= trimmed
.mLength
;
2532 SetupJustificationSpacing();
2535 static PRUint32
GetSkippedDistance(const gfxSkipCharsIterator
& aStart
,
2536 const gfxSkipCharsIterator
& aEnd
)
2538 return aEnd
.GetSkippedOffset() - aStart
.GetSkippedOffset();
2542 PropertyProvider::FindJustificationRange(gfxSkipCharsIterator
* aStart
,
2543 gfxSkipCharsIterator
* aEnd
)
2545 NS_PRECONDITION(mLength
!= PR_INT32_MAX
, "Can't call this with undefined length");
2546 NS_ASSERTION(aStart
&& aEnd
, "aStart or/and aEnd is null");
2548 aStart
->SetOriginalOffset(mStart
.GetOriginalOffset());
2549 aEnd
->SetOriginalOffset(mStart
.GetOriginalOffset() + mLength
);
2551 // Ignore first cluster at start of line for justification purposes
2552 if (mFrame
->GetStateBits() & TEXT_START_OF_LINE
) {
2553 while (aStart
->GetOriginalOffset() < aEnd
->GetOriginalOffset()) {
2554 aStart
->AdvanceOriginal(1);
2555 if (!aStart
->IsOriginalCharSkipped() &&
2556 mTextRun
->IsClusterStart(aStart
->GetSkippedOffset()))
2561 // Ignore trailing cluster at end of line for justification purposes
2562 if (mFrame
->GetStateBits() & TEXT_END_OF_LINE
) {
2563 while (aEnd
->GetOriginalOffset() > aStart
->GetOriginalOffset()) {
2564 aEnd
->AdvanceOriginal(-1);
2565 if (!aEnd
->IsOriginalCharSkipped() &&
2566 mTextRun
->IsClusterStart(aEnd
->GetSkippedOffset()))
2573 PropertyProvider::SetupJustificationSpacing()
2575 NS_PRECONDITION(mLength
!= PR_INT32_MAX
, "Can't call this with undefined length");
2577 if (!(mFrame
->GetStateBits() & TEXT_JUSTIFICATION_ENABLED
))
2580 gfxSkipCharsIterator
start(mStart
), end(mStart
);
2581 end
.AdvanceOriginal(mLength
);
2582 gfxSkipCharsIterator
realEnd(end
);
2583 FindJustificationRange(&start
, &end
);
2585 PRInt32 justifiableCharacters
=
2586 ComputeJustifiableCharacters(start
.GetOriginalOffset(),
2587 end
.GetOriginalOffset() - start
.GetOriginalOffset());
2588 if (justifiableCharacters
== 0) {
2589 // Nothing to do, nothing is justifiable and we shouldn't have any
2590 // justification space assigned
2594 gfxFloat naturalWidth
=
2595 mTextRun
->GetAdvanceWidth(mStart
.GetSkippedOffset(),
2596 GetSkippedDistance(mStart
, realEnd
), this);
2597 if (mFrame
->GetStateBits() & TEXT_HYPHEN_BREAK
) {
2598 gfxTextRunCache::AutoTextRun
hyphenTextRun(GetHyphenTextRun(mTextRun
, nsnull
, mFrame
));
2599 if (hyphenTextRun
.get()) {
2601 hyphenTextRun
->GetAdvanceWidth(0, hyphenTextRun
->GetLength(), nsnull
);
2604 gfxFloat totalJustificationSpace
= mFrame
->GetSize().width
- naturalWidth
;
2605 if (totalJustificationSpace
<= 0) {
2606 // No space available
2610 mJustificationSpacing
= totalJustificationSpace
/justifiableCharacters
;
2613 //----------------------------------------------------------------------
2615 // Helper class for managing blinking text
2617 class nsBlinkTimer
: public nsITimerCallback
2621 virtual ~nsBlinkTimer();
2625 void AddFrame(nsPresContext
* aPresContext
, nsIFrame
* aFrame
);
2627 PRBool
RemoveFrame(nsIFrame
* aFrame
);
2629 PRInt32
FrameCount();
2635 NS_DECL_NSITIMERCALLBACK
2637 static nsresult
AddBlinkFrame(nsPresContext
* aPresContext
, nsIFrame
* aFrame
);
2638 static nsresult
RemoveBlinkFrame(nsIFrame
* aFrame
);
2640 static PRBool
GetBlinkIsOff() { return sState
== 3; }
2645 nsPresContext
* mPresContext
; // pres context associated with the frame
2649 FrameData(nsPresContext
* aPresContext
,
2651 : mPresContext(aPresContext
), mFrame(aFrame
) {}
2654 nsCOMPtr
<nsITimer
> mTimer
;
2655 nsVoidArray mFrames
;
2656 nsPresContext
* mPresContext
;
2660 static nsBlinkTimer
* sTextBlinker
;
2661 static PRUint32 sState
; // 0-2 == on; 3 == off
2665 nsBlinkTimer
* nsBlinkTimer::sTextBlinker
= nsnull
;
2666 PRUint32
nsBlinkTimer::sState
= 0;
2669 static PRTime gLastTick
;
2672 nsBlinkTimer::nsBlinkTimer()
2676 nsBlinkTimer::~nsBlinkTimer()
2679 sTextBlinker
= nsnull
;
2682 void nsBlinkTimer::Start()
2685 mTimer
= do_CreateInstance("@mozilla.org/timer;1", &rv
);
2687 mTimer
->InitWithCallback(this, 250, nsITimer::TYPE_REPEATING_PRECISE
);
2691 void nsBlinkTimer::Stop()
2693 if (nsnull
!= mTimer
) {
2699 NS_IMPL_ISUPPORTS1(nsBlinkTimer
, nsITimerCallback
)
2701 void nsBlinkTimer::AddFrame(nsPresContext
* aPresContext
, nsIFrame
* aFrame
) {
2702 FrameData
* frameData
= new FrameData(aPresContext
, aFrame
);
2703 mFrames
.AppendElement(frameData
);
2704 if (1 == mFrames
.Count()) {
2709 PRBool
nsBlinkTimer::RemoveFrame(nsIFrame
* aFrame
) {
2710 PRInt32 i
, n
= mFrames
.Count();
2711 PRBool rv
= PR_FALSE
;
2712 for (i
= 0; i
< n
; i
++) {
2713 FrameData
* frameData
= (FrameData
*) mFrames
.ElementAt(i
);
2715 if (frameData
->mFrame
== aFrame
) {
2716 rv
= mFrames
.RemoveElementAt(i
);
2722 if (0 == mFrames
.Count()) {
2728 PRInt32
nsBlinkTimer::FrameCount() {
2729 return mFrames
.Count();
2732 NS_IMETHODIMP
nsBlinkTimer::Notify(nsITimer
*timer
)
2734 // Toggle blink state bit so that text code knows whether or not to
2735 // render. All text code shares the same flag so that they all blink
2737 sState
= (sState
+ 1) % 4;
2738 if (sState
== 1 || sState
== 2)
2739 // States 0, 1, and 2 are all the same.
2743 PRTime now
= PR_Now();
2746 LL_SUB(delta
, now
, gLastTick
);
2748 PR_snprintf(buf
, sizeof(buf
), "%lldusec", delta
);
2749 printf("%s\n", buf
);
2752 PRInt32 i
, n
= mFrames
.Count();
2753 for (i
= 0; i
< n
; i
++) {
2754 FrameData
* frameData
= (FrameData
*) mFrames
.ElementAt(i
);
2756 // Determine damaged area and tell view manager to redraw it
2757 // blink doesn't blink outline ... I hope
2758 nsRect
bounds(nsPoint(0, 0), frameData
->mFrame
->GetSize());
2759 frameData
->mFrame
->Invalidate(bounds
, PR_FALSE
);
2766 nsresult
nsBlinkTimer::AddBlinkFrame(nsPresContext
* aPresContext
, nsIFrame
* aFrame
)
2770 sTextBlinker
= new nsBlinkTimer
;
2771 if (!sTextBlinker
) return NS_ERROR_OUT_OF_MEMORY
;
2774 NS_ADDREF(sTextBlinker
);
2776 sTextBlinker
->AddFrame(aPresContext
, aFrame
);
2782 nsresult
nsBlinkTimer::RemoveBlinkFrame(nsIFrame
* aFrame
)
2784 NS_ASSERTION(sTextBlinker
, "Should have blink timer here");
2786 nsBlinkTimer
* blinkTimer
= sTextBlinker
; // copy so we can call NS_RELEASE on it
2787 if (!blinkTimer
) return NS_OK
;
2789 blinkTimer
->RemoveFrame(aFrame
);
2790 NS_RELEASE(blinkTimer
);
2795 //----------------------------------------------------------------------
2798 EnsureDifferentColors(nscolor colorA
, nscolor colorB
)
2800 if (colorA
== colorB
) {
2802 res
= NS_RGB(NS_GET_R(colorA
) ^ 0xff,
2803 NS_GET_G(colorA
) ^ 0xff,
2804 NS_GET_B(colorA
) ^ 0xff);
2810 //-----------------------------------------------------------------------------
2812 // TODO delete nsCSSRendering::TransformColor because we're moving it here
2814 DarkenColor(nscolor aColor
)
2816 PRUint16 hue
,sat
,value
;
2818 // convert the RBG to HSV so we can get the lightness (which is the v)
2819 NS_RGB2HSV(aColor
,hue
,sat
,value
);
2821 // The goal here is to send white to black while letting colored
2822 // stuff stay colored... So we adopt the following approach.
2823 // Something with sat = 0 should end up with value = 0. Something
2824 // with a high sat can end up with a high value and it's ok.... At
2825 // the same time, we don't want to make things lighter. Do
2826 // something simple, since it seems to work.
2829 // convert this color back into the RGB color space.
2830 NS_HSV2RGB(aColor
,hue
,sat
,value
);
2835 // Check whether we should darken text colors. We need to do this if
2836 // background images and colors are being suppressed, because that means
2837 // light text will not be visible against the (presumed light-colored) background.
2839 ShouldDarkenColors(nsPresContext
* aPresContext
)
2841 return !aPresContext
->GetBackgroundColorDraw() &&
2842 !aPresContext
->GetBackgroundImageDraw();
2845 nsTextPaintStyle::nsTextPaintStyle(nsTextFrame
* aFrame
)
2847 mPresContext(aFrame
->PresContext()),
2848 mInitCommonColors(PR_FALSE
),
2849 mInitSelectionColors(PR_FALSE
)
2851 for (int i
= 0; i
< 4; i
++)
2852 mIMEStyle
[i
].mInit
= PR_FALSE
;
2853 mIMEUnderlineRelativeSize
= -1.0f
;
2857 nsTextPaintStyle::EnsureSufficientContrast(nscolor
*aForeColor
, nscolor
*aBackColor
)
2861 // If the combination of selection background color and frame background color
2862 // is sufficient contrast, don't exchange the selection colors.
2863 PRInt32 backLuminosityDifference
=
2864 NS_LUMINOSITY_DIFFERENCE(*aBackColor
, mFrameBackgroundColor
);
2865 if (backLuminosityDifference
>= mSufficientContrast
)
2868 // Otherwise, we should use the higher-contrast color for the selection
2869 // background color.
2870 PRInt32 foreLuminosityDifference
=
2871 NS_LUMINOSITY_DIFFERENCE(*aForeColor
, mFrameBackgroundColor
);
2872 if (backLuminosityDifference
< foreLuminosityDifference
) {
2873 nscolor tmpColor
= *aForeColor
;
2874 *aForeColor
= *aBackColor
;
2875 *aBackColor
= tmpColor
;
2882 nsTextPaintStyle::GetTextColor()
2884 nscolor color
= mFrame
->GetStyleColor()->mColor
;
2885 if (ShouldDarkenColors(mPresContext
)) {
2886 color
= DarkenColor(color
);
2892 nsTextPaintStyle::GetSelectionColors(nscolor
* aForeColor
,
2893 nscolor
* aBackColor
)
2895 NS_ASSERTION(aForeColor
, "aForeColor is null");
2896 NS_ASSERTION(aBackColor
, "aBackColor is null");
2898 if (!InitSelectionColors())
2901 *aForeColor
= mSelectionTextColor
;
2902 *aBackColor
= mSelectionBGColor
;
2907 nsTextPaintStyle::GetHighlightColors(nscolor
* aForeColor
,
2908 nscolor
* aBackColor
)
2910 NS_ASSERTION(aForeColor
, "aForeColor is null");
2911 NS_ASSERTION(aBackColor
, "aBackColor is null");
2913 nsILookAndFeel
* look
= mPresContext
->LookAndFeel();
2914 nscolor foreColor
, backColor
;
2915 look
->GetColor(nsILookAndFeel::eColor_TextHighlightBackground
,
2917 look
->GetColor(nsILookAndFeel::eColor_TextSelectForeground
,
2919 EnsureSufficientContrast(&foreColor
, &backColor
);
2920 *aForeColor
= foreColor
;
2921 *aBackColor
= backColor
;
2925 nsTextPaintStyle::GetIMESelectionColors(PRInt32 aIndex
,
2926 nscolor
* aForeColor
,
2927 nscolor
* aBackColor
)
2929 NS_ASSERTION(aForeColor
, "aForeColor is null");
2930 NS_ASSERTION(aBackColor
, "aBackColor is null");
2931 NS_ASSERTION(aIndex
>= 0 && aIndex
< 4, "Index out of range");
2933 nsIMEStyle
* IMEStyle
= GetIMEStyle(aIndex
);
2934 *aForeColor
= IMEStyle
->mTextColor
;
2935 *aBackColor
= IMEStyle
->mBGColor
;
2939 nsTextPaintStyle::GetIMEUnderline(PRInt32 aIndex
,
2940 nscolor
* aLineColor
,
2941 float* aRelativeSize
,
2944 NS_ASSERTION(aLineColor
, "aLineColor is null");
2945 NS_ASSERTION(aRelativeSize
, "aRelativeSize is null");
2946 NS_ASSERTION(aIndex
>= 0 && aIndex
< 4, "Index out of range");
2948 nsIMEStyle
* IMEStyle
= GetIMEStyle(aIndex
);
2949 if (IMEStyle
->mUnderlineStyle
== NS_STYLE_BORDER_STYLE_NONE
||
2950 IMEStyle
->mUnderlineColor
== NS_TRANSPARENT
||
2951 mIMEUnderlineRelativeSize
<= 0.0f
)
2954 *aLineColor
= IMEStyle
->mUnderlineColor
;
2955 *aRelativeSize
= mIMEUnderlineRelativeSize
;
2956 *aStyle
= IMEStyle
->mUnderlineStyle
;
2961 nsTextPaintStyle::InitCommonColors()
2963 if (mInitCommonColors
)
2966 nsStyleContext
* sc
= mFrame
->GetStyleContext();
2968 const nsStyleBackground
* bg
=
2969 nsCSSRendering::FindNonTransparentBackground(sc
);
2970 NS_ASSERTION(bg
, "Cannot find NonTransparentBackground.");
2971 mFrameBackgroundColor
= bg
->mBackgroundColor
;
2973 nsILookAndFeel
* look
= mPresContext
->LookAndFeel();
2974 nscolor defaultWindowBackgroundColor
, selectionTextColor
, selectionBGColor
;
2975 look
->GetColor(nsILookAndFeel::eColor_TextSelectBackground
,
2977 look
->GetColor(nsILookAndFeel::eColor_TextSelectForeground
,
2978 selectionTextColor
);
2979 look
->GetColor(nsILookAndFeel::eColor_WindowBackground
,
2980 defaultWindowBackgroundColor
);
2982 mSufficientContrast
=
2983 PR_MIN(PR_MIN(NS_SUFFICIENT_LUMINOSITY_DIFFERENCE
,
2984 NS_LUMINOSITY_DIFFERENCE(selectionTextColor
,
2986 NS_LUMINOSITY_DIFFERENCE(defaultWindowBackgroundColor
,
2989 mInitCommonColors
= PR_TRUE
;
2992 static nsIFrame
* GetNonGeneratedAncestor(nsIFrame
* f
) {
2993 while (f
->GetStateBits() & NS_FRAME_GENERATED_CONTENT
) {
2994 f
= nsLayoutUtils::GetParentOrPlaceholderFor(f
->PresContext()->FrameManager(), f
);
3000 FindElementAncestor(nsINode
* aNode
)
3002 while (aNode
&& !aNode
->IsNodeOfType(nsINode::eELEMENT
)) {
3003 aNode
= aNode
->GetParent();
3005 return static_cast<nsIContent
*>(aNode
);
3009 nsTextPaintStyle::InitSelectionColors()
3011 if (mInitSelectionColors
)
3014 PRInt16 selectionFlags
;
3015 PRInt16 selectionStatus
= mFrame
->GetSelectionStatus(&selectionFlags
);
3016 if (!(selectionFlags
& nsISelectionDisplay::DISPLAY_TEXT
) ||
3017 selectionStatus
< nsISelectionController::SELECTION_ON
) {
3018 // Not displaying the normal selection.
3019 // We're not caching this fact, so every call to GetSelectionColors
3020 // will come through here. We could avoid this, but it's not really worth it.
3024 mInitSelectionColors
= PR_TRUE
;
3026 nsIFrame
* nonGeneratedAncestor
= GetNonGeneratedAncestor(mFrame
);
3027 nsIContent
* selectionContent
= FindElementAncestor(nonGeneratedAncestor
->GetContent());
3029 if (selectionContent
&&
3030 selectionStatus
== nsISelectionController::SELECTION_ON
) {
3031 nsRefPtr
<nsStyleContext
> sc
= nsnull
;
3032 sc
= mPresContext
->StyleSet()->
3033 ProbePseudoStyleFor(selectionContent
, nsCSSPseudoElements::mozSelection
,
3034 mFrame
->GetStyleContext());
3035 // Use -moz-selection pseudo class.
3037 const nsStyleBackground
* bg
= sc
->GetStyleBackground();
3038 mSelectionBGColor
= bg
->mBackgroundColor
;
3039 if (bg
->mBackgroundFlags
& NS_STYLE_BG_COLOR_TRANSPARENT
) {
3040 mSelectionBGColor
= NS_RGBA(0,0,0,0);
3042 mSelectionTextColor
= sc
->GetStyleColor()->mColor
;
3047 nsILookAndFeel
* look
= mPresContext
->LookAndFeel();
3049 nscolor selectionBGColor
;
3050 look
->GetColor(nsILookAndFeel::eColor_TextSelectBackground
,
3053 if (selectionStatus
== nsISelectionController::SELECTION_ATTENTION
) {
3054 look
->GetColor(nsILookAndFeel::eColor_TextSelectBackgroundAttention
,
3056 mSelectionBGColor
= EnsureDifferentColors(mSelectionBGColor
,
3058 } else if (selectionStatus
!= nsISelectionController::SELECTION_ON
) {
3059 look
->GetColor(nsILookAndFeel::eColor_TextSelectBackgroundDisabled
,
3061 mSelectionBGColor
= EnsureDifferentColors(mSelectionBGColor
,
3064 mSelectionBGColor
= selectionBGColor
;
3067 look
->GetColor(nsILookAndFeel::eColor_TextSelectForeground
,
3068 mSelectionTextColor
);
3070 // On MacOS X, we don't exchange text color and BG color.
3071 if (mSelectionTextColor
== NS_DONT_CHANGE_COLOR
) {
3072 mSelectionTextColor
= EnsureDifferentColors(mFrame
->GetStyleColor()->mColor
,
3075 EnsureSufficientContrast(&mSelectionTextColor
, &mSelectionBGColor
);
3080 nsTextPaintStyle::nsIMEStyle
*
3081 nsTextPaintStyle::GetIMEStyle(PRInt32 aIndex
)
3083 InitIMEStyle(aIndex
);
3084 return &mIMEStyle
[aIndex
];
3088 nsILookAndFeel::nsColorID mForeground
, mBackground
, mLine
;
3089 nsILookAndFeel::nsMetricID mLineStyle
;
3091 static StyleIDs IMEStyleIDs
[] = {
3092 { nsILookAndFeel::eColor_IMERawInputForeground
,
3093 nsILookAndFeel::eColor_IMERawInputBackground
,
3094 nsILookAndFeel::eColor_IMERawInputUnderline
,
3095 nsILookAndFeel::eMetric_IMERawInputUnderlineStyle
},
3096 { nsILookAndFeel::eColor_IMESelectedRawTextForeground
,
3097 nsILookAndFeel::eColor_IMESelectedRawTextBackground
,
3098 nsILookAndFeel::eColor_IMESelectedRawTextUnderline
,
3099 nsILookAndFeel::eMetric_IMESelectedRawTextUnderlineStyle
},
3100 { nsILookAndFeel::eColor_IMEConvertedTextForeground
,
3101 nsILookAndFeel::eColor_IMEConvertedTextBackground
,
3102 nsILookAndFeel::eColor_IMEConvertedTextUnderline
,
3103 nsILookAndFeel::eMetric_IMEConvertedTextUnderlineStyle
},
3104 { nsILookAndFeel::eColor_IMESelectedConvertedTextForeground
,
3105 nsILookAndFeel::eColor_IMESelectedConvertedTextBackground
,
3106 nsILookAndFeel::eColor_IMESelectedConvertedTextUnderline
,
3107 nsILookAndFeel::eMetric_IMESelectedConvertedTextUnderline
}
3110 static PRUint8 sUnderlineStyles
[] = {
3111 NS_STYLE_BORDER_STYLE_NONE
, // NS_UNDERLINE_STYLE_NONE 0
3112 NS_STYLE_BORDER_STYLE_DOTTED
, // NS_UNDERLINE_STYLE_DOTTED 1
3113 NS_STYLE_BORDER_STYLE_DASHED
, // NS_UNDERLINE_STYLE_DASHED 2
3114 NS_STYLE_BORDER_STYLE_SOLID
, // NS_UNDERLINE_STYLE_SOLID 3
3115 NS_STYLE_BORDER_STYLE_DOUBLE
// NS_UNDERLINE_STYLE_DOUBLE 4
3119 nsTextPaintStyle::InitIMEStyle(PRInt32 aIndex
)
3121 nsIMEStyle
* IMEStyle
= &mIMEStyle
[aIndex
];
3122 if (IMEStyle
->mInit
)
3125 StyleIDs
* styleIDs
= &IMEStyleIDs
[aIndex
];
3127 nsILookAndFeel
* look
= mPresContext
->LookAndFeel();
3128 nscolor foreColor
, backColor
, lineColor
;
3130 look
->GetColor(styleIDs
->mForeground
, foreColor
);
3131 look
->GetColor(styleIDs
->mBackground
, backColor
);
3132 look
->GetColor(styleIDs
->mLine
, lineColor
);
3133 look
->GetMetric(styleIDs
->mLineStyle
, lineStyle
);
3135 // Convert special color to actual color
3136 NS_ASSERTION(foreColor
!= NS_TRANSPARENT
,
3137 "foreColor cannot be NS_TRANSPARENT");
3138 NS_ASSERTION(backColor
!= NS_SAME_AS_FOREGROUND_COLOR
,
3139 "backColor cannot be NS_SAME_AS_FOREGROUND_COLOR");
3140 NS_ASSERTION(backColor
!= NS_40PERCENT_FOREGROUND_COLOR
,
3141 "backColor cannot be NS_40PERCENT_FOREGROUND_COLOR");
3143 foreColor
= GetResolvedForeColor(foreColor
, GetTextColor(), backColor
);
3145 if (NS_GET_A(backColor
) > 0)
3146 EnsureSufficientContrast(&foreColor
, &backColor
);
3148 lineColor
= GetResolvedForeColor(lineColor
, foreColor
, backColor
);
3150 if (!NS_IS_VALID_UNDERLINE_STYLE(lineStyle
))
3151 lineStyle
= NS_UNDERLINE_STYLE_SOLID
;
3153 IMEStyle
->mTextColor
= foreColor
;
3154 IMEStyle
->mBGColor
= backColor
;
3155 IMEStyle
->mUnderlineColor
= lineColor
;
3156 IMEStyle
->mUnderlineStyle
= sUnderlineStyles
[lineStyle
];
3157 IMEStyle
->mInit
= PR_TRUE
;
3159 if (mIMEUnderlineRelativeSize
== -1.0f
) {
3160 look
->GetMetric(nsILookAndFeel::eMetricFloat_IMEUnderlineRelativeSize
,
3161 mIMEUnderlineRelativeSize
);
3162 NS_ASSERTION(mIMEUnderlineRelativeSize
>= 0.0f
,
3163 "underline size must be larger than 0");
3167 inline nscolor
Get40PercentColor(nscolor aForeColor
, nscolor aBackColor
)
3169 nscolor foreColor
= NS_RGBA(NS_GET_R(aForeColor
),
3170 NS_GET_G(aForeColor
),
3171 NS_GET_B(aForeColor
),
3172 (PRUint8
)(255 * 0.4f
));
3173 // Don't use true alpha color for readability.
3174 return NS_ComposeColors(aBackColor
, foreColor
);
3178 nsTextPaintStyle::GetResolvedForeColor(nscolor aColor
,
3179 nscolor aDefaultForeColor
,
3182 if (aColor
== NS_SAME_AS_FOREGROUND_COLOR
)
3183 return aDefaultForeColor
;
3185 if (aColor
!= NS_40PERCENT_FOREGROUND_COLOR
)
3188 // Get actual background color
3189 nscolor actualBGColor
= aBackColor
;
3190 if (actualBGColor
== NS_TRANSPARENT
) {
3192 actualBGColor
= mFrameBackgroundColor
;
3194 return Get40PercentColor(aDefaultForeColor
, actualBGColor
);
3197 //-----------------------------------------------------------------------------
3199 #ifdef ACCESSIBILITY
3200 NS_IMETHODIMP
nsTextFrame::GetAccessible(nsIAccessible
** aAccessible
)
3203 nsAutoString renderedWhitespace
;
3204 GetRenderedText(&renderedWhitespace
, nsnull
, nsnull
, 0, 1);
3205 if (renderedWhitespace
.IsEmpty()) {
3206 return NS_ERROR_FAILURE
;
3210 nsCOMPtr
<nsIAccessibilityService
> accService
= do_GetService("@mozilla.org/accessibilityService;1");
3213 return accService
->CreateHTMLTextAccessible(static_cast<nsIFrame
*>(this), aAccessible
);
3215 return NS_ERROR_FAILURE
;
3220 //-----------------------------------------------------------------------------
3222 nsTextFrame::Init(nsIContent
* aContent
,
3224 nsIFrame
* aPrevInFlow
)
3226 NS_ASSERTION(!aPrevInFlow
, "Can't be a continuation!");
3227 NS_PRECONDITION(aContent
->IsNodeOfType(nsINode::eTEXT
),
3229 // We're not a continuing frame.
3230 // mContentOffset = 0; not necessary since we get zeroed out at init
3231 return nsFrame::Init(aContent
, aParent
, aPrevInFlow
);
3235 nsTextFrame::Destroy()
3238 if (mNextContinuation
) {
3239 mNextContinuation
->SetPrevInFlow(nsnull
);
3241 // Let the base class destroy the frame
3245 class nsContinuingTextFrame
: public nsTextFrame
{
3247 friend nsIFrame
* NS_NewContinuingTextFrame(nsIPresShell
* aPresShell
, nsStyleContext
* aContext
);
3249 NS_IMETHOD
Init(nsIContent
* aContent
,
3251 nsIFrame
* aPrevInFlow
);
3253 virtual void Destroy();
3255 virtual nsIFrame
* GetPrevContinuation() const {
3256 return mPrevContinuation
;
3258 NS_IMETHOD
SetPrevContinuation(nsIFrame
* aPrevContinuation
) {
3259 NS_ASSERTION (!aPrevContinuation
|| GetType() == aPrevContinuation
->GetType(),
3260 "setting a prev continuation with incorrect type!");
3261 NS_ASSERTION (!nsSplittableFrame::IsInPrevContinuationChain(aPrevContinuation
, this),
3262 "creating a loop in continuation chain!");
3263 mPrevContinuation
= aPrevContinuation
;
3264 RemoveStateBits(NS_FRAME_IS_FLUID_CONTINUATION
);
3267 virtual nsIFrame
* GetPrevInFlowVirtual() const { return GetPrevInFlow(); }
3268 nsIFrame
* GetPrevInFlow() const {
3269 return (GetStateBits() & NS_FRAME_IS_FLUID_CONTINUATION
) ? mPrevContinuation
: nsnull
;
3271 NS_IMETHOD
SetPrevInFlow(nsIFrame
* aPrevInFlow
) {
3272 NS_ASSERTION (!aPrevInFlow
|| GetType() == aPrevInFlow
->GetType(),
3273 "setting a prev in flow with incorrect type!");
3274 NS_ASSERTION (!nsSplittableFrame::IsInPrevContinuationChain(aPrevInFlow
, this),
3275 "creating a loop in continuation chain!");
3276 mPrevContinuation
= aPrevInFlow
;
3277 AddStateBits(NS_FRAME_IS_FLUID_CONTINUATION
);
3280 virtual nsIFrame
* GetFirstInFlow() const;
3281 virtual nsIFrame
* GetFirstContinuation() const;
3283 virtual void AddInlineMinWidth(nsIRenderingContext
*aRenderingContext
,
3284 InlineMinWidthData
*aData
);
3285 virtual void AddInlinePrefWidth(nsIRenderingContext
*aRenderingContext
,
3286 InlinePrefWidthData
*aData
);
3288 virtual nsresult
GetRenderedText(nsAString
* aString
= nsnull
,
3289 gfxSkipChars
* aSkipChars
= nsnull
,
3290 gfxSkipCharsIterator
* aSkipIter
= nsnull
,
3291 PRUint32 aSkippedStartOffset
= 0,
3292 PRUint32 aSkippedMaxLength
= PR_UINT32_MAX
)
3293 { return NS_ERROR_NOT_IMPLEMENTED
; } // Call on a primary text frame only
3296 nsContinuingTextFrame(nsStyleContext
* aContext
) : nsTextFrame(aContext
) {}
3297 nsIFrame
* mPrevContinuation
;
3301 nsContinuingTextFrame::Init(nsIContent
* aContent
,
3303 nsIFrame
* aPrevInFlow
)
3305 NS_ASSERTION(aPrevInFlow
, "Must be a continuation!");
3306 // NOTE: bypassing nsTextFrame::Init!!!
3307 nsresult rv
= nsFrame::Init(aContent
, aParent
, aPrevInFlow
);
3310 nsTextFrame
* nextContinuation
=
3311 static_cast<nsTextFrame
*>(aPrevInFlow
->GetNextContinuation());
3313 // Hook the frame into the flow
3314 SetPrevInFlow(aPrevInFlow
);
3315 aPrevInFlow
->SetNextInFlow(this);
3316 nsTextFrame
* prev
= static_cast<nsTextFrame
*>(aPrevInFlow
);
3317 mContentOffset
= prev
->GetContentOffset() + prev
->GetContentLengthHint();
3318 NS_ASSERTION(mContentOffset
< PRInt32(aContent
->GetText()->GetLength()),
3319 "Creating ContinuingTextFrame, but there is no more content");
3320 if (prev
->GetStyleContext() != GetStyleContext()) {
3321 // We're taking part of prev's text, and its style may be different
3322 // so clear its textrun which may no longer be valid (and don't set ours)
3323 prev
->ClearTextRun();
3325 mTextRun
= prev
->GetTextRun();
3328 if (aPrevInFlow
->GetStateBits() & NS_FRAME_IS_BIDI
) {
3329 nsPropertyTable
*propTable
= PresContext()->PropertyTable();
3330 propTable
->SetProperty(this, nsGkAtoms::embeddingLevel
,
3331 propTable
->GetProperty(aPrevInFlow
, nsGkAtoms::embeddingLevel
),
3333 propTable
->SetProperty(this, nsGkAtoms::baseLevel
,
3334 propTable
->GetProperty(aPrevInFlow
, nsGkAtoms::baseLevel
),
3336 propTable
->SetProperty(this, nsGkAtoms::charType
,
3337 propTable
->GetProperty(aPrevInFlow
, nsGkAtoms::charType
),
3339 if (nextContinuation
) {
3340 SetNextContinuation(nextContinuation
);
3341 nextContinuation
->SetPrevContinuation(this);
3342 // Adjust next-continuations' content offset as needed.
3343 while (nextContinuation
&&
3344 nextContinuation
->GetContentOffset() < mContentOffset
) {
3346 propTable
->GetProperty(this, nsGkAtoms::embeddingLevel
) ==
3347 propTable
->GetProperty(nextContinuation
, nsGkAtoms::embeddingLevel
) &&
3348 propTable
->GetProperty(this, nsGkAtoms::baseLevel
) ==
3349 propTable
->GetProperty(nextContinuation
, nsGkAtoms::baseLevel
) &&
3350 propTable
->GetProperty(this, nsGkAtoms::charType
) ==
3351 propTable
->GetProperty(nextContinuation
, nsGkAtoms::charType
),
3352 "stealing text from different type of BIDI continuation");
3353 nextContinuation
->mContentOffset
= mContentOffset
;
3354 nextContinuation
= static_cast<nsTextFrame
*>(nextContinuation
->GetNextContinuation());
3357 mState
|= NS_FRAME_IS_BIDI
;
3358 } // prev frame is bidi
3365 nsContinuingTextFrame::Destroy()
3368 if (mPrevContinuation
|| mNextContinuation
) {
3369 nsSplittableFrame::RemoveFromFlow(this);
3371 // Let the base class destroy the frame
3376 nsContinuingTextFrame::GetFirstInFlow() const
3378 // Can't cast to |nsContinuingTextFrame*| because the first one isn't.
3379 nsIFrame
*firstInFlow
,
3380 *previous
= const_cast<nsIFrame
*>
3381 (static_cast<const nsIFrame
*>(this));
3383 firstInFlow
= previous
;
3384 previous
= firstInFlow
->GetPrevInFlow();
3390 nsContinuingTextFrame::GetFirstContinuation() const
3392 // Can't cast to |nsContinuingTextFrame*| because the first one isn't.
3393 nsIFrame
*firstContinuation
,
3394 *previous
= const_cast<nsIFrame
*>
3395 (static_cast<const nsIFrame
*>(mPrevContinuation
));
3397 NS_ASSERTION(previous
, "How can an nsContinuingTextFrame be the first continuation?");
3400 firstContinuation
= previous
;
3401 previous
= firstContinuation
->GetPrevContinuation();
3403 return firstContinuation
;
3406 // XXX Do we want to do all the work for the first-in-flow or do the
3407 // work for each part? (Be careful of first-letter / first-line, though,
3408 // especially first-line!) Doing all the work on the first-in-flow has
3409 // the advantage of avoiding the potential for incremental reflow bugs,
3410 // but depends on our maintining the frame tree in reasonable ways even
3411 // for edge cases (block-within-inline splits, nextBidi, etc.)
3413 // XXX We really need to make :first-letter happen during frame
3416 // Needed for text frames in XUL.
3417 /* virtual */ nscoord
3418 nsTextFrame::GetMinWidth(nsIRenderingContext
*aRenderingContext
)
3420 return nsLayoutUtils::MinWidthFromInline(this, aRenderingContext
);
3423 // Needed for text frames in XUL.
3424 /* virtual */ nscoord
3425 nsTextFrame::GetPrefWidth(nsIRenderingContext
*aRenderingContext
)
3427 return nsLayoutUtils::PrefWidthFromInline(this, aRenderingContext
);
3431 nsContinuingTextFrame::AddInlineMinWidth(nsIRenderingContext
*aRenderingContext
,
3432 InlineMinWidthData
*aData
)
3434 // Do nothing, since the first-in-flow accounts for everything.
3439 nsContinuingTextFrame::AddInlinePrefWidth(nsIRenderingContext
*aRenderingContext
,
3440 InlinePrefWidthData
*aData
)
3442 // Do nothing, since the first-in-flow accounts for everything.
3447 DestroySelectionDetails(SelectionDetails
* aDetails
)
3450 SelectionDetails
* next
= aDetails
->mNext
;
3456 //----------------------------------------------------------------------
3458 #if defined(DEBUG_rbs) || defined(DEBUG_bzbarsky)
3460 VerifyNotDirty(nsFrameState state
)
3462 PRBool isZero
= state
& NS_FRAME_FIRST_REFLOW
;
3463 PRBool isDirty
= state
& NS_FRAME_IS_DIRTY
;
3464 if (!isZero
&& isDirty
)
3465 NS_WARNING("internal offsets may be out-of-sync");
3467 #define DEBUG_VERIFY_NOT_DIRTY(state) \
3468 VerifyNotDirty(state)
3470 #define DEBUG_VERIFY_NOT_DIRTY(state)
3474 NS_NewTextFrame(nsIPresShell
* aPresShell
, nsStyleContext
* aContext
)
3476 return new (aPresShell
) nsTextFrame(aContext
);
3480 NS_NewContinuingTextFrame(nsIPresShell
* aPresShell
, nsStyleContext
* aContext
)
3482 return new (aPresShell
) nsContinuingTextFrame(aContext
);
3485 nsTextFrame::~nsTextFrame()
3487 if (0 != (mState
& TEXT_BLINK_ON
))
3489 nsBlinkTimer::RemoveBlinkFrame(this);
3494 nsTextFrame::GetCursor(const nsPoint
& aPoint
,
3495 nsIFrame::Cursor
& aCursor
)
3497 FillCursorInformationFromStyle(GetStyleUserInterface(), aCursor
);
3498 if (NS_STYLE_CURSOR_AUTO
== aCursor
.mCursor
) {
3499 aCursor
.mCursor
= NS_STYLE_CURSOR_TEXT
;
3501 // If tabindex >= 0, use default cursor to indicate it's not selectable
3502 nsIFrame
*ancestorFrame
= this;
3503 while ((ancestorFrame
= ancestorFrame
->GetParent()) != nsnull
) {
3504 nsIContent
*ancestorContent
= ancestorFrame
->GetContent();
3505 if (ancestorContent
&& ancestorContent
->HasAttr(kNameSpaceID_None
, nsGkAtoms::tabindex
)) {
3506 nsAutoString tabIndexStr
;
3507 ancestorContent
->GetAttr(kNameSpaceID_None
, nsGkAtoms::tabindex
, tabIndexStr
);
3508 if (!tabIndexStr
.IsEmpty()) {
3509 PRInt32 rv
, tabIndexVal
= tabIndexStr
.ToInteger(&rv
);
3510 if (NS_SUCCEEDED(rv
) && tabIndexVal
>= 0) {
3511 aCursor
.mCursor
= NS_STYLE_CURSOR_DEFAULT
;
3523 nsTextFrame::GetLastInFlow() const
3525 nsTextFrame
* lastInFlow
= const_cast<nsTextFrame
*>(this);
3526 while (lastInFlow
->GetNextInFlow()) {
3527 lastInFlow
= static_cast<nsTextFrame
*>(lastInFlow
->GetNextInFlow());
3529 NS_POSTCONDITION(lastInFlow
, "illegal state in flow chain.");
3533 nsTextFrame::GetLastContinuation() const
3535 nsTextFrame
* lastInFlow
= const_cast<nsTextFrame
*>(this);
3536 while (lastInFlow
->mNextContinuation
) {
3537 lastInFlow
= static_cast<nsTextFrame
*>(lastInFlow
->mNextContinuation
);
3539 NS_POSTCONDITION(lastInFlow
, "illegal state in continuation chain.");
3544 nsTextFrame::ClearTextRun()
3546 // save textrun because ClearAllTextRunReferences will clear ours
3547 gfxTextRun
* textRun
= mTextRun
;
3552 UnhookTextRunFromFrames(textRun
);
3553 // see comments in BuildTextRunForFrames...
3554 // if (textRun->GetFlags() & gfxFontGroup::TEXT_IS_PERSISTENT) {
3555 // NS_ERROR("Shouldn't reach here for now...");
3556 // // the textrun's text may be referencing a DOM node that has changed,
3557 // // so we'd better kill this textrun now.
3558 // if (textRun->GetExpirationState()->IsTracked()) {
3559 // gTextRuns->RemoveFromCache(textRun);
3565 if (!(textRun
->GetFlags() & gfxTextRunWordCache::TEXT_IN_CACHE
)) {
3566 // Remove it now because it's not doing anything useful
3567 gTextRuns
->RemoveFromCache(textRun
);
3573 ClearTextRunsInFlowChain(nsTextFrame
* aFrame
)
3576 for (f
= aFrame
; f
; f
= static_cast<nsTextFrame
*>(f
->GetNextInFlow())) {
3582 nsTextFrame::CharacterDataChanged(nsPresContext
* aPresContext
,
3586 ClearTextRunsInFlowChain(this);
3588 nsTextFrame
* targetTextFrame
;
3589 PRInt32 nodeLength
= mContent
->GetText()->GetLength();
3592 targetTextFrame
= static_cast<nsTextFrame
*>(GetLastContinuation());
3593 targetTextFrame
->mState
&= ~TEXT_WHITESPACE_FLAGS
;
3595 // Mark all the continuation frames as dirty, and fix up content offsets to
3597 // Don't set NS_FRAME_IS_DIRTY on |this|, since we call FrameNeedsReflow
3599 nsTextFrame
* textFrame
= this;
3600 PRInt32 newLength
= nodeLength
;
3602 textFrame
->mState
&= ~TEXT_WHITESPACE_FLAGS
;
3603 // If the text node has shrunk, clip the frame contentlength as necessary
3604 if (textFrame
->mContentOffset
> newLength
) {
3605 textFrame
->mContentOffset
= newLength
;
3607 textFrame
= static_cast<nsTextFrame
*>(textFrame
->GetNextContinuation());
3611 textFrame
->mState
|= NS_FRAME_IS_DIRTY
;
3613 targetTextFrame
= this;
3616 // Ask the parent frame to reflow me.
3617 aPresContext
->GetPresShell()->FrameNeedsReflow(targetTextFrame
,
3618 nsIPresShell::eStyleChange
,
3625 nsTextFrame::DidSetStyleContext()
3631 class nsDisplayText
: public nsDisplayItem
{
3633 nsDisplayText(nsTextFrame
* aFrame
) : nsDisplayItem(aFrame
) {
3634 MOZ_COUNT_CTOR(nsDisplayText
);
3636 #ifdef NS_BUILD_REFCNT_LOGGING
3637 virtual ~nsDisplayText() {
3638 MOZ_COUNT_DTOR(nsDisplayText
);
3642 virtual nsRect
GetBounds(nsDisplayListBuilder
* aBuilder
) {
3643 return mFrame
->GetOverflowRect() + aBuilder
->ToReferenceFrame(mFrame
);
3645 virtual nsIFrame
* HitTest(nsDisplayListBuilder
* aBuilder
, nsPoint aPt
,
3646 HitTestState
* aState
) {
3647 return nsRect(aBuilder
->ToReferenceFrame(mFrame
), mFrame
->GetSize()).Contains(aPt
) ? mFrame
: nsnull
;
3649 virtual void Paint(nsDisplayListBuilder
* aBuilder
, nsIRenderingContext
* aCtx
,
3650 const nsRect
& aDirtyRect
);
3651 NS_DISPLAY_DECL_NAME("Text")
3655 nsDisplayText::Paint(nsDisplayListBuilder
* aBuilder
,
3656 nsIRenderingContext
* aCtx
, const nsRect
& aDirtyRect
) {
3657 static_cast<nsTextFrame
*>(mFrame
)->
3658 PaintText(aCtx
, aBuilder
->ToReferenceFrame(mFrame
), aDirtyRect
);
3662 nsTextFrame::BuildDisplayList(nsDisplayListBuilder
* aBuilder
,
3663 const nsRect
& aDirtyRect
,
3664 const nsDisplayListSet
& aLists
)
3666 if (!IsVisibleForPainting(aBuilder
))
3669 DO_GLOBAL_REFLOW_COUNT_DSP("nsTextFrame");
3671 if ((0 != (mState
& TEXT_BLINK_ON
)) && nsBlinkTimer::GetBlinkIsOff() &&
3672 PresContext()->IsDynamic())
3675 return aLists
.Content()->AppendNewToTop(new (aBuilder
) nsDisplayText(this));
3679 GetGeneratedContentOwner(nsIFrame
* aFrame
, PRBool
* aIsBefore
)
3681 *aIsBefore
= PR_FALSE
;
3682 while (aFrame
&& (aFrame
->GetStateBits() & NS_FRAME_GENERATED_CONTENT
)) {
3683 if (aFrame
->GetStyleContext()->GetPseudoType() == nsCSSPseudoElements::before
) {
3684 *aIsBefore
= PR_TRUE
;
3686 aFrame
= aFrame
->GetParent();
3692 nsTextFrame::GetSelectionDetails()
3694 const nsFrameSelection
* frameSelection
= GetConstFrameSelection();
3695 if (!(GetStateBits() & NS_FRAME_GENERATED_CONTENT
)) {
3696 SelectionDetails
* details
=
3697 frameSelection
->LookUpSelection(mContent
, GetContentOffset(),
3698 GetContentLength(), PR_FALSE
);
3699 SelectionDetails
* sd
;
3700 for (sd
= details
; sd
; sd
= sd
->mNext
) {
3701 sd
->mStart
+= mContentOffset
;
3702 sd
->mEnd
+= mContentOffset
;
3707 // Check if the beginning or end of the element is selected, depending on
3708 // whether we're :before content or :after content.
3710 nsIFrame
* owner
= GetGeneratedContentOwner(this, &isBefore
);
3711 if (!owner
|| !owner
->GetContent())
3714 SelectionDetails
* details
=
3715 frameSelection
->LookUpSelection(owner
->GetContent(),
3716 isBefore
? 0 : owner
->GetContent()->GetChildCount(), 0, PR_FALSE
);
3717 SelectionDetails
* sd
;
3718 for (sd
= details
; sd
; sd
= sd
->mNext
) {
3719 // The entire text is selected!
3720 sd
->mStart
= GetContentOffset();
3721 sd
->mEnd
= GetContentEnd();
3727 FillClippedRect(gfxContext
* aCtx
, nsPresContext
* aPresContext
,
3728 nscolor aColor
, const gfxRect
& aDirtyRect
, const gfxRect
& aRect
)
3730 gfxRect r
= aRect
.Intersect(aDirtyRect
);
3731 // For now, we need to put this in pixel coordinates
3732 PRInt32 app
= aPresContext
->AppUnitsPerDevPixel();
3735 aCtx
->Rectangle(gfxRect(r
.X() / app
, r
.Y() / app
,
3736 r
.Width() / app
, r
.Height() / app
), PR_TRUE
);
3737 aCtx
->SetColor(gfxRGBA(aColor
));
3741 nsTextFrame::TextDecorations
3742 nsTextFrame::GetTextDecorations(nsPresContext
* aPresContext
)
3744 TextDecorations decorations
;
3746 // Quirks mode text decoration are rendered by children; see bug 1777
3747 // In non-quirks mode, nsHTMLContainer::Paint and nsBlockFrame::Paint
3748 // does the painting of text decorations.
3749 if (eCompatibility_NavQuirks
!= aPresContext
->CompatibilityMode())
3752 PRBool useOverride
= PR_FALSE
;
3753 nscolor overrideColor
;
3755 // A mask of all possible decorations.
3756 PRUint8 decorMask
= NS_STYLE_TEXT_DECORATION_UNDERLINE
|
3757 NS_STYLE_TEXT_DECORATION_OVERLINE
|
3758 NS_STYLE_TEXT_DECORATION_LINE_THROUGH
;
3760 for (nsStyleContext
* context
= GetStyleContext();
3761 decorMask
&& context
&& context
->HasTextDecorations();
3762 context
= context
->GetParent()) {
3763 const nsStyleTextReset
* styleText
= context
->GetStyleTextReset();
3765 (NS_STYLE_TEXT_DECORATION_OVERRIDE_ALL
& styleText
->mTextDecoration
)) {
3766 // This handles the <a href="blah.html"><font color="green">La
3767 // la la</font></a> case. The link underline should be green.
3768 useOverride
= PR_TRUE
;
3769 overrideColor
= context
->GetStyleColor()->mColor
;
3772 PRUint8 useDecorations
= decorMask
& styleText
->mTextDecoration
;
3773 if (useDecorations
) {// a decoration defined here
3774 nscolor color
= context
->GetStyleColor()->mColor
;
3776 if (NS_STYLE_TEXT_DECORATION_UNDERLINE
& useDecorations
) {
3777 decorations
.mUnderColor
= useOverride
? overrideColor
: color
;
3778 decorMask
&= ~NS_STYLE_TEXT_DECORATION_UNDERLINE
;
3779 decorations
.mDecorations
|= NS_STYLE_TEXT_DECORATION_UNDERLINE
;
3781 if (NS_STYLE_TEXT_DECORATION_OVERLINE
& useDecorations
) {
3782 decorations
.mOverColor
= useOverride
? overrideColor
: color
;
3783 decorMask
&= ~NS_STYLE_TEXT_DECORATION_OVERLINE
;
3784 decorations
.mDecorations
|= NS_STYLE_TEXT_DECORATION_OVERLINE
;
3786 if (NS_STYLE_TEXT_DECORATION_LINE_THROUGH
& useDecorations
) {
3787 decorations
.mStrikeColor
= useOverride
? overrideColor
: color
;
3788 decorMask
&= ~NS_STYLE_TEXT_DECORATION_LINE_THROUGH
;
3789 decorations
.mDecorations
|= NS_STYLE_TEXT_DECORATION_LINE_THROUGH
;
3798 nsTextFrame::UnionTextDecorationOverflow(nsPresContext
* aPresContext
,
3799 PropertyProvider
& aProvider
,
3800 nsRect
* aOverflowRect
)
3802 // Text-shadow overflows
3803 nsRect shadowRect
= nsLayoutUtils::GetTextShadowRectsUnion(*aOverflowRect
, this);
3804 aOverflowRect
->UnionRect(*aOverflowRect
, shadowRect
);
3806 if (IsFloatingFirstLetterChild()) {
3807 // The underline/overline drawable area must be contained in the overflow
3808 // rect when this is in floating first letter frame at *both* modes.
3809 nscoord fontAscent
, fontHeight
;
3810 nsIFontMetrics
* fm
= aProvider
.GetFontMetrics();
3811 fm
->GetMaxAscent(fontAscent
);
3812 fm
->GetMaxHeight(fontHeight
);
3813 nsRect
fontRect(0, mAscent
- fontAscent
, GetSize().width
, fontHeight
);
3814 aOverflowRect
->UnionRect(*aOverflowRect
, fontRect
);
3817 // When this frame is not selected, the text-decoration area must be in
3820 if (!(GetStateBits() & NS_FRAME_SELECTED_CONTENT
) ||
3821 !HasSelectionOverflowingDecorations(aPresContext
, &ratio
))
3824 nsLineLayout::CombineTextDecorations(aPresContext
,
3825 NS_STYLE_TEXT_DECORATION_UNDERLINE
,
3826 this, *aOverflowRect
, mAscent
, ratio
);
3827 AddStateBits(TEXT_SELECTION_UNDERLINE_OVERFLOWED
);
3831 nsTextFrame::PaintTextDecorations(gfxContext
* aCtx
, const gfxRect
& aDirtyRect
,
3832 const gfxPoint
& aFramePt
,
3833 const gfxPoint
& aTextBaselinePt
,
3834 nsTextPaintStyle
& aTextPaintStyle
,
3835 PropertyProvider
& aProvider
,
3836 const nscolor
& aOverrideColor
)
3838 TextDecorations decorations
=
3839 GetTextDecorations(aTextPaintStyle
.PresContext());
3840 if (!decorations
.HasDecorationlines())
3843 gfxFont
* firstFont
= aProvider
.GetFontGroup()->GetFontAt(0);
3846 const gfxFont::Metrics
& fontMetrics
= firstFont
->GetMetrics();
3847 gfxFloat app
= aTextPaintStyle
.PresContext()->AppUnitsPerDevPixel();
3849 // XXX aFramePt is in AppUnits, shouldn't it be nsFloatPoint?
3850 gfxPoint
pt(aFramePt
.x
/ app
, (aTextBaselinePt
.y
- mAscent
) / app
);
3851 gfxSize
size(GetRect().width
/ app
, 0);
3852 gfxFloat ascent
= gfxFloat(mAscent
) / app
;
3855 if (decorations
.HasOverline()) {
3856 lineColor
= aOverrideColor
? aOverrideColor
: decorations
.mOverColor
;
3857 size
.height
= fontMetrics
.underlineSize
;
3858 nsCSSRendering::PaintDecorationLine(
3859 aCtx
, lineColor
, pt
, size
, ascent
, fontMetrics
.maxAscent
,
3860 NS_STYLE_TEXT_DECORATION_OVERLINE
, NS_STYLE_BORDER_STYLE_SOLID
);
3862 if (decorations
.HasUnderline()) {
3863 lineColor
= aOverrideColor
? aOverrideColor
: decorations
.mUnderColor
;
3864 size
.height
= fontMetrics
.underlineSize
;
3865 gfxFloat offset
= aProvider
.GetFontGroup()->GetUnderlineOffset();
3866 nsCSSRendering::PaintDecorationLine(
3867 aCtx
, lineColor
, pt
, size
, ascent
, offset
,
3868 NS_STYLE_TEXT_DECORATION_UNDERLINE
, NS_STYLE_BORDER_STYLE_SOLID
);
3870 if (decorations
.HasStrikeout()) {
3871 lineColor
= aOverrideColor
? aOverrideColor
: decorations
.mStrikeColor
;
3872 size
.height
= fontMetrics
.strikeoutSize
;
3873 gfxFloat offset
= fontMetrics
.strikeoutOffset
;
3874 nsCSSRendering::PaintDecorationLine(
3875 aCtx
, lineColor
, pt
, size
, ascent
, offset
,
3876 NS_STYLE_TEXT_DECORATION_LINE_THROUGH
, NS_STYLE_BORDER_STYLE_SOLID
);
3880 // Make sure this stays in sync with DrawSelectionDecorations below
3881 static const SelectionType SelectionTypesWithDecorations
=
3882 nsISelectionController::SELECTION_SPELLCHECK
|
3883 nsISelectionController::SELECTION_IME_RAWINPUT
|
3884 nsISelectionController::SELECTION_IME_SELECTEDRAWTEXT
|
3885 nsISelectionController::SELECTION_IME_CONVERTEDTEXT
|
3886 nsISelectionController::SELECTION_IME_SELECTEDCONVERTEDTEXT
;
3888 static void DrawIMEUnderline(gfxContext
* aContext
, PRInt32 aIndex
,
3889 nsTextPaintStyle
& aTextPaintStyle
, const gfxPoint
& aPt
, gfxFloat aWidth
,
3890 gfxFloat aAscent
, gfxFloat aSize
, gfxFloat aOffset
)
3895 if (!aTextPaintStyle
.GetIMEUnderline(aIndex
, &color
, &relativeSize
, &style
))
3898 gfxFloat actualSize
= relativeSize
* aSize
;
3899 gfxFloat width
= PR_MAX(0, aWidth
- 2.0 * aSize
);
3900 gfxPoint
pt(aPt
.x
+ 1.0, aPt
.y
);
3901 nsCSSRendering::PaintDecorationLine(
3902 aContext
, color
, pt
, gfxSize(width
, actualSize
), aAscent
, aOffset
,
3903 NS_STYLE_TEXT_DECORATION_UNDERLINE
, style
);
3907 * This, plus SelectionTypesWithDecorations, encapsulates all knowledge about
3908 * drawing text decoration for selections.
3910 static void DrawSelectionDecorations(gfxContext
* aContext
, SelectionType aType
,
3911 nsTextPaintStyle
& aTextPaintStyle
, const gfxPoint
& aPt
, gfxFloat aWidth
,
3912 gfxFloat aAscent
, const gfxFont::Metrics
& aFontMetrics
)
3914 gfxSize
size(aWidth
, aFontMetrics
.underlineSize
);
3917 case nsISelectionController::SELECTION_SPELLCHECK
: {
3918 nsCSSRendering::PaintDecorationLine(
3919 aContext
, NS_RGB(255,0,0),
3920 aPt
, size
, aAscent
, aFontMetrics
.underlineOffset
,
3921 NS_STYLE_TEXT_DECORATION_UNDERLINE
, NS_STYLE_BORDER_STYLE_DOTTED
);
3925 case nsISelectionController::SELECTION_IME_RAWINPUT
:
3926 DrawIMEUnderline(aContext
, nsTextPaintStyle::eIndexRawInput
,
3927 aTextPaintStyle
, aPt
, aWidth
, aAscent
, size
.height
,
3928 aFontMetrics
.underlineOffset
);
3930 case nsISelectionController::SELECTION_IME_SELECTEDRAWTEXT
:
3931 DrawIMEUnderline(aContext
, nsTextPaintStyle::eIndexSelRawText
,
3932 aTextPaintStyle
, aPt
, aWidth
, aAscent
, size
.height
,
3933 aFontMetrics
.underlineOffset
);
3935 case nsISelectionController::SELECTION_IME_CONVERTEDTEXT
:
3936 DrawIMEUnderline(aContext
, nsTextPaintStyle::eIndexConvText
,
3937 aTextPaintStyle
, aPt
, aWidth
, aAscent
, size
.height
,
3938 aFontMetrics
.underlineOffset
);
3940 case nsISelectionController::SELECTION_IME_SELECTEDCONVERTEDTEXT
:
3941 DrawIMEUnderline(aContext
, nsTextPaintStyle::eIndexSelConvText
,
3942 aTextPaintStyle
, aPt
, aWidth
, aAscent
, size
.height
,
3943 aFontMetrics
.underlineOffset
);
3947 NS_WARNING("Requested selection decorations when there aren't any");
3953 * This function encapsulates all knowledge of how selections affect foreground
3954 * and background colors.
3955 * @return true if the selection affects colors, false otherwise
3956 * @param aForeground the foreground color to use
3957 * @param aBackground the background color to use, or RGBA(0,0,0,0) if no
3958 * background should be painted
3960 static PRBool
GetSelectionTextColors(SelectionType aType
, nsTextPaintStyle
& aTextPaintStyle
,
3961 nscolor
* aForeground
, nscolor
* aBackground
)
3964 case nsISelectionController::SELECTION_NORMAL
:
3965 return aTextPaintStyle
.GetSelectionColors(aForeground
, aBackground
);
3966 case nsISelectionController::SELECTION_FIND
:
3967 aTextPaintStyle
.GetHighlightColors(aForeground
, aBackground
);
3969 case nsISelectionController::SELECTION_IME_RAWINPUT
:
3970 aTextPaintStyle
.GetIMESelectionColors(nsTextPaintStyle::eIndexRawInput
,
3971 aForeground
, aBackground
);
3973 case nsISelectionController::SELECTION_IME_SELECTEDRAWTEXT
:
3974 aTextPaintStyle
.GetIMESelectionColors(nsTextPaintStyle::eIndexSelRawText
,
3975 aForeground
, aBackground
);
3977 case nsISelectionController::SELECTION_IME_CONVERTEDTEXT
:
3978 aTextPaintStyle
.GetIMESelectionColors(nsTextPaintStyle::eIndexConvText
,
3979 aForeground
, aBackground
);
3981 case nsISelectionController::SELECTION_IME_SELECTEDCONVERTEDTEXT
:
3982 aTextPaintStyle
.GetIMESelectionColors(nsTextPaintStyle::eIndexSelConvText
,
3983 aForeground
, aBackground
);
3987 *aForeground
= aTextPaintStyle
.GetTextColor();
3988 *aBackground
= NS_RGBA(0,0,0,0);
3994 * This class lets us iterate over chunks of text in a uniform selection state,
3995 * observing cluster boundaries, in content order, maintaining the current
3996 * x-offset as we go, and telling whether the text chunk has a hyphen after
3997 * it or not. The caller is responsible for actually computing the advance
3998 * width of each chunk.
4000 class SelectionIterator
{
4003 * aStart and aLength are in the original string. aSelectionBuffer is
4004 * according to the original string.
4006 SelectionIterator(SelectionType
* aSelectionBuffer
, PRInt32 aStart
,
4007 PRInt32 aLength
, PropertyProvider
& aProvider
,
4008 gfxTextRun
* aTextRun
);
4011 * Returns the next segment of uniformly selected (or not) text.
4012 * @param aXOffset the offset from the origin of the frame to the start
4013 * of the text (the left baseline origin for LTR, the right baseline origin
4015 * @param aOffset the transformed string offset of the text for this segment
4016 * @param aLength the transformed string length of the text for this segment
4017 * @param aHyphenWidth if a hyphen is to be rendered after the text, the
4018 * width of the hyphen, otherwise zero
4019 * @param aType the selection type for this segment
4020 * @return false if there are no more segments
4022 PRBool
GetNextSegment(gfxFloat
* aXOffset
, PRUint32
* aOffset
, PRUint32
* aLength
,
4023 gfxFloat
* aHyphenWidth
, SelectionType
* aType
);
4024 void UpdateWithAdvance(gfxFloat aAdvance
) {
4025 mXOffset
+= aAdvance
*mTextRun
->GetDirection();
4029 SelectionType
* mSelectionBuffer
;
4030 PropertyProvider
& mProvider
;
4031 gfxTextRun
* mTextRun
;
4032 gfxSkipCharsIterator mIterator
;
4033 PRInt32 mOriginalStart
;
4034 PRInt32 mOriginalEnd
;
4038 SelectionIterator::SelectionIterator(SelectionType
* aSelectionBuffer
,
4039 PRInt32 aStart
, PRInt32 aLength
, PropertyProvider
& aProvider
,
4040 gfxTextRun
* aTextRun
)
4041 : mSelectionBuffer(aSelectionBuffer
), mProvider(aProvider
),
4042 mTextRun(aTextRun
), mIterator(aProvider
.GetStart()),
4043 mOriginalStart(aStart
), mOriginalEnd(aStart
+ aLength
),
4044 mXOffset(mTextRun
->IsRightToLeft() ? aProvider
.GetFrame()->GetSize().width
: 0)
4046 mIterator
.SetOriginalOffset(aStart
);
4049 PRBool
SelectionIterator::GetNextSegment(gfxFloat
* aXOffset
,
4050 PRUint32
* aOffset
, PRUint32
* aLength
, gfxFloat
* aHyphenWidth
, SelectionType
* aType
)
4052 if (mIterator
.GetOriginalOffset() >= mOriginalEnd
)
4055 // save offset into transformed string now
4056 PRUint32 runOffset
= mIterator
.GetSkippedOffset();
4058 PRInt32 index
= mIterator
.GetOriginalOffset() - mOriginalStart
;
4059 SelectionType type
= mSelectionBuffer
[index
];
4060 for (++index
; mOriginalStart
+ index
< mOriginalEnd
; ++index
) {
4061 if (mSelectionBuffer
[index
] != type
)
4064 mIterator
.SetOriginalOffset(index
+ mOriginalStart
);
4066 // Advance to the next cluster boundary
4067 while (mIterator
.GetOriginalOffset() < mOriginalEnd
&&
4068 !mIterator
.IsOriginalCharSkipped() &&
4069 !mTextRun
->IsClusterStart(mIterator
.GetSkippedOffset())) {
4070 mIterator
.AdvanceOriginal(1);
4073 PRBool haveHyphenBreak
=
4074 (mProvider
.GetFrame()->GetStateBits() & TEXT_HYPHEN_BREAK
) != 0;
4075 *aOffset
= runOffset
;
4076 *aLength
= mIterator
.GetSkippedOffset() - runOffset
;
4077 *aXOffset
= mXOffset
;
4079 if (mIterator
.GetOriginalOffset() == mOriginalEnd
&& haveHyphenBreak
) {
4080 *aHyphenWidth
= mProvider
.GetHyphenWidth();
4087 AddHyphenToMetrics(nsTextFrame
* aTextFrame
, gfxTextRun
* aBaseTextRun
,
4088 gfxTextRun::Metrics
* aMetrics
, PRBool aTightBoundingBox
,
4089 gfxContext
* aContext
)
4091 // Fix up metrics to include hyphen
4092 gfxTextRunCache::AutoTextRun
hyphenTextRun(
4093 GetHyphenTextRun(aBaseTextRun
, aContext
, aTextFrame
));
4094 if (!hyphenTextRun
.get())
4097 gfxTextRun::Metrics hyphenMetrics
=
4098 hyphenTextRun
->MeasureText(0, hyphenTextRun
->GetLength(), aTightBoundingBox
, aContext
, nsnull
);
4099 aMetrics
->CombineWith(hyphenMetrics
, aBaseTextRun
->IsRightToLeft());
4103 nsTextFrame::PaintOneShadow(PRUint32 aOffset
, PRUint32 aLength
,
4104 nsCSSShadowItem
* aShadowDetails
,
4105 PropertyProvider
* aProvider
, const gfxRect
& aDirtyRect
,
4106 const gfxPoint
& aFramePt
, const gfxPoint
& aTextBaselinePt
,
4107 gfxContext
* aCtx
, const nscolor
& aForegroundColor
)
4109 gfxPoint
shadowOffset(aShadowDetails
->mXOffset
.GetCoordValue(),
4110 aShadowDetails
->mYOffset
.GetCoordValue());
4111 nscoord blurRadius
= PR_MAX(aShadowDetails
->mRadius
.GetCoordValue(), 0);
4113 gfxTextRun::Metrics shadowMetrics
=
4114 mTextRun
->MeasureText(aOffset
, aLength
, PR_FALSE
,
4116 if (GetStateBits() & TEXT_HYPHEN_BREAK
) {
4117 AddHyphenToMetrics(this, mTextRun
, &shadowMetrics
, PR_FALSE
, aCtx
);
4120 // This rect is the box which is equivalent to where the shadow will be painted.
4121 // The origin of mBoundingBox is the text baseline left, so we must translate it by
4122 // that much in order to make the origin the top-left corner of the text bounding box.
4123 gfxRect shadowRect
= shadowMetrics
.mBoundingBox
+
4124 gfxPoint(aFramePt
.x
, aTextBaselinePt
.y
) + shadowOffset
;
4126 nsContextBoxBlur contextBoxBlur
;
4127 gfxContext
* shadowContext
= contextBoxBlur
.Init(shadowRect
, blurRadius
,
4128 PresContext()->AppUnitsPerDevPixel(),
4133 nscolor shadowColor
;
4134 if (aShadowDetails
->mHasColor
)
4135 shadowColor
= aShadowDetails
->mColor
;
4137 shadowColor
= aForegroundColor
;
4141 aCtx
->SetColor(gfxRGBA(shadowColor
));
4143 // Draw the text onto our alpha-only surface to capture the alpha values.
4144 // Remember that the box blur context has a device offset on it, so we don't need to
4145 // translate any coordinates to fit on the surface.
4146 gfxFloat advanceWidth
;
4147 DrawText(shadowContext
,
4148 aTextBaselinePt
+ shadowOffset
,
4149 aOffset
, aLength
, &aDirtyRect
, aProvider
, advanceWidth
,
4150 (GetStateBits() & TEXT_HYPHEN_BREAK
) != 0);
4152 // This will only have an effect in quirks mode. Standards mode text-decoration shadow painting
4153 // is handled in nsHTMLContainerFrame.cpp, so you must remember to consider that if you change
4154 // any code behaviour here.
4155 nsTextPaintStyle
textPaintStyle(this);
4156 PaintTextDecorations(shadowContext
, aDirtyRect
, aFramePt
+ shadowOffset
,
4157 aTextBaselinePt
+ shadowOffset
,
4158 textPaintStyle
, *aProvider
, shadowColor
);
4160 contextBoxBlur
.DoPaint();
4164 // Paints selection backgrounds and text in the correct colors. Also computes
4165 // aAllTypes, the union of all selection types that are applying to this text.
4167 nsTextFrame::PaintTextWithSelectionColors(gfxContext
* aCtx
,
4168 const gfxPoint
& aFramePt
,
4169 const gfxPoint
& aTextBaselinePt
, const gfxRect
& aDirtyRect
,
4170 PropertyProvider
& aProvider
, nsTextPaintStyle
& aTextPaintStyle
,
4171 SelectionDetails
* aDetails
, SelectionType
* aAllTypes
)
4173 PRInt32 contentOffset
= aProvider
.GetStart().GetOriginalOffset();
4174 PRInt32 contentLength
= aProvider
.GetOriginalLength();
4176 // Figure out which selections control the colors to use for each character.
4177 nsAutoTArray
<SelectionType
,BIG_TEXT_NODE_SIZE
> prevailingSelectionsBuffer
;
4178 if (!prevailingSelectionsBuffer
.AppendElements(contentLength
))
4180 SelectionType
* prevailingSelections
= prevailingSelectionsBuffer
.Elements();
4182 SelectionType allTypes
= 0;
4183 for (i
= 0; i
< contentLength
; ++i
) {
4184 prevailingSelections
[i
] = nsISelectionController::SELECTION_NONE
;
4187 SelectionDetails
*sdptr
= aDetails
;
4188 PRBool anyBackgrounds
= PR_FALSE
;
4190 PRInt32 start
= PR_MAX(0, sdptr
->mStart
- contentOffset
);
4191 PRInt32 end
= PR_MIN(contentLength
, sdptr
->mEnd
- contentOffset
);
4192 SelectionType type
= sdptr
->mType
;
4195 // Ignore selections that don't set colors
4196 nscolor foreground
, background
;
4197 if (GetSelectionTextColors(type
, aTextPaintStyle
, &foreground
, &background
)) {
4198 if (NS_GET_A(background
) > 0) {
4199 anyBackgrounds
= PR_TRUE
;
4201 for (i
= start
; i
< end
; ++i
) {
4202 PRInt16 currentPrevailingSelection
= prevailingSelections
[i
];
4203 // Favour normal selection over IME selections
4204 if (currentPrevailingSelection
== nsISelectionController::SELECTION_NONE
||
4205 type
< currentPrevailingSelection
) {
4206 prevailingSelections
[i
] = type
;
4211 sdptr
= sdptr
->mNext
;
4213 *aAllTypes
= allTypes
;
4215 gfxFloat xOffset
, hyphenWidth
;
4216 PRUint32 offset
, length
; // in transformed string
4218 // Draw background colors
4219 if (anyBackgrounds
) {
4220 SelectionIterator
iterator(prevailingSelections
, contentOffset
, contentLength
,
4221 aProvider
, mTextRun
);
4222 while (iterator
.GetNextSegment(&xOffset
, &offset
, &length
, &hyphenWidth
, &type
)) {
4223 nscolor foreground
, background
;
4224 GetSelectionTextColors(type
, aTextPaintStyle
, &foreground
, &background
);
4225 // Draw background color
4226 gfxFloat advance
= hyphenWidth
+
4227 mTextRun
->GetAdvanceWidth(offset
, length
, &aProvider
);
4228 if (NS_GET_A(background
) > 0) {
4229 gfxFloat x
= xOffset
- (mTextRun
->IsRightToLeft() ? advance
: 0);
4230 FillClippedRect(aCtx
, aTextPaintStyle
.PresContext(),
4231 background
, aDirtyRect
,
4232 gfxRect(aFramePt
.x
+ x
, aFramePt
.y
, advance
, GetSize().height
));
4234 iterator
.UpdateWithAdvance(advance
);
4239 SelectionIterator
iterator(prevailingSelections
, contentOffset
, contentLength
,
4240 aProvider
, mTextRun
);
4241 while (iterator
.GetNextSegment(&xOffset
, &offset
, &length
, &hyphenWidth
, &type
)) {
4242 nscolor foreground
, background
;
4243 GetSelectionTextColors(type
, aTextPaintStyle
, &foreground
, &background
);
4244 // Draw text segment
4245 aCtx
->SetColor(gfxRGBA(foreground
));
4248 DrawText(aCtx
, gfxPoint(aFramePt
.x
+ xOffset
, aTextBaselinePt
.y
),
4249 offset
, length
, &aDirtyRect
, &aProvider
,
4250 advance
, hyphenWidth
> 0);
4252 advance
+= hyphenWidth
;
4254 iterator
.UpdateWithAdvance(advance
);
4259 nsTextFrame::PaintTextSelectionDecorations(gfxContext
* aCtx
,
4260 const gfxPoint
& aFramePt
,
4261 const gfxPoint
& aTextBaselinePt
, const gfxRect
& aDirtyRect
,
4262 PropertyProvider
& aProvider
, nsTextPaintStyle
& aTextPaintStyle
,
4263 SelectionDetails
* aDetails
, SelectionType aSelectionType
)
4265 PRInt32 contentOffset
= aProvider
.GetStart().GetOriginalOffset();
4266 PRInt32 contentLength
= aProvider
.GetOriginalLength();
4268 // Figure out which characters will be decorated for this selection. Here
4269 // we just fill the buffer with either SELECTION_NONE or aSelectionType.
4270 nsAutoTArray
<SelectionType
,BIG_TEXT_NODE_SIZE
> selectedCharsBuffer
;
4271 if (!selectedCharsBuffer
.AppendElements(contentLength
))
4273 SelectionType
* selectedChars
= selectedCharsBuffer
.Elements();
4275 for (i
= 0; i
< contentLength
; ++i
) {
4276 selectedChars
[i
] = nsISelectionController::SELECTION_NONE
;
4279 SelectionDetails
*sdptr
= aDetails
;
4281 if (sdptr
->mType
== aSelectionType
) {
4282 PRInt32 start
= PR_MAX(0, sdptr
->mStart
- contentOffset
);
4283 PRInt32 end
= PR_MIN(contentLength
, sdptr
->mEnd
- contentOffset
);
4284 for (i
= start
; i
< end
; ++i
) {
4285 selectedChars
[i
] = aSelectionType
;
4288 sdptr
= sdptr
->mNext
;
4291 gfxFont
* firstFont
= aProvider
.GetFontGroup()->GetFontAt(0);
4294 gfxFont::Metrics
decorationMetrics(firstFont
->GetMetrics());
4295 decorationMetrics
.underlineOffset
=
4296 aProvider
.GetFontGroup()->GetUnderlineOffset();
4298 SelectionIterator
iterator(selectedChars
, contentOffset
, contentLength
,
4299 aProvider
, mTextRun
);
4300 gfxFloat xOffset
, hyphenWidth
;
4301 PRUint32 offset
, length
;
4302 PRInt32 app
= aTextPaintStyle
.PresContext()->AppUnitsPerDevPixel();
4303 // XXX aTextBaselinePt is in AppUnits, shouldn't it be nsFloatPoint?
4304 gfxPoint
pt(0.0, (aTextBaselinePt
.y
- mAscent
) / app
);
4306 while (iterator
.GetNextSegment(&xOffset
, &offset
, &length
, &hyphenWidth
, &type
)) {
4307 gfxFloat advance
= hyphenWidth
+
4308 mTextRun
->GetAdvanceWidth(offset
, length
, &aProvider
);
4309 if (type
== aSelectionType
) {
4310 pt
.x
= (aFramePt
.x
+ xOffset
-
4311 (mTextRun
->IsRightToLeft() ? advance
: 0)) / app
;
4312 gfxFloat width
= PR_ABS(advance
) / app
;
4313 DrawSelectionDecorations(aCtx
, aSelectionType
, aTextPaintStyle
,
4314 pt
, width
, mAscent
/ app
, decorationMetrics
);
4316 iterator
.UpdateWithAdvance(advance
);
4321 nsTextFrame::PaintTextWithSelection(gfxContext
* aCtx
,
4322 const gfxPoint
& aFramePt
,
4323 const gfxPoint
& aTextBaselinePt
, const gfxRect
& aDirtyRect
,
4324 PropertyProvider
& aProvider
, nsTextPaintStyle
& aTextPaintStyle
)
4326 SelectionDetails
* details
= GetSelectionDetails();
4330 SelectionType allTypes
;
4331 PaintTextWithSelectionColors(aCtx
, aFramePt
, aTextBaselinePt
, aDirtyRect
,
4332 aProvider
, aTextPaintStyle
, details
, &allTypes
);
4333 PaintTextDecorations(aCtx
, aDirtyRect
, aFramePt
, aTextBaselinePt
,
4334 aTextPaintStyle
, aProvider
);
4336 // Iterate through just the selection types that paint decorations and
4337 // paint decorations for any that actually occur in this frame. Paint
4338 // higher-numbered selection types below lower-numered ones on the
4339 // general principal that lower-numbered selections are higher priority.
4340 allTypes
&= SelectionTypesWithDecorations
;
4341 for (i
= nsISelectionController::NUM_SELECTIONTYPES
- 1; i
>= 1; --i
) {
4342 SelectionType type
= 1 << (i
- 1);
4343 if (allTypes
& type
) {
4344 // There is some selection of this type. Try to paint its decorations
4345 // (there might not be any for this type but that's OK,
4346 // PaintTextSelectionDecorations will exit early).
4347 PaintTextSelectionDecorations(aCtx
, aFramePt
, aTextBaselinePt
, aDirtyRect
,
4348 aProvider
, aTextPaintStyle
, details
, type
);
4352 DestroySelectionDetails(details
);
4357 ComputeTransformedLength(PropertyProvider
& aProvider
)
4359 gfxSkipCharsIterator
iter(aProvider
.GetStart());
4360 PRUint32 start
= iter
.GetSkippedOffset();
4361 iter
.AdvanceOriginal(aProvider
.GetOriginalLength());
4362 return iter
.GetSkippedOffset() - start
;
4366 nsTextFrame::GetSnappedBaselineY(gfxContext
* aContext
, gfxFloat aY
)
4368 gfxFloat appUnitsPerDevUnit
= mTextRun
->GetAppUnitsPerDevUnit();
4369 gfxFloat baseline
= aY
+ mAscent
;
4370 gfxRect
putativeRect(0, baseline
/appUnitsPerDevUnit
, 1, 1);
4371 if (!aContext
->UserToDevicePixelSnapped(putativeRect
))
4373 return aContext
->DeviceToUser(putativeRect
.pos
).y
*appUnitsPerDevUnit
;
4377 nsTextFrame::PaintText(nsIRenderingContext
* aRenderingContext
, nsPoint aPt
,
4378 const nsRect
& aDirtyRect
)
4380 // Don't pass in aRenderingContext here, because we need a *reference*
4381 // context and aRenderingContext might have some transform in it
4382 // XXX get the block and line passed to us somehow! This is slow!
4383 gfxSkipCharsIterator iter
= EnsureTextRun();
4387 nsTextPaintStyle
textPaintStyle(this);
4388 PropertyProvider
provider(this, iter
);
4389 // Trim trailing whitespace
4390 provider
.InitializeForDisplay(PR_TRUE
);
4392 gfxContext
* ctx
= aRenderingContext
->ThebesContext();
4394 gfxPoint
framePt(aPt
.x
, aPt
.y
);
4395 gfxPoint
textBaselinePt(
4396 mTextRun
->IsRightToLeft() ? gfxFloat(aPt
.x
+ GetSize().width
) : framePt
.x
,
4397 GetSnappedBaselineY(ctx
, aPt
.y
));
4399 gfxRect
dirtyRect(aDirtyRect
.x
, aDirtyRect
.y
,
4400 aDirtyRect
.width
, aDirtyRect
.height
);
4402 gfxFloat advanceWidth
;
4403 gfxRGBA foregroundColor
= gfxRGBA(textPaintStyle
.GetTextColor());
4405 // Paint the text shadow before doing any foreground stuff
4406 const nsStyleText
* textStyle
= GetStyleText();
4407 if (textStyle
->mTextShadow
) {
4408 // Text shadow happens with the last value being painted at the back,
4409 // ie. it is painted first.
4410 for (PRUint32 i
= textStyle
->mTextShadow
->Length(); i
> 0; --i
) {
4411 PaintOneShadow(provider
.GetStart().GetSkippedOffset(),
4412 ComputeTransformedLength(provider
),
4413 textStyle
->mTextShadow
->ShadowAt(i
- 1), &provider
,
4414 dirtyRect
, framePt
, textBaselinePt
, ctx
,
4415 textPaintStyle
.GetTextColor());
4419 // Fork off to the (slower) paint-with-selection path if necessary.
4420 if (GetNonGeneratedAncestor(this)->GetStateBits() & NS_FRAME_SELECTED_CONTENT
) {
4421 if (PaintTextWithSelection(ctx
, framePt
, textBaselinePt
,
4422 dirtyRect
, provider
, textPaintStyle
))
4426 ctx
->SetColor(foregroundColor
);
4428 DrawText(ctx
, textBaselinePt
, provider
.GetStart().GetSkippedOffset(),
4429 ComputeTransformedLength(provider
), &dirtyRect
,
4430 &provider
, advanceWidth
,
4431 (GetStateBits() & TEXT_HYPHEN_BREAK
) != 0);
4432 PaintTextDecorations(ctx
, dirtyRect
, framePt
, textBaselinePt
,
4433 textPaintStyle
, provider
);
4437 nsTextFrame::DrawText(gfxContext
* aCtx
, const gfxPoint
& aTextBaselinePt
,
4438 PRUint32 aOffset
, PRUint32 aLength
,
4439 const gfxRect
* aDirtyRect
, PropertyProvider
* aProvider
,
4440 gfxFloat
& aAdvanceWidth
, PRBool aDrawSoftHyphen
)
4442 // Paint the text and soft-hyphen (if any) onto the given graphics context
4443 mTextRun
->Draw(aCtx
, aTextBaselinePt
, aOffset
, aLength
,
4444 aDirtyRect
, aProvider
, &aAdvanceWidth
);
4446 if (aDrawSoftHyphen
) {
4447 // Don't use ctx as the context, because we need a reference context here,
4448 // ctx may be transformed.
4449 gfxTextRunCache::AutoTextRun
hyphenTextRun(GetHyphenTextRun(mTextRun
, nsnull
, this));
4450 if (hyphenTextRun
.get()) {
4451 // For right-to-left text runs, the soft-hyphen is positioned at the left
4452 // of the text, minus its own width
4453 gfxFloat hyphenBaselineX
= aTextBaselinePt
.x
+ mTextRun
->GetDirection() * aAdvanceWidth
-
4454 (mTextRun
->IsRightToLeft() ? hyphenTextRun
->GetAdvanceWidth(0, hyphenTextRun
->GetLength(), nsnull
) : 0);
4455 hyphenTextRun
->Draw(aCtx
, gfxPoint(hyphenBaselineX
, aTextBaselinePt
.y
),
4456 0, hyphenTextRun
->GetLength(), aDirtyRect
, nsnull
, nsnull
);
4462 nsTextFrame::GetSelectionStatus(PRInt16
* aSelectionFlags
)
4464 // get the selection controller
4465 nsCOMPtr
<nsISelectionController
> selectionController
;
4466 nsresult rv
= GetSelectionController(PresContext(),
4467 getter_AddRefs(selectionController
));
4468 if (NS_FAILED(rv
) || !selectionController
)
4469 return nsISelectionController::SELECTION_OFF
;
4471 selectionController
->GetSelectionFlags(aSelectionFlags
);
4473 PRInt16 selectionValue
;
4474 selectionController
->GetDisplaySelection(&selectionValue
);
4476 return selectionValue
;
4480 nsTextFrame::IsVisibleInSelection(nsISelection
* aSelection
)
4482 // Check the quick way first
4483 PRBool isSelected
= (mState
& NS_FRAME_SELECTED_CONTENT
) == NS_FRAME_SELECTED_CONTENT
;
4487 SelectionDetails
* details
= GetSelectionDetails();
4488 PRBool found
= PR_FALSE
;
4490 // where are the selection points "really"
4491 SelectionDetails
*sdptr
= details
;
4493 if (sdptr
->mEnd
> GetContentOffset() &&
4494 sdptr
->mStart
< GetContentEnd() &&
4495 sdptr
->mType
== nsISelectionController::SELECTION_NORMAL
) {
4499 sdptr
= sdptr
->mNext
;
4501 DestroySelectionDetails(details
);
4507 * Compute the longest prefix of text whose width is <= aWidth. Return
4508 * the length of the prefix. Also returns the width of the prefix in aFitWidth.
4511 CountCharsFit(gfxTextRun
* aTextRun
, PRUint32 aStart
, PRUint32 aLength
,
4512 gfxFloat aWidth
, PropertyProvider
* aProvider
,
4513 gfxFloat
* aFitWidth
)
4518 for (i
= 1; i
<= aLength
; ++i
) {
4519 if (i
== aLength
|| aTextRun
->IsClusterStart(aStart
+ i
)) {
4520 gfxFloat nextWidth
= width
+
4521 aTextRun
->GetAdvanceWidth(aStart
+ last
, i
- last
, aProvider
);
4522 if (nextWidth
> aWidth
)
4532 nsIFrame::ContentOffsets
4533 nsTextFrame::CalcContentOffsetsFromFramePoint(nsPoint aPoint
) {
4534 ContentOffsets offsets
;
4536 gfxSkipCharsIterator iter
= EnsureTextRun();
4540 PropertyProvider
provider(this, iter
);
4541 // Trim leading but not trailing whitespace if possible
4542 provider
.InitializeForDisplay(PR_FALSE
);
4543 gfxFloat width
= mTextRun
->IsRightToLeft() ? mRect
.width
- aPoint
.x
: aPoint
.x
;
4545 PRUint32 skippedLength
= ComputeTransformedLength(provider
);
4547 PRUint32 charsFit
= CountCharsFit(mTextRun
,
4548 provider
.GetStart().GetSkippedOffset(), skippedLength
, width
, &provider
, &fitWidth
);
4550 PRInt32 selectedOffset
;
4551 if (charsFit
< skippedLength
) {
4552 // charsFit characters fitted, but no more could fit. See if we're
4553 // more than halfway through the cluster.. If we are, choose the next
4555 gfxSkipCharsIterator
extraCluster(provider
.GetStart());
4556 extraCluster
.AdvanceSkipped(charsFit
);
4557 gfxSkipCharsIterator
extraClusterLastChar(extraCluster
);
4558 FindClusterEnd(mTextRun
,
4559 provider
.GetStart().GetOriginalOffset() + provider
.GetOriginalLength(),
4560 &extraClusterLastChar
);
4561 gfxFloat charWidth
=
4562 mTextRun
->GetAdvanceWidth(extraCluster
.GetSkippedOffset(),
4563 GetSkippedDistance(extraCluster
, extraClusterLastChar
) + 1,
4565 selectedOffset
= width
<= fitWidth
+ charWidth
/2
4566 ? extraCluster
.GetOriginalOffset()
4567 : extraClusterLastChar
.GetOriginalOffset() + 1;
4569 // All characters fitted, we're at (or beyond) the end of the text.
4570 // XXX This could be some pathological situation where negative spacing
4571 // caused characters to move backwards. We can't really handle that
4572 // in the current frame system because frames can't have negative
4573 // intrinsic widths.
4575 provider
.GetStart().GetOriginalOffset() + provider
.GetOriginalLength();
4578 offsets
.content
= GetContent();
4579 offsets
.offset
= offsets
.secondaryOffset
= selectedOffset
;
4580 offsets
.associateWithNext
= mContentOffset
== offsets
.offset
;
4585 nsTextFrame::HasSelectionOverflowingDecorations(nsPresContext
* aPresContext
,
4589 nsILookAndFeel
* look
= aPresContext
->LookAndFeel();
4590 look
->GetMetric(nsILookAndFeel::eMetricFloat_IMEUnderlineRelativeSize
, ratio
);
4596 SelectionDetails
*details
= GetSelectionDetails();
4597 PRBool retval
= PR_FALSE
;
4598 for (SelectionDetails
*sd
= details
; sd
; sd
= sd
->mNext
) {
4599 if (sd
->mStart
!= sd
->mEnd
&&
4600 sd
->mType
& SelectionTypesWithDecorations
) {
4605 DestroySelectionDetails(details
);
4610 //null range means the whole thing
4612 nsTextFrame::SetSelected(nsPresContext
* aPresContext
,
4613 nsIDOMRange
*aRange
,
4617 DEBUG_VERIFY_NOT_DIRTY(mState
);
4618 #if 0 //XXXrbs disable due to bug 310318
4619 if (mState
& NS_FRAME_IS_DIRTY
)
4620 return NS_ERROR_UNEXPECTED
;
4623 if (aSelected
&& ParentDisablesSelection())
4626 // check whether style allows selection
4628 IsSelectable(&selectable
, nsnull
);
4630 return NS_OK
;//do not continue no selection for this frame.
4632 PRBool found
= PR_FALSE
;
4634 //lets see if the range contains us, if so we must redraw!
4635 nsCOMPtr
<nsIDOMNode
> endNode
;
4637 nsCOMPtr
<nsIDOMNode
> startNode
;
4638 PRInt32 startOffset
;
4639 aRange
->GetEndContainer(getter_AddRefs(endNode
));
4640 aRange
->GetEndOffset(&endOffset
);
4641 aRange
->GetStartContainer(getter_AddRefs(startNode
));
4642 aRange
->GetStartOffset(&startOffset
);
4643 nsCOMPtr
<nsIDOMNode
> thisNode
= do_QueryInterface(GetContent());
4645 if (thisNode
== startNode
)
4647 if (GetContentEnd() >= startOffset
)
4650 if (thisNode
== endNode
)
4652 if (endOffset
== startOffset
) //no need to redraw since drawing takes place with cursor
4655 if (mContentOffset
> endOffset
)
4660 else if (thisNode
== endNode
)
4662 if (mContentOffset
< endOffset
)
4675 // null range means the whole thing
4680 AddStateBits(NS_FRAME_SELECTED_CONTENT
);
4682 { //we need to see if any other selection is available.
4683 SelectionDetails
*details
= GetSelectionDetails();
4685 RemoveStateBits(NS_FRAME_SELECTED_CONTENT
);
4687 DestroySelectionDetails(details
);
4691 // If the selection state is changed in this content, we need to reflow
4692 // to recompute the overflow area for underline of spellchecking or IME if
4693 // their underline is thicker than normal decoration line.
4694 PRBool didHaveSelectionUnderline
=
4695 !!(mState
& TEXT_SELECTION_UNDERLINE_OVERFLOWED
);
4696 PRBool willHaveSelectionUnderline
=
4697 aSelected
&& HasSelectionOverflowingDecorations(PresContext());
4698 if (didHaveSelectionUnderline
!= willHaveSelectionUnderline
) {
4699 PresContext()->PresShell()->FrameNeedsReflow(this,
4700 nsIPresShell::eStyleChange
,
4703 // Selection might change anything. Invalidate the overflow area.
4704 Invalidate(GetOverflowRect(), PR_FALSE
);
4706 if (aSpread
== eSpreadDown
)
4708 nsIFrame
* frame
= GetPrevContinuation();
4710 frame
->SetSelected(aPresContext
, aRange
,aSelected
,eSpreadNone
);
4711 frame
= frame
->GetPrevContinuation();
4713 frame
= GetNextContinuation();
4715 frame
->SetSelected(aPresContext
, aRange
,aSelected
,eSpreadNone
);
4716 frame
= frame
->GetNextContinuation();
4723 nsTextFrame::GetPointFromOffset(PRInt32 inOffset
,
4727 return NS_ERROR_NULL_POINTER
;
4732 DEBUG_VERIFY_NOT_DIRTY(mState
);
4733 if (mState
& NS_FRAME_IS_DIRTY
)
4734 return NS_ERROR_UNEXPECTED
;
4736 if (GetContentLength() <= 0) {
4740 gfxSkipCharsIterator iter
= EnsureTextRun();
4742 return NS_ERROR_FAILURE
;
4744 PropertyProvider
properties(this, iter
);
4745 // Don't trim trailing whitespace, we want the caret to appear in the right
4746 // place if it's positioned there
4747 properties
.InitializeForDisplay(PR_FALSE
);
4749 if (inOffset
< GetContentOffset()){
4750 NS_WARNING("offset before this frame's content");
4751 inOffset
= GetContentOffset();
4752 } else if (inOffset
> GetContentEnd()) {
4753 NS_WARNING("offset after this frame's content");
4754 inOffset
= GetContentEnd();
4756 PRInt32 trimmedOffset
= properties
.GetStart().GetOriginalOffset();
4757 PRInt32 trimmedEnd
= trimmedOffset
+ properties
.GetOriginalLength();
4758 inOffset
= PR_MAX(inOffset
, trimmedOffset
);
4759 inOffset
= PR_MIN(inOffset
, trimmedEnd
);
4761 iter
.SetOriginalOffset(inOffset
);
4763 if (inOffset
< trimmedEnd
&&
4764 !iter
.IsOriginalCharSkipped() &&
4765 !mTextRun
->IsClusterStart(iter
.GetSkippedOffset())) {
4766 NS_WARNING("GetPointFromOffset called for non-cluster boundary");
4767 FindClusterStart(mTextRun
, &iter
);
4770 gfxFloat advanceWidth
=
4771 mTextRun
->GetAdvanceWidth(properties
.GetStart().GetSkippedOffset(),
4772 GetSkippedDistance(properties
.GetStart(), iter
),
4774 nscoord width
= NSToCoordCeil(advanceWidth
);
4776 if (mTextRun
->IsRightToLeft()) {
4777 outPoint
->x
= mRect
.width
- width
;
4779 outPoint
->x
= width
;
4787 nsTextFrame::GetChildFrameContainingOffset(PRInt32 aContentOffset
,
4789 PRInt32
* aOutOffset
,
4790 nsIFrame
**aOutFrame
)
4792 DEBUG_VERIFY_NOT_DIRTY(mState
);
4793 #if 0 //XXXrbs disable due to bug 310227
4794 if (mState
& NS_FRAME_IS_DIRTY
)
4795 return NS_ERROR_UNEXPECTED
;
4798 NS_ASSERTION(aOutOffset
&& aOutFrame
, "Bad out parameters");
4799 NS_ASSERTION(aContentOffset
>= 0, "Negative content offset, existing code was very broken!");
4801 nsTextFrame
* f
= this;
4802 if (aContentOffset
>= mContentOffset
) {
4804 nsTextFrame
* next
= static_cast<nsTextFrame
*>(f
->GetNextContinuation());
4805 if (!next
|| aContentOffset
< next
->GetContentOffset())
4807 if (aContentOffset
== next
->GetContentOffset()) {
4817 nsTextFrame
* prev
= static_cast<nsTextFrame
*>(f
->GetPrevContinuation());
4818 if (!prev
|| aContentOffset
> f
->GetContentOffset())
4820 if (aContentOffset
== f
->GetContentOffset()) {
4830 *aOutOffset
= aContentOffset
- f
->GetContentOffset();
4836 nsTextFrame::PeekOffsetNoAmount(PRBool aForward
, PRInt32
* aOffset
)
4838 NS_ASSERTION(aOffset
&& *aOffset
<= GetContentLength(), "aOffset out of range");
4840 gfxSkipCharsIterator iter
= EnsureTextRun();
4844 TrimmedOffsets trimmed
= GetTrimmedOffsets(mContent
->GetText(), PR_TRUE
);
4845 // Check whether there are nonskipped characters in the trimmmed range
4846 return iter
.ConvertOriginalToSkipped(trimmed
.GetEnd()) >
4847 iter
.ConvertOriginalToSkipped(trimmed
.mStart
);
4851 * This class iterates through the clusters before or after the given
4852 * aPosition (which is a content offset). You can test each cluster
4853 * to see if it's whitespace (as far as selection/caret movement is concerned),
4854 * or punctuation, or if there is a word break before the cluster. ("Before"
4855 * is interpreted according to aDirection, so if aDirection is -1, "before"
4856 * means actually *after* the cluster content.)
4858 class NS_STACK_CLASS ClusterIterator
{
4860 ClusterIterator(nsTextFrame
* aTextFrame
, PRInt32 aPosition
, PRInt32 aDirection
,
4861 nsString
& aContext
);
4863 PRBool
NextCluster();
4864 PRBool
IsWhitespace();
4865 PRBool
IsPunctuation();
4866 PRBool
HaveWordBreakBefore() { return mHaveWordBreak
; }
4867 PRInt32
GetAfterOffset();
4868 PRInt32
GetBeforeOffset();
4871 nsCOMPtr
<nsIUGenCategory
> mCategories
;
4872 gfxSkipCharsIterator mIterator
;
4873 const nsTextFragment
* mFrag
;
4874 nsTextFrame
* mTextFrame
;
4877 nsTextFrame::TrimmedOffsets mTrimmed
;
4878 nsTArray
<PRPackedBool
> mWordBreaks
;
4879 PRPackedBool mHaveWordBreak
;
4883 IsAcceptableCaretPosition(const gfxSkipCharsIterator
& aIter
, gfxTextRun
* aTextRun
,
4886 if (aIter
.IsOriginalCharSkipped())
4888 PRUint32 index
= aIter
.GetSkippedOffset();
4889 if (!aTextRun
->IsClusterStart(index
))
4891 return !(aFrame
->GetStyleText()->NewlineIsSignificant() &&
4892 aTextRun
->GetChar(index
) == '\n');
4896 nsTextFrame::PeekOffsetCharacter(PRBool aForward
, PRInt32
* aOffset
)
4898 PRInt32 contentLength
= GetContentLength();
4899 NS_ASSERTION(aOffset
&& *aOffset
<= contentLength
, "aOffset out of range");
4902 PRUint8 selectStyle
;
4903 IsSelectable(&selectable
, &selectStyle
);
4904 if (selectStyle
== NS_STYLE_USER_SELECT_ALL
)
4907 gfxSkipCharsIterator iter
= EnsureTextRun();
4911 TrimmedOffsets trimmed
= GetTrimmedOffsets(mContent
->GetText(), PR_FALSE
);
4913 // A negative offset means "end of frame".
4914 PRInt32 startOffset
= GetContentOffset() + (*aOffset
< 0 ? contentLength
: *aOffset
);
4918 for (i
= PR_MIN(trimmed
.GetEnd(), startOffset
) - 1;
4919 i
>= trimmed
.mStart
; --i
) {
4920 iter
.SetOriginalOffset(i
);
4921 if (IsAcceptableCaretPosition(iter
, mTextRun
, this)) {
4922 *aOffset
= i
- mContentOffset
;
4929 for (i
= startOffset
+ 1; i
<= trimmed
.GetEnd(); ++i
) {
4930 iter
.SetOriginalOffset(i
);
4931 // XXX we can't necessarily stop at the end of this frame,
4932 // but we really have no choice right now. We need to do a deeper
4933 // fix/restructuring of PeekOffsetCharacter
4934 if (i
== trimmed
.GetEnd() ||
4935 IsAcceptableCaretPosition(iter
, mTextRun
, this)) {
4936 *aOffset
= i
- mContentOffset
;
4940 *aOffset
= contentLength
;
4947 ClusterIterator::IsWhitespace()
4949 NS_ASSERTION(mCharIndex
>= 0, "No cluster selected");
4950 return IsSelectionSpace(mFrag
, mCharIndex
);
4954 ClusterIterator::IsPunctuation()
4956 NS_ASSERTION(mCharIndex
>= 0, "No cluster selected");
4959 nsIUGenCategory::nsUGenCategory c
= mCategories
->Get(mFrag
->CharAt(mCharIndex
));
4960 return c
== nsIUGenCategory::kPunctuation
|| c
== nsIUGenCategory::kSymbol
;
4964 ClusterIterator::GetBeforeOffset()
4966 NS_ASSERTION(mCharIndex
>= 0, "No cluster selected");
4967 return mCharIndex
+ (mDirection
> 0 ? 0 : 1);
4971 ClusterIterator::GetAfterOffset()
4973 NS_ASSERTION(mCharIndex
>= 0, "No cluster selected");
4974 return mCharIndex
+ (mDirection
> 0 ? 1 : 0);
4978 ClusterIterator::NextCluster()
4982 gfxTextRun
* textRun
= mTextFrame
->GetTextRun();
4984 mHaveWordBreak
= PR_FALSE
;
4986 PRBool keepGoing
= PR_FALSE
;
4987 if (mDirection
> 0) {
4988 if (mIterator
.GetOriginalOffset() >= mTrimmed
.GetEnd())
4990 keepGoing
= mIterator
.IsOriginalCharSkipped() ||
4991 mIterator
.GetOriginalOffset() < mTrimmed
.mStart
||
4992 !textRun
->IsClusterStart(mIterator
.GetSkippedOffset());
4993 mCharIndex
= mIterator
.GetOriginalOffset();
4994 mIterator
.AdvanceOriginal(1);
4996 if (mIterator
.GetOriginalOffset() <= mTrimmed
.mStart
)
4998 mIterator
.AdvanceOriginal(-1);
4999 keepGoing
= mIterator
.IsOriginalCharSkipped() ||
5000 mIterator
.GetOriginalOffset() >= mTrimmed
.GetEnd() ||
5001 !textRun
->IsClusterStart(mIterator
.GetSkippedOffset());
5002 mCharIndex
= mIterator
.GetOriginalOffset();
5005 if (mWordBreaks
[GetBeforeOffset() - mTextFrame
->GetContentOffset()]) {
5006 mHaveWordBreak
= PR_TRUE
;
5013 ClusterIterator::ClusterIterator(nsTextFrame
* aTextFrame
, PRInt32 aPosition
,
5014 PRInt32 aDirection
, nsString
& aContext
)
5015 : mTextFrame(aTextFrame
), mDirection(aDirection
), mCharIndex(-1)
5017 mIterator
= aTextFrame
->EnsureTextRun();
5018 if (!aTextFrame
->GetTextRun()) {
5019 mDirection
= 0; // signal failure
5022 mIterator
.SetOriginalOffset(aPosition
);
5024 mCategories
= do_GetService(NS_UNICHARCATEGORY_CONTRACTID
);
5026 mFrag
= aTextFrame
->GetContent()->GetText();
5027 mTrimmed
= aTextFrame
->GetTrimmedOffsets(mFrag
, PR_TRUE
);
5029 PRInt32 textOffset
= aTextFrame
->GetContentOffset();
5030 PRInt32 textLen
= aTextFrame
->GetContentLength();
5031 if (!mWordBreaks
.AppendElements(textLen
+ 1)) {
5032 mDirection
= 0; // signal failure
5035 memset(mWordBreaks
.Elements(), PR_FALSE
, textLen
+ 1);
5037 if (aDirection
> 0) {
5038 if (aContext
.IsEmpty()) {
5039 // No previous context, so it must be the start of a line or text run
5040 mWordBreaks
[0] = PR_TRUE
;
5042 textStart
= aContext
.Length();
5043 mFrag
->AppendTo(aContext
, textOffset
, textLen
);
5045 if (aContext
.IsEmpty()) {
5046 // No following context, so it must be the end of a line or text run
5047 mWordBreaks
[textLen
] = PR_TRUE
;
5051 mFrag
->AppendTo(str
, textOffset
, textLen
);
5052 aContext
.Insert(str
, 0);
5054 nsIWordBreaker
* wordBreaker
= nsContentUtils::WordBreaker();
5056 for (i
= 0; i
<= textLen
; ++i
) {
5057 PRInt32 indexInText
= i
+ textStart
;
5059 wordBreaker
->BreakInBetween(aContext
.get(), indexInText
,
5060 aContext
.get() + indexInText
,
5061 aContext
.Length() - indexInText
);
5066 nsTextFrame::PeekOffsetWord(PRBool aForward
, PRBool aWordSelectEatSpace
, PRBool aIsKeyboardSelect
,
5067 PRInt32
* aOffset
, PeekWordState
* aState
)
5069 PRInt32 contentLength
= GetContentLength();
5070 NS_ASSERTION (aOffset
&& *aOffset
<= contentLength
, "aOffset out of range");
5073 PRUint8 selectStyle
;
5074 IsSelectable(&selectable
, &selectStyle
);
5075 if (selectStyle
== NS_STYLE_USER_SELECT_ALL
)
5078 PRInt32 offset
= GetContentOffset() + (*aOffset
< 0 ? contentLength
: *aOffset
);
5079 ClusterIterator
cIter(this, offset
, aForward
? 1 : -1, aState
->mContext
);
5081 if (!cIter
.NextCluster())
5085 PRBool isPunctuation
= cIter
.IsPunctuation();
5086 PRBool isWhitespace
= cIter
.IsWhitespace();
5087 PRBool isWordBreakBefore
= cIter
.HaveWordBreakBefore();
5088 if (aWordSelectEatSpace
== isWhitespace
&& !aState
->mSawBeforeType
) {
5089 aState
->SetSawBeforeType();
5090 aState
->Update(isPunctuation
, isWhitespace
);
5093 // See if we can break before the current cluster
5094 if (!aState
->mAtStart
) {
5096 if (isPunctuation
!= aState
->mLastCharWasPunctuation
) {
5097 canBreak
= BreakWordBetweenPunctuation(aState
, aForward
,
5098 isPunctuation
, isWhitespace
, aIsKeyboardSelect
);
5099 } else if (!aState
->mLastCharWasWhitespace
&&
5100 !isWhitespace
&& !isPunctuation
&& isWordBreakBefore
) {
5101 // if both the previous and the current character are not white
5102 // space but this can be word break before, we don't need to eat
5103 // a white space in this case. This case happens in some languages
5104 // that their words are not separated by white spaces. E.g.,
5105 // Japanese and Chinese.
5108 canBreak
= isWordBreakBefore
&& aState
->mSawBeforeType
;
5111 *aOffset
= cIter
.GetBeforeOffset() - mContentOffset
;
5115 aState
->Update(isPunctuation
, isWhitespace
);
5116 } while (cIter
.NextCluster());
5118 *aOffset
= cIter
.GetAfterOffset() - mContentOffset
;
5122 // TODO this needs to be deCOMtaminated with the interface fixed in
5123 // nsIFrame.h, but we won't do that until the old textframe is gone.
5125 nsTextFrame::CheckVisibility(nsPresContext
* aContext
, PRInt32 aStartIndex
,
5126 PRInt32 aEndIndex
, PRBool aRecurse
, PRBool
*aFinished
, PRBool
*aRetval
)
5129 return NS_ERROR_NULL_POINTER
;
5131 // Text in the range is visible if there is at least one character in the range
5132 // that is not skipped and is mapped by this frame (which is the primary frame)
5133 // or one of its continuations.
5134 for (nsTextFrame
* f
= this; f
;
5135 f
= static_cast<nsTextFrame
*>(GetNextContinuation())) {
5136 if (f
->PeekOffsetNoAmount(PR_TRUE
, nsnull
)) {
5142 *aRetval
= PR_FALSE
;
5147 nsTextFrame::GetOffsets(PRInt32
&start
, PRInt32
&end
) const
5149 start
= GetContentOffset();
5150 end
= GetContentEnd();
5155 FindEndOfPunctuationRun(const nsTextFragment
* aFrag
,
5156 gfxTextRun
* aTextRun
,
5157 gfxSkipCharsIterator
* aIter
,
5164 for (i
= aStart
; i
< aEnd
- aOffset
; ++i
) {
5165 if (nsContentUtils::IsPunctuationMarkAt(aFrag
, aOffset
+ i
)) {
5166 aIter
->SetOriginalOffset(aOffset
+ i
);
5167 FindClusterEnd(aTextRun
, aEnd
, aIter
);
5168 i
= aIter
->GetOriginalOffset() - aOffset
;
5177 * Returns PR_TRUE if this text frame completes the first-letter, PR_FALSE
5178 * if it does not contain a true "letter".
5179 * If returns PR_TRUE, then it also updates aLength to cover just the first-letter
5182 * XXX :first-letter should be handled during frame construction
5183 * (and it has a good bit in common with nextBidi)
5185 * @param aLength an in/out parameter: on entry contains the maximum length to
5186 * return, on exit returns length of the first-letter fragment (which may
5187 * include leading and trailing punctuation, for example)
5190 FindFirstLetterRange(const nsTextFragment
* aFrag
,
5191 gfxTextRun
* aTextRun
,
5192 PRInt32 aOffset
, const gfxSkipCharsIterator
& aIter
,
5196 PRInt32 length
= *aLength
;
5197 PRInt32 endOffset
= aOffset
+ length
;
5198 gfxSkipCharsIterator
iter(aIter
);
5200 // skip leading whitespace, then consume clusters that start with punctuation
5201 i
= FindEndOfPunctuationRun(aFrag
, aTextRun
, &iter
, aOffset
,
5202 GetTrimmableWhitespaceCount(aFrag
, aOffset
, length
, 1),
5207 // If the next character is not a letter or number, there is no first-letter.
5208 // Return PR_TRUE so that we don't go on looking, but set aLength to 0.
5209 if (!nsContentUtils::IsAlphanumericAt(aFrag
, aOffset
+ i
)) {
5214 // consume another cluster (the actual first letter)
5215 iter
.SetOriginalOffset(aOffset
+ i
);
5216 FindClusterEnd(aTextRun
, endOffset
, &iter
);
5217 i
= iter
.GetOriginalOffset() - aOffset
;
5218 if (i
+ 1 == length
)
5221 // consume clusters that start with punctuation
5222 i
= FindEndOfPunctuationRun(aFrag
, aTextRun
, &iter
, aOffset
, i
+ 1, endOffset
);
5229 FindStartAfterSkippingWhitespace(PropertyProvider
* aProvider
,
5230 nsIFrame::InlineIntrinsicWidthData
* aData
,
5231 const nsStyleText
* aTextStyle
,
5232 gfxSkipCharsIterator
* aIterator
,
5233 PRUint32 aFlowEndInTextRun
)
5235 if (aData
->skipWhitespace
) {
5236 while (aIterator
->GetSkippedOffset() < aFlowEndInTextRun
&&
5237 IsTrimmableSpace(aProvider
->GetFragment(), aIterator
->GetOriginalOffset(), aTextStyle
)) {
5238 aIterator
->AdvanceOriginal(1);
5241 return aIterator
->GetSkippedOffset();
5245 void nsTextFrame::MarkIntrinsicWidthsDirty()
5248 nsFrame::MarkIntrinsicWidthsDirty();
5251 // XXX this doesn't handle characters shaped by line endings. We need to
5252 // temporarily override the "current line ending" settings.
5254 nsTextFrame::AddInlineMinWidthForFlow(nsIRenderingContext
*aRenderingContext
,
5255 nsIFrame::InlineMinWidthData
*aData
)
5257 PRUint32 flowEndInTextRun
;
5258 gfxContext
* ctx
= aRenderingContext
->ThebesContext();
5259 gfxSkipCharsIterator iter
=
5260 EnsureTextRun(ctx
, nsnull
, aData
->line
, &flowEndInTextRun
);
5264 // Pass null for the line container. This will disable tab spacing, but that's
5265 // OK since we can't really handle tabs for intrinsic sizing anyway.
5266 const nsStyleText
* textStyle
= GetStyleText();
5267 const nsTextFragment
* frag
= mContent
->GetText();
5268 PropertyProvider
provider(mTextRun
, textStyle
, frag
, this,
5269 iter
, PR_INT32_MAX
, nsnull
, 0);
5271 PRBool collapseWhitespace
= !textStyle
->WhiteSpaceIsSignificant();
5272 PRBool preformatNewlines
= textStyle
->NewlineIsSignificant();
5273 PRBool preformatTabs
= textStyle
->WhiteSpaceIsSignificant();
5274 gfxFloat tabWidth
= -1;
5276 FindStartAfterSkippingWhitespace(&provider
, aData
, textStyle
, &iter
, flowEndInTextRun
);
5277 if (start
>= flowEndInTextRun
)
5280 // XXX Should we consider hyphenation here?
5281 for (PRUint32 i
= start
, wordStart
= start
; i
<= flowEndInTextRun
; ++i
) {
5282 PRBool preformattedNewline
= PR_FALSE
;
5283 PRBool preformattedTab
= PR_FALSE
;
5284 if (i
< flowEndInTextRun
) {
5285 // XXXldb Shouldn't we be including the newline as part of the
5286 // segment that it ends rather than part of the segment that it
5288 preformattedNewline
= preformatNewlines
&& mTextRun
->GetChar(i
) == '\n';
5289 preformattedTab
= preformatTabs
&& mTextRun
->GetChar(i
) == '\t';
5290 if (!mTextRun
->CanBreakLineBefore(i
) && !preformattedNewline
&&
5292 // we can't break here (and it's not the end of the flow)
5297 if (i
> wordStart
) {
5299 NSToCoordCeil(mTextRun
->GetAdvanceWidth(wordStart
, i
- wordStart
, &provider
));
5300 aData
->currentLine
+= width
;
5301 aData
->atStartOfLine
= PR_FALSE
;
5303 if (collapseWhitespace
) {
5304 PRUint32 trimStart
= GetEndOfTrimmedText(frag
, textStyle
, wordStart
, i
, &iter
);
5305 if (trimStart
== start
) {
5306 // This is *all* trimmable whitespace, so whatever trailingWhitespace
5307 // we saw previously is still trailing...
5308 aData
->trailingWhitespace
+= width
;
5310 // Some non-whitespace so the old trailingWhitespace is no longer trailing
5311 aData
->trailingWhitespace
=
5312 NSToCoordCeil(mTextRun
->GetAdvanceWidth(trimStart
, i
- trimStart
, &provider
));
5315 aData
->trailingWhitespace
= 0;
5319 if (preformattedTab
) {
5320 PropertyProvider::Spacing spacing
;
5321 provider
.GetSpacing(i
, 1, &spacing
);
5322 aData
->currentLine
+= nscoord(spacing
.mBefore
);
5324 AdvanceToNextTab(aData
->currentLine
, FindLineContainer(this),
5325 mTextRun
, &tabWidth
);
5326 aData
->currentLine
= nscoord(afterTab
+ spacing
.mAfter
);
5328 } else if (i
< flowEndInTextRun
||
5329 (i
== mTextRun
->GetLength() &&
5330 (mTextRun
->GetFlags() & nsTextFrameUtils::TEXT_HAS_TRAILING_BREAK
))) {
5331 if (preformattedNewline
) {
5332 aData
->ForceBreak(aRenderingContext
);
5334 aData
->OptionallyBreak(aRenderingContext
);
5340 // Check if we have collapsible whitespace at the end
5341 aData
->skipWhitespace
=
5342 IsTrimmableSpace(provider
.GetFragment(),
5343 iter
.ConvertSkippedToOriginal(flowEndInTextRun
- 1),
5347 // XXX Need to do something here to avoid incremental reflow bugs due to
5348 // first-line and first-letter changing min-width
5350 nsTextFrame::AddInlineMinWidth(nsIRenderingContext
*aRenderingContext
,
5351 nsIFrame::InlineMinWidthData
*aData
)
5354 gfxTextRun
* lastTextRun
= nsnull
;
5355 // nsContinuingTextFrame does nothing for AddInlineMinWidth; all text frames
5356 // in the flow are handled right here.
5357 for (f
= this; f
; f
= static_cast<nsTextFrame
*>(f
->GetNextContinuation())) {
5358 // f->mTextRun could be null if we haven't set up textruns yet for f.
5359 // Except in OOM situations, lastTextRun will only be null for the first
5361 if (f
== this || f
->mTextRun
!= lastTextRun
) {
5362 // This will process all the text frames that share the same textrun as f.
5363 f
->AddInlineMinWidthForFlow(aRenderingContext
, aData
);
5364 lastTextRun
= f
->mTextRun
;
5369 // XXX this doesn't handle characters shaped by line endings. We need to
5370 // temporarily override the "current line ending" settings.
5372 nsTextFrame::AddInlinePrefWidthForFlow(nsIRenderingContext
*aRenderingContext
,
5373 nsIFrame::InlinePrefWidthData
*aData
)
5375 PRUint32 flowEndInTextRun
;
5376 gfxContext
* ctx
= aRenderingContext
->ThebesContext();
5377 gfxSkipCharsIterator iter
=
5378 EnsureTextRun(ctx
, nsnull
, aData
->line
, &flowEndInTextRun
);
5382 // Pass null for the line container. This will disable tab spacing, but that's
5383 // OK since we can't really handle tabs for intrinsic sizing anyway.
5385 const nsStyleText
* textStyle
= GetStyleText();
5386 const nsTextFragment
* frag
= mContent
->GetText();
5387 PropertyProvider
provider(mTextRun
, textStyle
, frag
, this,
5388 iter
, PR_INT32_MAX
, nsnull
, 0);
5390 PRBool collapseWhitespace
= !textStyle
->WhiteSpaceIsSignificant();
5391 PRBool preformatNewlines
= textStyle
->NewlineIsSignificant();
5392 PRBool preformatTabs
= textStyle
->WhiteSpaceIsSignificant();
5393 gfxFloat tabWidth
= -1;
5395 FindStartAfterSkippingWhitespace(&provider
, aData
, textStyle
, &iter
, flowEndInTextRun
);
5396 if (start
>= flowEndInTextRun
)
5399 // XXX Should we consider hyphenation here?
5400 // If newlines and tabs aren't preformatted, nothing to do inside
5401 // the loop so make i skip to the end
5402 PRUint32 loopStart
= (preformatNewlines
|| preformatTabs
) ? start
: flowEndInTextRun
;
5403 for (PRUint32 i
= loopStart
, lineStart
= start
; i
<= flowEndInTextRun
; ++i
) {
5404 PRBool preformattedNewline
= PR_FALSE
;
5405 PRBool preformattedTab
= PR_FALSE
;
5406 if (i
< flowEndInTextRun
) {
5407 // XXXldb Shouldn't we be including the newline as part of the
5408 // segment that it ends rather than part of the segment that it
5410 NS_ASSERTION(preformatNewlines
, "We can't be here unless newlines are hard breaks");
5411 preformattedNewline
= preformatNewlines
&& mTextRun
->GetChar(i
) == '\n';
5412 preformattedTab
= preformatTabs
&& mTextRun
->GetChar(i
) == '\t';
5413 if (!preformattedNewline
&& !preformattedTab
) {
5414 // we needn't break here (and it's not the end of the flow)
5419 if (i
> lineStart
) {
5421 NSToCoordCeil(mTextRun
->GetAdvanceWidth(lineStart
, i
- lineStart
, &provider
));
5422 aData
->currentLine
= NSCoordSaturatingAdd(aData
->currentLine
, width
);
5424 if (collapseWhitespace
) {
5425 PRUint32 trimStart
= GetEndOfTrimmedText(frag
, textStyle
, lineStart
, i
, &iter
);
5426 if (trimStart
== start
) {
5427 // This is *all* trimmable whitespace, so whatever trailingWhitespace
5428 // we saw previously is still trailing...
5429 aData
->trailingWhitespace
+= width
;
5431 // Some non-whitespace so the old trailingWhitespace is no longer trailing
5432 aData
->trailingWhitespace
=
5433 NSToCoordCeil(mTextRun
->GetAdvanceWidth(trimStart
, i
- trimStart
, &provider
));
5436 aData
->trailingWhitespace
= 0;
5440 if (preformattedTab
) {
5441 PropertyProvider::Spacing spacing
;
5442 provider
.GetSpacing(i
, 1, &spacing
);
5443 aData
->currentLine
+= nscoord(spacing
.mBefore
);
5445 AdvanceToNextTab(aData
->currentLine
, FindLineContainer(this),
5446 mTextRun
, &tabWidth
);
5447 aData
->currentLine
= nscoord(afterTab
+ spacing
.mAfter
);
5449 } else if (preformattedNewline
) {
5450 aData
->ForceBreak(aRenderingContext
);
5455 // Check if we have collapsible whitespace at the end
5456 aData
->skipWhitespace
=
5457 IsTrimmableSpace(provider
.GetFragment(),
5458 iter
.ConvertSkippedToOriginal(flowEndInTextRun
- 1),
5462 // XXX Need to do something here to avoid incremental reflow bugs due to
5463 // first-line and first-letter changing pref-width
5465 nsTextFrame::AddInlinePrefWidth(nsIRenderingContext
*aRenderingContext
,
5466 nsIFrame::InlinePrefWidthData
*aData
)
5469 gfxTextRun
* lastTextRun
= nsnull
;
5470 // nsContinuingTextFrame does nothing for AddInlineMinWidth; all text frames
5471 // in the flow are handled right here.
5472 for (f
= this; f
; f
= static_cast<nsTextFrame
*>(f
->GetNextContinuation())) {
5473 // f->mTextRun could be null if we haven't set up textruns yet for f.
5474 // Except in OOM situations, lastTextRun will only be null for the first
5476 if (f
== this || f
->mTextRun
!= lastTextRun
) {
5477 // This will process all the text frames that share the same textrun as f.
5478 f
->AddInlinePrefWidthForFlow(aRenderingContext
, aData
);
5479 lastTextRun
= f
->mTextRun
;
5484 /* virtual */ nsSize
5485 nsTextFrame::ComputeSize(nsIRenderingContext
*aRenderingContext
,
5486 nsSize aCBSize
, nscoord aAvailableWidth
,
5487 nsSize aMargin
, nsSize aBorder
, nsSize aPadding
,
5490 // Inlines and text don't compute size before reflow.
5491 return nsSize(NS_UNCONSTRAINEDSIZE
, NS_UNCONSTRAINEDSIZE
);
5495 RoundOut(const gfxRect
& aRect
)
5498 r
.x
= NSToCoordFloor(aRect
.X());
5499 r
.y
= NSToCoordFloor(aRect
.Y());
5500 r
.width
= NSToCoordCeil(aRect
.XMost()) - r
.x
;
5501 r
.height
= NSToCoordCeil(aRect
.YMost()) - r
.y
;
5506 nsTextFrame::ComputeTightBounds(gfxContext
* aContext
) const
5508 if ((GetStyleContext()->HasTextDecorations() &&
5509 eCompatibility_NavQuirks
== PresContext()->CompatibilityMode()) ||
5510 (GetStateBits() & TEXT_HYPHEN_BREAK
)) {
5511 // This is conservative, but OK.
5512 return GetOverflowRect();
5515 gfxSkipCharsIterator iter
= const_cast<nsTextFrame
*>(this)->EnsureTextRun();
5517 return nsRect(0, 0, 0, 0);
5519 PropertyProvider
provider(const_cast<nsTextFrame
*>(this), iter
);
5520 // Trim trailing whitespace
5521 provider
.InitializeForDisplay(PR_TRUE
);
5523 gfxTextRun::Metrics metrics
=
5524 mTextRun
->MeasureText(provider
.GetStart().GetSkippedOffset(),
5525 ComputeTransformedLength(provider
), PR_TRUE
,
5526 aContext
, &provider
);
5527 // mAscent should be the same as metrics.mAscent, but it's what we use to
5528 // paint so that's the one we'll use.
5529 return RoundOut(metrics
.mBoundingBox
) + nsPoint(0, mAscent
);
5533 AddCharToMetrics(gfxTextRun
* aCharTextRun
, gfxTextRun
* aBaseTextRun
,
5534 gfxTextRun::Metrics
* aMetrics
, PRBool aTightBoundingBox
,
5535 gfxContext
* aContext
)
5537 gfxTextRun::Metrics charMetrics
=
5538 aCharTextRun
->MeasureText(0, aCharTextRun
->GetLength(), aTightBoundingBox
, aContext
, nsnull
);
5540 aMetrics
->CombineWith(charMetrics
, aBaseTextRun
->IsRightToLeft());
5544 HasSoftHyphenBefore(const nsTextFragment
* aFrag
, gfxTextRun
* aTextRun
,
5545 PRInt32 aStartOffset
, const gfxSkipCharsIterator
& aIter
)
5547 if (!(aTextRun
->GetFlags() & nsTextFrameUtils::TEXT_HAS_SHY
))
5549 gfxSkipCharsIterator iter
= aIter
;
5550 while (iter
.GetOriginalOffset() > aStartOffset
) {
5551 iter
.AdvanceOriginal(-1);
5552 if (!iter
.IsOriginalCharSkipped())
5554 if (aFrag
->CharAt(iter
.GetOriginalOffset()) == CH_SHY
)
5561 nsTextFrame::SetLength(PRInt32 aLength
)
5563 mContentLengthHint
= aLength
;
5564 PRInt32 end
= GetContentOffset() + aLength
;
5565 nsTextFrame
* f
= static_cast<nsTextFrame
*>(GetNextInFlow());
5568 if (end
< f
->mContentOffset
) {
5569 // Our frame is shrinking. Give the text to our next in flow.
5570 f
->mContentOffset
= end
;
5571 if (f
->GetTextRun() != mTextRun
) {
5577 while (f
&& f
->mContentOffset
< end
) {
5578 // Our frame is growing. Take text from our in-flow.
5579 f
->mContentOffset
= end
;
5580 if (f
->GetTextRun() != mTextRun
) {
5584 f
= static_cast<nsTextFrame
*>(f
->GetNextInFlow());
5587 f
= static_cast<nsTextFrame
*>(this->GetFirstContinuation());
5589 f
->GetContentLength(); // Assert if negative length
5590 f
= static_cast<nsTextFrame
*>(f
->GetNextContinuation());
5596 nsTextFrame::IsFloatingFirstLetterChild()
5598 if (!GetStateBits() & TEXT_FIRST_LETTER
)
5600 nsIFrame
* frame
= GetParent();
5601 if (!frame
|| frame
->GetType() != nsGkAtoms::letterFrame
)
5603 return frame
->GetStyleDisplay()->IsFloating();
5607 nsTextFrame::Reflow(nsPresContext
* aPresContext
,
5608 nsHTMLReflowMetrics
& aMetrics
,
5609 const nsHTMLReflowState
& aReflowState
,
5610 nsReflowStatus
& aStatus
)
5612 DO_GLOBAL_REFLOW_COUNT("nsTextFrame");
5613 DISPLAY_REFLOW(aPresContext
, this, aReflowState
, aMetrics
, aStatus
);
5616 printf(": BeginReflow: availableSize=%d,%d\n",
5617 aReflowState
.availableWidth
, aReflowState
.availableHeight
);
5620 /////////////////////////////////////////////////////////////////////
5621 // Set up flags and clear out state
5622 /////////////////////////////////////////////////////////////////////
5624 // Clear out the reflow state flags in mState (without destroying
5625 // the TEXT_BLINK_ON bit). We also clear the whitespace flags because this
5626 // can change whether the frame maps whitespace-only text or not.
5627 RemoveStateBits(TEXT_REFLOW_FLAGS
| TEXT_WHITESPACE_FLAGS
);
5629 // Temporarily map all possible content while we construct our new textrun.
5630 // so that when doing reflow our styles prevail over any part of the
5631 // textrun we look at. Note that next-in-flows may be mapping the same
5632 // content; gfxTextRun construction logic will ensure that we take priority.
5633 PRInt32 maxContentLength
= GetInFlowContentLength();
5635 // XXX If there's no line layout, we shouldn't even have created this
5636 // frame. This may happen if, for example, this is text inside a table
5637 // but not inside a cell. For now, just don't reflow. We also don't need to
5638 // reflow if there is no content.
5639 if (!aReflowState
.mLineLayout
|| !maxContentLength
) {
5640 ClearMetrics(aMetrics
);
5641 aStatus
= NS_FRAME_COMPLETE
;
5645 nsLineLayout
& lineLayout
= *aReflowState
.mLineLayout
;
5647 if (aReflowState
.mFlags
.mBlinks
) {
5648 if (0 == (mState
& TEXT_BLINK_ON
)) {
5649 mState
|= TEXT_BLINK_ON
;
5650 nsBlinkTimer::AddBlinkFrame(aPresContext
, this);
5654 if (0 != (mState
& TEXT_BLINK_ON
)) {
5655 mState
&= ~TEXT_BLINK_ON
;
5656 nsBlinkTimer::RemoveBlinkFrame(this);
5660 const nsStyleText
* textStyle
= GetStyleText();
5662 PRBool atStartOfLine
= lineLayout
.LineIsEmpty();
5663 if (atStartOfLine
) {
5664 AddStateBits(TEXT_START_OF_LINE
);
5667 PRUint32 flowEndInTextRun
;
5668 nsIFrame
* lineContainer
= lineLayout
.GetLineContainerFrame();
5669 gfxContext
* ctx
= aReflowState
.rendContext
->ThebesContext();
5670 const nsTextFragment
* frag
= mContent
->GetText();
5672 // DOM offsets of the text range we need to measure, after trimming
5673 // whitespace, restricting to first-letter, and restricting preformatted text
5674 // to nearest newline
5675 PRInt32 length
= maxContentLength
;
5676 PRInt32 offset
= GetContentOffset();
5678 // Restrict preformatted text to the nearest newline
5679 PRInt32 newLineOffset
= -1; // this will be -1 or a content offset
5680 if (textStyle
->NewlineIsSignificant()) {
5681 newLineOffset
= FindChar(frag
, offset
, length
, '\n');
5682 if (newLineOffset
>= 0) {
5683 length
= newLineOffset
+ 1 - offset
;
5686 if (atStartOfLine
&& !textStyle
->WhiteSpaceIsSignificant()) {
5687 // Skip leading whitespace. Make sure we don't skip a 'pre-line'
5688 // newline if there is one.
5689 PRInt32 skipLength
= newLineOffset
>= 0 ? length
- 1 : length
;
5690 PRInt32 whitespaceCount
=
5691 GetTrimmableWhitespaceCount(frag
, offset
, skipLength
, 1);
5692 offset
+= whitespaceCount
;
5693 length
-= whitespaceCount
;
5696 PRBool completedFirstLetter
= PR_FALSE
;
5697 // Layout dependent styles are a problem because we need to reconstruct
5698 // the gfxTextRun based on our layout.
5699 if (lineLayout
.GetFirstLetterStyleOK() || lineLayout
.GetInFirstLine()) {
5700 SetLength(maxContentLength
);
5702 if (lineLayout
.GetFirstLetterStyleOK()) {
5703 // floating first-letter boundaries are significant in textrun
5704 // construction, so clear the textrun out every time we hit a first-letter
5705 // and have changed our length (which controls the first-letter boundary)
5707 // Find the length of the first-letter. We need a textrun for this.
5708 gfxSkipCharsIterator iter
=
5709 EnsureTextRun(ctx
, lineContainer
, lineLayout
.GetLine(), &flowEndInTextRun
);
5712 PRInt32 firstLetterLength
= length
;
5713 completedFirstLetter
=
5714 FindFirstLetterRange(frag
, mTextRun
, offset
, iter
, &firstLetterLength
);
5715 if (newLineOffset
>= 0) {
5716 // Don't allow a preformatted newline to be part of a first-letter.
5717 firstLetterLength
= PR_MIN(firstLetterLength
, length
- 1);
5719 // There is no text to be consumed by the first-letter before the
5720 // preformatted newline. Note that the first letter is therefore
5721 // complete (FindFirstLetterRange will have returned false).
5722 completedFirstLetter
= PR_TRUE
;
5725 length
= firstLetterLength
;
5727 AddStateBits(TEXT_FIRST_LETTER
);
5729 // Change this frame's length to the first-letter length right now
5730 // so that when we rebuild the textrun it will be built with the
5731 // right first-letter boundary
5732 SetLength(offset
+ length
- GetContentOffset());
5733 // Ensure that the textrun will be rebuilt
5739 gfxSkipCharsIterator iter
=
5740 EnsureTextRun(ctx
, lineContainer
, lineLayout
.GetLine(), &flowEndInTextRun
);
5742 PRInt32 skippedRunLength
;
5743 if (mTextRun
&& mTextRun
->GetLength() == iter
.GetSkippedOffset() &&
5745 (!iter
.IsOriginalCharSkipped(&skippedRunLength
) || skippedRunLength
< length
)) {
5746 // The textrun does not map enough text for this frame. This can happen
5747 // when the textrun was ended in the middle of a text node because a
5748 // preformatted newline was encountered, and prev-in-flow frames have
5749 // consumed all the text of the textrun. We need a new textrun.
5751 iter
= EnsureTextRun(ctx
, lineContainer
,
5752 lineLayout
.GetLine(), &flowEndInTextRun
);
5756 ClearMetrics(aMetrics
);
5757 aStatus
= NS_FRAME_COMPLETE
;
5761 NS_ASSERTION(gfxSkipCharsIterator(iter
).ConvertOriginalToSkipped(offset
+ length
)
5762 <= mTextRun
->GetLength(),
5763 "Text run does not map enough text for our reflow");
5765 /////////////////////////////////////////////////////////////////////
5766 // See how much text should belong to this text frame, and measure it
5767 /////////////////////////////////////////////////////////////////////
5769 iter
.SetOriginalOffset(offset
);
5770 nscoord xOffsetForTabs
= (mTextRun
->GetFlags() & nsTextFrameUtils::TEXT_HAS_TAB
) ?
5771 (lineLayout
.GetCurrentFrameXDistanceFromBlock() -
5772 lineContainer
->GetUsedBorderAndPadding().left
)
5774 PropertyProvider
provider(mTextRun
, textStyle
, frag
, this, iter
, length
,
5775 lineContainer
, xOffsetForTabs
);
5777 PRUint32 transformedOffset
= provider
.GetStart().GetSkippedOffset();
5779 // The metrics for the text go in here
5780 gfxTextRun::Metrics textMetrics
;
5781 PRBool needTightBoundingBox
= IsFloatingFirstLetterChild();
5783 NS_ASSERTION(!(NS_REFLOW_CALC_BOUNDING_METRICS
& aMetrics
.mFlags
),
5784 "We shouldn't be passed NS_REFLOW_CALC_BOUNDING_METRICS anymore");
5787 PRInt32 limitLength
= length
;
5788 PRInt32 forceBreak
= lineLayout
.GetForcedBreakPosition(mContent
);
5789 PRBool forceBreakAfter
= PR_FALSE
;
5790 if (forceBreak
>= offset
+ length
) {
5791 forceBreakAfter
= forceBreak
== offset
+ length
;
5792 // The break is not within the text considered for this textframe.
5795 if (forceBreak
>= 0) {
5796 limitLength
= forceBreak
- offset
;
5797 NS_ASSERTION(limitLength
>= 0, "Weird break found!");
5799 // This is the heart of text reflow right here! We don't know where
5800 // to break, so we need to see how much text fits in the available width.
5801 PRUint32 transformedLength
;
5802 if (offset
+ limitLength
>= PRInt32(frag
->GetLength())) {
5803 NS_ASSERTION(offset
+ limitLength
== PRInt32(frag
->GetLength()),
5804 "Content offset/length out of bounds");
5805 NS_ASSERTION(flowEndInTextRun
>= transformedOffset
,
5806 "Negative flow length?");
5807 transformedLength
= flowEndInTextRun
- transformedOffset
;
5809 // we're not looking at all the content, so we need to compute the
5810 // length of the transformed substring we're looking at
5811 gfxSkipCharsIterator
iter(provider
.GetStart());
5812 iter
.SetOriginalOffset(offset
+ limitLength
);
5813 transformedLength
= iter
.GetSkippedOffset() - transformedOffset
;
5815 PRUint32 transformedLastBreak
= 0;
5816 PRBool usedHyphenation
;
5817 gfxFloat trimmedWidth
= 0;
5818 gfxFloat availWidth
= aReflowState
.availableWidth
;
5819 PRBool canTrimTrailingWhitespace
= !textStyle
->WhiteSpaceIsSignificant();
5820 PRInt32 unusedOffset
;
5821 gfxBreakPriority breakPriority
;
5822 lineLayout
.GetLastOptionalBreakPosition(&unusedOffset
, &breakPriority
);
5823 PRUint32 transformedCharsFit
=
5824 mTextRun
->BreakAndMeasureText(transformedOffset
, transformedLength
,
5825 (GetStateBits() & TEXT_START_OF_LINE
) != 0,
5827 &provider
, !lineLayout
.LineIsBreakable(),
5828 canTrimTrailingWhitespace
? &trimmedWidth
: nsnull
,
5829 &textMetrics
, needTightBoundingBox
, ctx
,
5830 &usedHyphenation
, &transformedLastBreak
,
5831 textStyle
->WordCanWrap(), &breakPriority
);
5832 // The "end" iterator points to the first character after the string mapped
5833 // by this frame. Basically, its original-string offset is offset+charsFit
5834 // after we've computed charsFit.
5835 gfxSkipCharsIterator
end(provider
.GetEndHint());
5836 end
.SetSkippedOffset(transformedOffset
+ transformedCharsFit
);
5837 PRInt32 charsFit
= end
.GetOriginalOffset() - offset
;
5838 if (offset
+ charsFit
== newLineOffset
) {
5839 // We broke before a trailing preformatted '\n'. The newline should
5840 // be assigned to this frame. Note that newLineOffset will be -1 if
5841 // there was no preformatted newline, so we wouldn't get here in that
5845 // That might have taken us beyond our assigned content range (because
5846 // we might have advanced over some skipped chars that extend outside
5847 // this frame), so get back in.
5848 PRInt32 lastBreak
= -1;
5849 if (charsFit
>= limitLength
) {
5850 charsFit
= limitLength
;
5851 if (transformedLastBreak
!= PR_UINT32_MAX
) {
5852 // lastBreak is needed.
5853 // This may set lastBreak greater than 'length', but that's OK
5854 lastBreak
= end
.ConvertSkippedToOriginal(transformedOffset
+ transformedLastBreak
);
5856 end
.SetOriginalOffset(offset
+ charsFit
);
5857 // If we were forced to fit, and the break position is after a soft hyphen,
5858 // note that this is a hyphenation break.
5859 if ((forceBreak
>= 0 || forceBreakAfter
) &&
5860 HasSoftHyphenBefore(frag
, mTextRun
, offset
, end
)) {
5861 usedHyphenation
= PR_TRUE
;
5864 if (usedHyphenation
) {
5865 // Fix up metrics to include hyphen
5866 AddHyphenToMetrics(this, mTextRun
, &textMetrics
, needTightBoundingBox
, ctx
);
5867 AddStateBits(TEXT_HYPHEN_BREAK
| TEXT_HAS_NONCOLLAPSED_CHARACTERS
);
5870 gfxFloat trimmableWidth
= 0;
5871 PRBool brokeText
= forceBreak
>= 0 || transformedCharsFit
< transformedLength
;
5872 if (canTrimTrailingWhitespace
) {
5873 // Optimization: if we trimmed trailing whitespace, and we can be sure
5874 // this frame will be at the end of the line, then leave it trimmed off.
5875 // Otherwise we have to undo the trimming, in case we're not at the end of
5876 // the line. (If we actually do end up at the end of the line, we'll have
5877 // to trim it off again in TrimTrailingWhiteSpace, and we'd like to avoid
5878 // having to re-do it.)
5880 // We're definitely going to break so our trailing whitespace should
5881 // definitely be timmed. Record that we've already done it.
5882 AddStateBits(TEXT_TRIMMED_TRAILING_WHITESPACE
);
5884 // We might not be at the end of the line. (Note that even if this frame
5885 // ends in breakable whitespace, it might not be at the end of the line
5886 // because it might be followed by breakable, but preformatted, whitespace.)
5887 // Undo the trimming.
5888 textMetrics
.mAdvanceWidth
+= trimmedWidth
;
5889 trimmableWidth
= trimmedWidth
;
5890 if (mTextRun
->IsRightToLeft()) {
5891 // Space comes before text, so the bounding box is moved to the
5892 // right by trimmdWidth
5893 textMetrics
.mBoundingBox
.MoveBy(gfxPoint(trimmedWidth
, 0));
5898 if (!brokeText
&& lastBreak
>= 0) {
5899 // Since everything fit and no break was forced,
5900 // record the last break opportunity
5901 NS_ASSERTION(textMetrics
.mAdvanceWidth
- trimmableWidth
<= aReflowState
.availableWidth
,
5902 "If the text doesn't fit, and we have a break opportunity, why didn't MeasureText use it?");
5903 lineLayout
.NotifyOptionalBreakPosition(mContent
, lastBreak
, PR_TRUE
, breakPriority
);
5906 PRInt32 contentLength
= offset
+ charsFit
- GetContentOffset();
5908 /////////////////////////////////////////////////////////////////////
5909 // Compute output metrics
5910 /////////////////////////////////////////////////////////////////////
5912 // first-letter frames should use the tight bounding box metrics for ascent/descent
5913 // for good drop-cap effects
5914 if (GetStateBits() & TEXT_FIRST_LETTER
) {
5915 textMetrics
.mAscent
= PR_MAX(0, -textMetrics
.mBoundingBox
.Y());
5916 textMetrics
.mDescent
= PR_MAX(0, textMetrics
.mBoundingBox
.YMost());
5919 // Setup metrics for caller
5920 // Disallow negative widths
5921 aMetrics
.width
= NSToCoordCeil(PR_MAX(0, textMetrics
.mAdvanceWidth
));
5923 if (transformedCharsFit
== 0 && !usedHyphenation
) {
5924 aMetrics
.ascent
= 0;
5925 aMetrics
.height
= 0;
5926 } else if (needTightBoundingBox
) {
5927 // Use actual text metrics for floating first letter frame.
5928 aMetrics
.ascent
= NSToCoordCeil(textMetrics
.mAscent
);
5929 aMetrics
.height
= aMetrics
.ascent
+ NSToCoordCeil(textMetrics
.mDescent
);
5931 // Otherwise, ascent should contain the overline drawable area.
5932 // And also descent should contain the underline drawable area.
5933 // nsIFontMetrics::GetMaxAscent/GetMaxDescent contains them.
5934 nscoord fontAscent
, fontDescent
;
5935 nsIFontMetrics
* fm
= provider
.GetFontMetrics();
5936 fm
->GetMaxAscent(fontAscent
);
5937 fm
->GetMaxDescent(fontDescent
);
5938 aMetrics
.ascent
= PR_MAX(NSToCoordCeil(textMetrics
.mAscent
), fontAscent
);
5939 nscoord descent
= PR_MAX(NSToCoordCeil(textMetrics
.mDescent
), fontDescent
);
5940 aMetrics
.height
= aMetrics
.ascent
+ descent
;
5943 NS_ASSERTION(aMetrics
.ascent
>= 0, "Negative ascent???");
5944 NS_ASSERTION(aMetrics
.height
- aMetrics
.ascent
>= 0, "Negative descent???");
5946 mAscent
= aMetrics
.ascent
;
5948 // Handle text that runs outside its normal bounds.
5949 nsRect boundingBox
= RoundOut(textMetrics
.mBoundingBox
) + nsPoint(0, mAscent
);
5950 aMetrics
.mOverflowArea
.UnionRect(boundingBox
,
5951 nsRect(0, 0, aMetrics
.width
, aMetrics
.height
));
5953 UnionTextDecorationOverflow(aPresContext
, provider
, &aMetrics
.mOverflowArea
);
5955 /////////////////////////////////////////////////////////////////////
5956 // Clean up, update state
5957 /////////////////////////////////////////////////////////////////////
5959 // If all our characters are discarded or collapsed, then trimmable width
5960 // from the last textframe should be preserved. Otherwise the trimmable width
5961 // from this textframe overrides. (Currently in CSS trimmable width can be
5962 // at most one space so there's no way for trimmable width from a previous
5963 // frame to accumulate with trimmable width from this frame.)
5964 if (transformedCharsFit
> 0) {
5965 lineLayout
.SetTrimmableWidth(NSToCoordFloor(trimmableWidth
));
5966 AddStateBits(TEXT_HAS_NONCOLLAPSED_CHARACTERS
);
5968 if (charsFit
> 0 && charsFit
== length
&&
5969 HasSoftHyphenBefore(frag
, mTextRun
, offset
, end
)) {
5970 // Record a potential break after final soft hyphen
5971 lineLayout
.NotifyOptionalBreakPosition(mContent
, offset
+ length
,
5972 textMetrics
.mAdvanceWidth
+ provider
.GetHyphenWidth() <= availWidth
,
5975 PRBool breakAfter
= forceBreakAfter
;
5976 if (!breakAfter
&& charsFit
== length
&&
5977 transformedOffset
+ transformedLength
== mTextRun
->GetLength() &&
5978 (mTextRun
->GetFlags() & nsTextFrameUtils::TEXT_HAS_TRAILING_BREAK
)) {
5979 // We placed all the text in the textrun and we have a break opportunity at
5980 // the end of the textrun. We need to record it because the following
5981 // content may not care about nsLineBreaker.
5983 // Note that because we didn't break, we can be sure that (thanks to the
5984 // code up above) textMetrics.mAdvanceWidth includes the width of any
5985 // trailing whitespace. So we need to subtract trimmableWidth here
5986 // because if we did break at this point, that much width would be trimmed.
5987 if (textMetrics
.mAdvanceWidth
- trimmableWidth
> availWidth
) {
5988 breakAfter
= PR_TRUE
;
5990 lineLayout
.NotifyOptionalBreakPosition(mContent
, offset
+ length
, PR_TRUE
,
5994 if (completedFirstLetter
) {
5995 lineLayout
.SetFirstLetterStyleOK(PR_FALSE
);
5998 // Compute reflow status
5999 aStatus
= contentLength
== maxContentLength
6000 ? NS_FRAME_COMPLETE
: NS_FRAME_NOT_COMPLETE
;
6002 if (charsFit
== 0 && length
> 0) {
6003 // Couldn't place any text
6004 aStatus
= NS_INLINE_LINE_BREAK_BEFORE();
6005 } else if (contentLength
> 0 && mContentOffset
+ contentLength
- 1 == newLineOffset
) {
6007 aStatus
= NS_INLINE_LINE_BREAK_AFTER(aStatus
);
6008 lineLayout
.SetLineEndsInBR(PR_TRUE
);
6009 } else if (breakAfter
) {
6010 aStatus
= NS_INLINE_LINE_BREAK_AFTER(aStatus
);
6012 if (completedFirstLetter
) {
6013 lineLayout
.SetFirstLetterStyleOK(PR_FALSE
);
6014 aStatus
|= NS_INLINE_BREAK_FIRST_LETTER_COMPLETE
;
6017 // Compute space and letter counts for justification, if required
6018 if (!textStyle
->WhiteSpaceIsSignificant() &&
6019 lineContainer
->GetStyleText()->mTextAlign
== NS_STYLE_TEXT_ALIGN_JUSTIFY
) {
6020 AddStateBits(TEXT_JUSTIFICATION_ENABLED
); // This will include a space for trailing whitespace, if any is present.
6021 // This is corrected for in nsLineLayout::TrimWhiteSpaceIn.
6022 PRInt32 numJustifiableCharacters
=
6023 provider
.ComputeJustifiableCharacters(offset
, charsFit
);
6025 NS_ASSERTION(numJustifiableCharacters
<= charsFit
,
6026 "Bad justifiable character count");
6027 lineLayout
.SetTextJustificationWeights(numJustifiableCharacters
,
6028 charsFit
- numJustifiableCharacters
);
6031 SetLength(contentLength
);
6033 Invalidate(nsRect(nsPoint(0, 0), GetSize()));
6037 printf(": desiredSize=%d,%d(b=%d) status=%x\n",
6038 aMetrics
.width
, aMetrics
.height
, aMetrics
.ascent
,
6041 NS_FRAME_SET_TRUNCATION(aStatus
, aReflowState
, aMetrics
);
6045 /* virtual */ PRBool
6046 nsTextFrame::CanContinueTextRun() const
6048 // We can continue a text run through a text frame
6052 nsTextFrame::TrimOutput
6053 nsTextFrame::TrimTrailingWhiteSpace(nsIRenderingContext
* aRC
)
6056 result
.mChanged
= PR_FALSE
;
6057 result
.mLastCharIsJustifiable
= PR_FALSE
;
6058 result
.mDeltaWidth
= 0;
6060 AddStateBits(TEXT_END_OF_LINE
);
6062 PRInt32 contentLength
= GetContentLength();
6066 gfxContext
* ctx
= aRC
->ThebesContext();
6067 gfxSkipCharsIterator start
= EnsureTextRun(ctx
);
6068 NS_ENSURE_TRUE(mTextRun
, result
);
6070 PRUint32 trimmedStart
= start
.GetSkippedOffset();
6072 const nsTextFragment
* frag
= mContent
->GetText();
6073 TrimmedOffsets trimmed
= GetTrimmedOffsets(frag
, PR_TRUE
);
6074 gfxSkipCharsIterator trimmedEndIter
= start
;
6075 const nsStyleText
* textStyle
= GetStyleText();
6077 PRUint32 trimmedEnd
= trimmedEndIter
.ConvertOriginalToSkipped(trimmed
.GetEnd());
6079 if (GetStateBits() & TEXT_TRIMMED_TRAILING_WHITESPACE
) {
6080 // We pre-trimmed this frame, so the last character is justifiable
6081 result
.mLastCharIsJustifiable
= PR_TRUE
;
6082 } else if (trimmed
.GetEnd() < GetContentEnd()) {
6083 gfxSkipCharsIterator end
= trimmedEndIter
;
6084 PRUint32 endOffset
= end
.ConvertOriginalToSkipped(GetContentOffset() + contentLength
);
6085 if (trimmedEnd
< endOffset
) {
6086 // We can't be dealing with tabs here ... they wouldn't be trimmed. So it's
6087 // OK to pass null for the line container.
6088 PropertyProvider
provider(mTextRun
, textStyle
, frag
, this, start
, contentLength
,
6090 delta
= mTextRun
->GetAdvanceWidth(trimmedEnd
, endOffset
- trimmedEnd
, &provider
);
6091 // non-compressed whitespace being skipped at end of line -> justifiable
6092 // XXX should we actually *count* justifiable characters that should be
6093 // removed from the overall count? I think so...
6094 result
.mLastCharIsJustifiable
= PR_TRUE
;
6095 result
.mChanged
= PR_TRUE
;
6099 if (!result
.mLastCharIsJustifiable
&&
6100 (GetStateBits() & TEXT_JUSTIFICATION_ENABLED
)) {
6101 // Check if any character in the last cluster is justifiable
6102 PropertyProvider
provider(mTextRun
, textStyle
, frag
, this, start
, contentLength
,
6104 PRBool isCJK
= IsChineseJapaneseLangGroup(this);
6105 gfxSkipCharsIterator
justificationStart(start
), justificationEnd(trimmedEndIter
);
6106 provider
.FindJustificationRange(&justificationStart
, &justificationEnd
);
6109 for (i
= justificationEnd
.GetOriginalOffset(); i
< trimmed
.GetEnd(); ++i
) {
6110 if (IsJustifiableCharacter(frag
, i
, isCJK
)) {
6111 result
.mLastCharIsJustifiable
= PR_TRUE
;
6116 gfxFloat advanceDelta
;
6117 mTextRun
->SetLineBreaks(trimmedStart
, trimmedEnd
- trimmedStart
,
6118 (GetStateBits() & TEXT_START_OF_LINE
) != 0, PR_TRUE
,
6119 &advanceDelta
, ctx
);
6120 if (advanceDelta
!= 0) {
6121 result
.mChanged
= PR_TRUE
;
6124 // aDeltaWidth is *subtracted* from our width.
6125 // If advanceDelta is positive then setting the line break made us longer,
6126 // so aDeltaWidth could go negative.
6127 result
.mDeltaWidth
= NSToCoordFloor(delta
- advanceDelta
);
6128 // If aDeltaWidth goes negative, that means this frame might not actually fit
6129 // anymore!!! We need higher level line layout to recover somehow.
6130 // If it's because the frame has a soft hyphen that is now being displayed,
6131 // this should actually be OK, because our reflow recorded the break
6132 // opportunity that allowed the soft hyphen to be used, and we wouldn't
6133 // have recorded the opportunity unless the hyphen fit (or was the first
6134 // opportunity on the line).
6135 // Otherwise this can/ really only happen when we have glyphs with special
6136 // shapes at the end of lines, I think. Breaking inside a kerning pair won't
6137 // do it because that would mean we broke inside this textrun, and
6138 // BreakAndMeasureText should make sure the resulting shaped substring fits.
6139 // Maybe if we passed a maxTextLength? But that only happens at direction
6140 // changes (so we wouldn't kern across the boundary) or for first-letter
6141 // (which always fits because it starts the line!).
6142 NS_WARN_IF_FALSE(result
.mDeltaWidth
>= 0,
6143 "Negative deltawidth, something odd is happening");
6147 printf(": trim => %d\n", result
.mDeltaWidth
);
6153 nsTextFrame::RecomputeOverflowRect()
6155 gfxSkipCharsIterator iter
= EnsureTextRun();
6157 return nsRect(nsPoint(0,0), GetSize());
6159 PropertyProvider
provider(this, iter
);
6160 provider
.InitializeForDisplay(PR_TRUE
);
6162 gfxTextRun::Metrics textMetrics
=
6163 mTextRun
->MeasureText(provider
.GetStart().GetSkippedOffset(),
6164 ComputeTransformedLength(provider
), PR_FALSE
, nsnull
,
6167 nsRect boundingBox
= RoundOut(textMetrics
.mBoundingBox
) + nsPoint(0, mAscent
);
6168 boundingBox
.UnionRect(boundingBox
,
6169 nsRect(nsPoint(0,0), GetSize()));
6171 UnionTextDecorationOverflow(PresContext(), provider
, &boundingBox
);
6176 static PRUnichar
TransformChar(const nsStyleText
* aStyle
, gfxTextRun
* aTextRun
,
6177 PRUint32 aSkippedOffset
, PRUnichar aChar
)
6179 if (aChar
== '\n') {
6180 return aStyle
->NewlineIsSignificant() ? aChar
: ' ';
6182 switch (aStyle
->mTextTransform
) {
6183 case NS_STYLE_TEXT_TRANSFORM_LOWERCASE
:
6184 nsContentUtils::GetCaseConv()->ToLower(aChar
, &aChar
);
6186 case NS_STYLE_TEXT_TRANSFORM_UPPERCASE
:
6187 nsContentUtils::GetCaseConv()->ToUpper(aChar
, &aChar
);
6189 case NS_STYLE_TEXT_TRANSFORM_CAPITALIZE
:
6190 if (aTextRun
->CanBreakLineBefore(aSkippedOffset
)) {
6191 nsContentUtils::GetCaseConv()->ToTitle(aChar
, &aChar
);
6199 nsresult
nsTextFrame::GetRenderedText(nsAString
* aAppendToString
,
6200 gfxSkipChars
* aSkipChars
,
6201 gfxSkipCharsIterator
* aSkipIter
,
6202 PRUint32 aSkippedStartOffset
,
6203 PRUint32 aSkippedMaxLength
)
6205 // The handling of aSkippedStartOffset and aSkippedMaxLength could be more efficient...
6206 gfxSkipCharsBuilder skipCharsBuilder
;
6207 nsTextFrame
* textFrame
;
6208 const nsTextFragment
* textFrag
= mContent
->GetText();
6209 PRUint32 keptCharsLength
= 0;
6210 PRUint32 validCharsLength
= 0;
6212 // Build skipChars and copy text, for each text frame in this continuation block
6213 for (textFrame
= this; textFrame
;
6214 textFrame
= static_cast<nsTextFrame
*>(textFrame
->GetNextContinuation())) {
6215 // For each text frame continuation in this block ...
6217 // Ensure the text run and grab the gfxSkipCharsIterator for it
6218 gfxSkipCharsIterator iter
= textFrame
->EnsureTextRun();
6219 if (!textFrame
->mTextRun
)
6220 return NS_ERROR_FAILURE
;
6222 // Skip to the start of the text run, past ignored chars at start of line
6223 // XXX In the future we may decide to trim extra spaces before a hard line
6224 // break, in which case we need to accurately detect those sitations and
6225 // call GetTrimmedOffsets() with PR_TRUE to trim whitespace at the line's end
6226 TrimmedOffsets trimmedContentOffsets
= textFrame
->GetTrimmedOffsets(textFrag
, PR_FALSE
);
6227 PRInt32 startOfLineSkipChars
= trimmedContentOffsets
.mStart
- textFrame
->mContentOffset
;
6228 if (startOfLineSkipChars
> 0) {
6229 skipCharsBuilder
.SkipChars(startOfLineSkipChars
);
6230 iter
.SetOriginalOffset(trimmedContentOffsets
.mStart
);
6233 // Keep and copy the appropriate chars withing the caller's requested range
6234 const nsStyleText
* textStyle
= textFrame
->GetStyleText();
6235 while (iter
.GetOriginalOffset() < trimmedContentOffsets
.GetEnd() &&
6236 keptCharsLength
< aSkippedMaxLength
) {
6237 // For each original char from content text
6238 if (iter
.IsOriginalCharSkipped() || ++validCharsLength
<= aSkippedStartOffset
) {
6239 skipCharsBuilder
.SkipChar();
6242 skipCharsBuilder
.KeepChar();
6243 if (aAppendToString
) {
6244 aAppendToString
->Append(
6245 TransformChar(textStyle
, textFrame
->mTextRun
, iter
.GetSkippedOffset(),
6246 textFrag
->CharAt(iter
.GetOriginalOffset())));
6249 iter
.AdvanceOriginal(1);
6251 if (keptCharsLength
>= aSkippedMaxLength
) {
6252 break; // Already past the end, don't build string or gfxSkipCharsIter anymore
6257 aSkipChars
->TakeFrom(&skipCharsBuilder
); // Copy skipChars into aSkipChars
6259 // Caller must provide both pointers in order to retrieve a gfxSkipCharsIterator,
6260 // because the gfxSkipCharsIterator holds a weak pointer to the gfxSkipCars.
6261 *aSkipIter
= gfxSkipCharsIterator(*aSkipChars
, GetContentLength());
6269 // Translate the mapped content into a string that's printable
6271 nsTextFrame::ToCString(nsCString
& aBuf
, PRInt32
* aTotalContentLength
) const
6273 // Get the frames text content
6274 const nsTextFragment
* frag
= mContent
->GetText();
6279 // Compute the total length of the text content.
6280 *aTotalContentLength
= frag
->GetLength();
6282 PRInt32 contentLength
= GetContentLength();
6283 // Set current fragment and current fragment offset
6284 if (0 == contentLength
) {
6287 PRInt32 fragOffset
= GetContentOffset();
6288 PRInt32 n
= fragOffset
+ contentLength
;
6289 while (fragOffset
< n
) {
6290 PRUnichar ch
= frag
->CharAt(fragOffset
++);
6292 aBuf
.AppendLiteral("\\r");
6293 } else if (ch
== '\n') {
6294 aBuf
.AppendLiteral("\\n");
6295 } else if (ch
== '\t') {
6296 aBuf
.AppendLiteral("\\t");
6297 } else if ((ch
< ' ') || (ch
>= 127)) {
6298 aBuf
.Append(nsPrintfCString("\\u%04x", ch
));
6307 nsTextFrame::GetType() const
6309 return nsGkAtoms::textFrame
;
6312 /* virtual */ PRBool
6313 nsTextFrame::IsEmpty()
6315 NS_ASSERTION(!(mState
& TEXT_IS_ONLY_WHITESPACE
) ||
6316 !(mState
& TEXT_ISNOT_ONLY_WHITESPACE
),
6319 // XXXldb Should this check compatibility mode as well???
6320 const nsStyleText
* textStyle
= GetStyleText();
6321 if (textStyle
->WhiteSpaceIsSignificant()) {
6322 // XXX shouldn't we return true if the length is zero?
6326 if (mState
& TEXT_ISNOT_ONLY_WHITESPACE
) {
6330 if (mState
& TEXT_IS_ONLY_WHITESPACE
) {
6334 PRBool isEmpty
= IsAllWhitespace(mContent
->GetText(),
6335 textStyle
->mWhiteSpace
!= NS_STYLE_WHITESPACE_PRE_LINE
);
6336 mState
|= (isEmpty
? TEXT_IS_ONLY_WHITESPACE
: TEXT_ISNOT_ONLY_WHITESPACE
);
6342 nsTextFrame::GetFrameName(nsAString
& aResult
) const
6344 return MakeFrameName(NS_LITERAL_STRING("Text"), aResult
);
6347 NS_IMETHODIMP_(nsFrameState
)
6348 nsTextFrame::GetDebugStateBits() const
6350 // mask out our emptystate flags; those are just caches
6351 return nsFrame::GetDebugStateBits() &
6352 ~(TEXT_WHITESPACE_FLAGS
| TEXT_REFLOW_FLAGS
);
6356 nsTextFrame::List(FILE* out
, PRInt32 aIndent
) const
6359 IndentBy(out
, aIndent
);
6361 #ifdef DEBUG_waterson
6362 fprintf(out
, " [parent=%p]", mParent
);
6365 fprintf(out
, " [view=%p]", static_cast<void*>(GetView()));
6368 PRInt32 totalContentLength
;
6370 ToCString(tmp
, &totalContentLength
);
6372 // Output the first/last content offset and prev/next in flow info
6373 PRBool isComplete
= GetContentEnd() == totalContentLength
;
6374 fprintf(out
, "[%d,%d,%c] ",
6375 GetContentOffset(), GetContentLength(),
6376 isComplete
? 'T':'F');
6378 if (nsnull
!= mNextSibling
) {
6379 fprintf(out
, " next=%p", static_cast<void*>(mNextSibling
));
6381 nsIFrame
* prevContinuation
= GetPrevContinuation();
6382 if (nsnull
!= prevContinuation
) {
6383 fprintf(out
, " prev-continuation=%p", static_cast<void*>(prevContinuation
));
6385 if (nsnull
!= mNextContinuation
) {
6386 fprintf(out
, " next-continuation=%p", static_cast<void*>(mNextContinuation
));
6389 // Output the rect and state
6390 fprintf(out
, " {%d,%d,%d,%d}", mRect
.x
, mRect
.y
, mRect
.width
, mRect
.height
);
6392 if (mState
& NS_FRAME_SELECTED_CONTENT
) {
6393 fprintf(out
, " [state=%08x] SELECTED", mState
);
6395 fprintf(out
, " [state=%08x]", mState
);
6398 fprintf(out
, " [content=%p]", static_cast<void*>(mContent
));
6399 if (GetStateBits() & NS_FRAME_OUTSIDE_CHILDREN
) {
6400 nsRect overflowArea
= GetOverflowRect();
6401 fprintf(out
, " [overflow=%d,%d,%d,%d]", overflowArea
.x
, overflowArea
.y
,
6402 overflowArea
.width
, overflowArea
.height
);
6404 fprintf(out
, " sc=%p", static_cast<void*>(mStyleContext
));
6405 nsIAtom
* pseudoTag
= mStyleContext
->GetPseudoType();
6407 nsAutoString atomString
;
6408 pseudoTag
->ToString(atomString
);
6409 fprintf(out
, " pst=%s",
6410 NS_LossyConvertUTF16toASCII(atomString
).get());
6417 IndentBy(out
, aIndent
);
6419 fputs(tmp
.get(), out
);
6423 IndentBy(out
, aIndent
);
6431 nsTextFrame::AdjustOffsetsForBidi(PRInt32 aStart
, PRInt32 aEnd
)
6433 AddStateBits(NS_FRAME_IS_BIDI
);
6436 * After Bidi resolution we may need to reassign text runs.
6437 * This is called during bidi resolution from the block container, so we
6438 * shouldn't be holding a local reference to a textrun anywhere.
6442 nsTextFrame
* prev
= static_cast<nsTextFrame
*>(GetPrevContinuation());
6444 // the bidi resolver can be very evil when columns/pages are involved. Don't
6445 // let it violate our invariants.
6446 PRInt32 prevOffset
= prev
->GetContentOffset();
6447 aStart
= PR_MAX(aStart
, prevOffset
);
6448 aEnd
= PR_MAX(aEnd
, prevOffset
);
6449 prev
->ClearTextRun();
6452 mContentOffset
= aStart
;
6453 SetLength(aEnd
- aStart
);
6457 * @return PR_TRUE if this text frame ends with a newline character. It should return
6458 * PR_FALSE if it is not a text frame.
6461 nsTextFrame::HasTerminalNewline() const
6463 return ::HasTerminalNewline(this);
6467 nsTextFrame::IsAtEndOfLine() const
6469 return (GetStateBits() & TEXT_END_OF_LINE
) != 0;