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
);
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_TextHighlightForeground
,
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.");
2972 nscolor defaultBgColor
= mPresContext
->DefaultBackgroundColor();
2973 NS_ASSERTION(NS_GET_A(defaultBgColor
) == 255,
2974 "default background color is not opaque");
2976 mFrameBackgroundColor
= NS_ComposeColors(defaultBgColor
,
2977 bg
->mBackgroundColor
);
2979 nsILookAndFeel
* look
= mPresContext
->LookAndFeel();
2980 nscolor defaultWindowBackgroundColor
, selectionTextColor
, selectionBGColor
;
2981 look
->GetColor(nsILookAndFeel::eColor_TextSelectBackground
,
2983 look
->GetColor(nsILookAndFeel::eColor_TextSelectForeground
,
2984 selectionTextColor
);
2985 look
->GetColor(nsILookAndFeel::eColor_WindowBackground
,
2986 defaultWindowBackgroundColor
);
2988 mSufficientContrast
=
2989 PR_MIN(PR_MIN(NS_SUFFICIENT_LUMINOSITY_DIFFERENCE
,
2990 NS_LUMINOSITY_DIFFERENCE(selectionTextColor
,
2992 NS_LUMINOSITY_DIFFERENCE(defaultWindowBackgroundColor
,
2995 mInitCommonColors
= PR_TRUE
;
2998 static nsIFrame
* GetNonGeneratedAncestor(nsIFrame
* f
) {
2999 while (f
->GetStateBits() & NS_FRAME_GENERATED_CONTENT
) {
3000 f
= nsLayoutUtils::GetParentOrPlaceholderFor(f
->PresContext()->FrameManager(), f
);
3006 FindElementAncestor(nsINode
* aNode
)
3008 while (aNode
&& !aNode
->IsNodeOfType(nsINode::eELEMENT
)) {
3009 aNode
= aNode
->GetParent();
3011 return static_cast<nsIContent
*>(aNode
);
3015 nsTextPaintStyle::InitSelectionColors()
3017 if (mInitSelectionColors
)
3020 PRInt16 selectionFlags
;
3021 PRInt16 selectionStatus
= mFrame
->GetSelectionStatus(&selectionFlags
);
3022 if (!(selectionFlags
& nsISelectionDisplay::DISPLAY_TEXT
) ||
3023 selectionStatus
< nsISelectionController::SELECTION_ON
) {
3024 // Not displaying the normal selection.
3025 // We're not caching this fact, so every call to GetSelectionColors
3026 // will come through here. We could avoid this, but it's not really worth it.
3030 mInitSelectionColors
= PR_TRUE
;
3032 nsIFrame
* nonGeneratedAncestor
= GetNonGeneratedAncestor(mFrame
);
3033 nsIContent
* selectionContent
= FindElementAncestor(nonGeneratedAncestor
->GetContent());
3035 if (selectionContent
&&
3036 selectionStatus
== nsISelectionController::SELECTION_ON
) {
3037 nsRefPtr
<nsStyleContext
> sc
= nsnull
;
3038 sc
= mPresContext
->StyleSet()->
3039 ProbePseudoStyleFor(selectionContent
, nsCSSPseudoElements::mozSelection
,
3040 mFrame
->GetStyleContext());
3041 // Use -moz-selection pseudo class.
3043 const nsStyleBackground
* bg
= sc
->GetStyleBackground();
3044 mSelectionBGColor
= bg
->mBackgroundColor
;
3045 mSelectionTextColor
= sc
->GetStyleColor()->mColor
;
3050 nsILookAndFeel
* look
= mPresContext
->LookAndFeel();
3052 nscolor selectionBGColor
;
3053 look
->GetColor(nsILookAndFeel::eColor_TextSelectBackground
,
3056 if (selectionStatus
== nsISelectionController::SELECTION_ATTENTION
) {
3057 look
->GetColor(nsILookAndFeel::eColor_TextSelectBackgroundAttention
,
3059 mSelectionBGColor
= EnsureDifferentColors(mSelectionBGColor
,
3061 } else if (selectionStatus
!= nsISelectionController::SELECTION_ON
) {
3062 look
->GetColor(nsILookAndFeel::eColor_TextSelectBackgroundDisabled
,
3064 mSelectionBGColor
= EnsureDifferentColors(mSelectionBGColor
,
3067 mSelectionBGColor
= selectionBGColor
;
3070 look
->GetColor(nsILookAndFeel::eColor_TextSelectForeground
,
3071 mSelectionTextColor
);
3073 // On MacOS X, we don't exchange text color and BG color.
3074 if (mSelectionTextColor
== NS_DONT_CHANGE_COLOR
) {
3075 mSelectionTextColor
= EnsureDifferentColors(mFrame
->GetStyleColor()->mColor
,
3078 EnsureSufficientContrast(&mSelectionTextColor
, &mSelectionBGColor
);
3083 nsTextPaintStyle::nsIMEStyle
*
3084 nsTextPaintStyle::GetIMEStyle(PRInt32 aIndex
)
3086 InitIMEStyle(aIndex
);
3087 return &mIMEStyle
[aIndex
];
3091 nsILookAndFeel::nsColorID mForeground
, mBackground
, mLine
;
3092 nsILookAndFeel::nsMetricID mLineStyle
;
3094 static StyleIDs IMEStyleIDs
[] = {
3095 { nsILookAndFeel::eColor_IMERawInputForeground
,
3096 nsILookAndFeel::eColor_IMERawInputBackground
,
3097 nsILookAndFeel::eColor_IMERawInputUnderline
,
3098 nsILookAndFeel::eMetric_IMERawInputUnderlineStyle
},
3099 { nsILookAndFeel::eColor_IMESelectedRawTextForeground
,
3100 nsILookAndFeel::eColor_IMESelectedRawTextBackground
,
3101 nsILookAndFeel::eColor_IMESelectedRawTextUnderline
,
3102 nsILookAndFeel::eMetric_IMESelectedRawTextUnderlineStyle
},
3103 { nsILookAndFeel::eColor_IMEConvertedTextForeground
,
3104 nsILookAndFeel::eColor_IMEConvertedTextBackground
,
3105 nsILookAndFeel::eColor_IMEConvertedTextUnderline
,
3106 nsILookAndFeel::eMetric_IMEConvertedTextUnderlineStyle
},
3107 { nsILookAndFeel::eColor_IMESelectedConvertedTextForeground
,
3108 nsILookAndFeel::eColor_IMESelectedConvertedTextBackground
,
3109 nsILookAndFeel::eColor_IMESelectedConvertedTextUnderline
,
3110 nsILookAndFeel::eMetric_IMESelectedConvertedTextUnderline
}
3113 static PRUint8 sUnderlineStyles
[] = {
3114 NS_STYLE_BORDER_STYLE_NONE
, // NS_UNDERLINE_STYLE_NONE 0
3115 NS_STYLE_BORDER_STYLE_DOTTED
, // NS_UNDERLINE_STYLE_DOTTED 1
3116 NS_STYLE_BORDER_STYLE_DASHED
, // NS_UNDERLINE_STYLE_DASHED 2
3117 NS_STYLE_BORDER_STYLE_SOLID
, // NS_UNDERLINE_STYLE_SOLID 3
3118 NS_STYLE_BORDER_STYLE_DOUBLE
// NS_UNDERLINE_STYLE_DOUBLE 4
3122 nsTextPaintStyle::InitIMEStyle(PRInt32 aIndex
)
3124 nsIMEStyle
* IMEStyle
= &mIMEStyle
[aIndex
];
3125 if (IMEStyle
->mInit
)
3128 StyleIDs
* styleIDs
= &IMEStyleIDs
[aIndex
];
3130 nsILookAndFeel
* look
= mPresContext
->LookAndFeel();
3131 nscolor foreColor
, backColor
, lineColor
;
3133 look
->GetColor(styleIDs
->mForeground
, foreColor
);
3134 look
->GetColor(styleIDs
->mBackground
, backColor
);
3135 look
->GetColor(styleIDs
->mLine
, lineColor
);
3136 look
->GetMetric(styleIDs
->mLineStyle
, lineStyle
);
3138 // Convert special color to actual color
3139 NS_ASSERTION(foreColor
!= NS_TRANSPARENT
,
3140 "foreColor cannot be NS_TRANSPARENT");
3141 NS_ASSERTION(backColor
!= NS_SAME_AS_FOREGROUND_COLOR
,
3142 "backColor cannot be NS_SAME_AS_FOREGROUND_COLOR");
3143 NS_ASSERTION(backColor
!= NS_40PERCENT_FOREGROUND_COLOR
,
3144 "backColor cannot be NS_40PERCENT_FOREGROUND_COLOR");
3146 foreColor
= GetResolvedForeColor(foreColor
, GetTextColor(), backColor
);
3148 if (NS_GET_A(backColor
) > 0)
3149 EnsureSufficientContrast(&foreColor
, &backColor
);
3151 lineColor
= GetResolvedForeColor(lineColor
, foreColor
, backColor
);
3153 if (!NS_IS_VALID_UNDERLINE_STYLE(lineStyle
))
3154 lineStyle
= NS_UNDERLINE_STYLE_SOLID
;
3156 IMEStyle
->mTextColor
= foreColor
;
3157 IMEStyle
->mBGColor
= backColor
;
3158 IMEStyle
->mUnderlineColor
= lineColor
;
3159 IMEStyle
->mUnderlineStyle
= sUnderlineStyles
[lineStyle
];
3160 IMEStyle
->mInit
= PR_TRUE
;
3162 if (mIMEUnderlineRelativeSize
== -1.0f
) {
3163 look
->GetMetric(nsILookAndFeel::eMetricFloat_IMEUnderlineRelativeSize
,
3164 mIMEUnderlineRelativeSize
);
3165 NS_ASSERTION(mIMEUnderlineRelativeSize
>= 0.0f
,
3166 "underline size must be larger than 0");
3170 inline nscolor
Get40PercentColor(nscolor aForeColor
, nscolor aBackColor
)
3172 nscolor foreColor
= NS_RGBA(NS_GET_R(aForeColor
),
3173 NS_GET_G(aForeColor
),
3174 NS_GET_B(aForeColor
),
3175 (PRUint8
)(255 * 0.4f
));
3176 // Don't use true alpha color for readability.
3177 return NS_ComposeColors(aBackColor
, foreColor
);
3181 nsTextPaintStyle::GetResolvedForeColor(nscolor aColor
,
3182 nscolor aDefaultForeColor
,
3185 if (aColor
== NS_SAME_AS_FOREGROUND_COLOR
)
3186 return aDefaultForeColor
;
3188 if (aColor
!= NS_40PERCENT_FOREGROUND_COLOR
)
3191 // Get actual background color
3192 nscolor actualBGColor
= aBackColor
;
3193 if (actualBGColor
== NS_TRANSPARENT
) {
3195 actualBGColor
= mFrameBackgroundColor
;
3197 return Get40PercentColor(aDefaultForeColor
, actualBGColor
);
3200 //-----------------------------------------------------------------------------
3202 #ifdef ACCESSIBILITY
3203 NS_IMETHODIMP
nsTextFrame::GetAccessible(nsIAccessible
** aAccessible
)
3206 nsAutoString renderedWhitespace
;
3207 GetRenderedText(&renderedWhitespace
, nsnull
, nsnull
, 0, 1);
3208 if (renderedWhitespace
.IsEmpty()) {
3209 return NS_ERROR_FAILURE
;
3213 nsCOMPtr
<nsIAccessibilityService
> accService
= do_GetService("@mozilla.org/accessibilityService;1");
3216 return accService
->CreateHTMLTextAccessible(static_cast<nsIFrame
*>(this), aAccessible
);
3218 return NS_ERROR_FAILURE
;
3223 //-----------------------------------------------------------------------------
3225 nsTextFrame::Init(nsIContent
* aContent
,
3227 nsIFrame
* aPrevInFlow
)
3229 NS_ASSERTION(!aPrevInFlow
, "Can't be a continuation!");
3230 NS_PRECONDITION(aContent
->IsNodeOfType(nsINode::eTEXT
),
3232 // We're not a continuing frame.
3233 // mContentOffset = 0; not necessary since we get zeroed out at init
3234 return nsFrame::Init(aContent
, aParent
, aPrevInFlow
);
3238 nsTextFrame::Destroy()
3241 if (mNextContinuation
) {
3242 mNextContinuation
->SetPrevInFlow(nsnull
);
3244 // Let the base class destroy the frame
3248 class nsContinuingTextFrame
: public nsTextFrame
{
3250 friend nsIFrame
* NS_NewContinuingTextFrame(nsIPresShell
* aPresShell
, nsStyleContext
* aContext
);
3252 NS_IMETHOD
Init(nsIContent
* aContent
,
3254 nsIFrame
* aPrevInFlow
);
3256 virtual void Destroy();
3258 virtual nsIFrame
* GetPrevContinuation() const {
3259 return mPrevContinuation
;
3261 NS_IMETHOD
SetPrevContinuation(nsIFrame
* aPrevContinuation
) {
3262 NS_ASSERTION (!aPrevContinuation
|| GetType() == aPrevContinuation
->GetType(),
3263 "setting a prev continuation with incorrect type!");
3264 NS_ASSERTION (!nsSplittableFrame::IsInPrevContinuationChain(aPrevContinuation
, this),
3265 "creating a loop in continuation chain!");
3266 mPrevContinuation
= aPrevContinuation
;
3267 RemoveStateBits(NS_FRAME_IS_FLUID_CONTINUATION
);
3270 virtual nsIFrame
* GetPrevInFlowVirtual() const { return GetPrevInFlow(); }
3271 nsIFrame
* GetPrevInFlow() const {
3272 return (GetStateBits() & NS_FRAME_IS_FLUID_CONTINUATION
) ? mPrevContinuation
: nsnull
;
3274 NS_IMETHOD
SetPrevInFlow(nsIFrame
* aPrevInFlow
) {
3275 NS_ASSERTION (!aPrevInFlow
|| GetType() == aPrevInFlow
->GetType(),
3276 "setting a prev in flow with incorrect type!");
3277 NS_ASSERTION (!nsSplittableFrame::IsInPrevContinuationChain(aPrevInFlow
, this),
3278 "creating a loop in continuation chain!");
3279 mPrevContinuation
= aPrevInFlow
;
3280 AddStateBits(NS_FRAME_IS_FLUID_CONTINUATION
);
3283 virtual nsIFrame
* GetFirstInFlow() const;
3284 virtual nsIFrame
* GetFirstContinuation() const;
3286 virtual void AddInlineMinWidth(nsIRenderingContext
*aRenderingContext
,
3287 InlineMinWidthData
*aData
);
3288 virtual void AddInlinePrefWidth(nsIRenderingContext
*aRenderingContext
,
3289 InlinePrefWidthData
*aData
);
3291 virtual nsresult
GetRenderedText(nsAString
* aString
= nsnull
,
3292 gfxSkipChars
* aSkipChars
= nsnull
,
3293 gfxSkipCharsIterator
* aSkipIter
= nsnull
,
3294 PRUint32 aSkippedStartOffset
= 0,
3295 PRUint32 aSkippedMaxLength
= PR_UINT32_MAX
)
3296 { return NS_ERROR_NOT_IMPLEMENTED
; } // Call on a primary text frame only
3299 nsContinuingTextFrame(nsStyleContext
* aContext
) : nsTextFrame(aContext
) {}
3300 nsIFrame
* mPrevContinuation
;
3304 nsContinuingTextFrame::Init(nsIContent
* aContent
,
3306 nsIFrame
* aPrevInFlow
)
3308 NS_ASSERTION(aPrevInFlow
, "Must be a continuation!");
3309 // NOTE: bypassing nsTextFrame::Init!!!
3310 nsresult rv
= nsFrame::Init(aContent
, aParent
, aPrevInFlow
);
3313 nsTextFrame
* nextContinuation
=
3314 static_cast<nsTextFrame
*>(aPrevInFlow
->GetNextContinuation());
3316 // Hook the frame into the flow
3317 SetPrevInFlow(aPrevInFlow
);
3318 aPrevInFlow
->SetNextInFlow(this);
3319 nsTextFrame
* prev
= static_cast<nsTextFrame
*>(aPrevInFlow
);
3320 mContentOffset
= prev
->GetContentOffset() + prev
->GetContentLengthHint();
3321 NS_ASSERTION(mContentOffset
< PRInt32(aContent
->GetText()->GetLength()),
3322 "Creating ContinuingTextFrame, but there is no more content");
3323 if (prev
->GetStyleContext() != GetStyleContext()) {
3324 // We're taking part of prev's text, and its style may be different
3325 // so clear its textrun which may no longer be valid (and don't set ours)
3326 prev
->ClearTextRun();
3328 mTextRun
= prev
->GetTextRun();
3331 if (aPrevInFlow
->GetStateBits() & NS_FRAME_IS_BIDI
) {
3332 nsPropertyTable
*propTable
= PresContext()->PropertyTable();
3333 propTable
->SetProperty(this, nsGkAtoms::embeddingLevel
,
3334 propTable
->GetProperty(aPrevInFlow
, nsGkAtoms::embeddingLevel
),
3336 propTable
->SetProperty(this, nsGkAtoms::baseLevel
,
3337 propTable
->GetProperty(aPrevInFlow
, nsGkAtoms::baseLevel
),
3339 propTable
->SetProperty(this, nsGkAtoms::charType
,
3340 propTable
->GetProperty(aPrevInFlow
, nsGkAtoms::charType
),
3342 if (nextContinuation
) {
3343 SetNextContinuation(nextContinuation
);
3344 nextContinuation
->SetPrevContinuation(this);
3345 // Adjust next-continuations' content offset as needed.
3346 while (nextContinuation
&&
3347 nextContinuation
->GetContentOffset() < mContentOffset
) {
3349 propTable
->GetProperty(this, nsGkAtoms::embeddingLevel
) ==
3350 propTable
->GetProperty(nextContinuation
, nsGkAtoms::embeddingLevel
) &&
3351 propTable
->GetProperty(this, nsGkAtoms::baseLevel
) ==
3352 propTable
->GetProperty(nextContinuation
, nsGkAtoms::baseLevel
) &&
3353 propTable
->GetProperty(this, nsGkAtoms::charType
) ==
3354 propTable
->GetProperty(nextContinuation
, nsGkAtoms::charType
),
3355 "stealing text from different type of BIDI continuation");
3356 nextContinuation
->mContentOffset
= mContentOffset
;
3357 nextContinuation
= static_cast<nsTextFrame
*>(nextContinuation
->GetNextContinuation());
3360 mState
|= NS_FRAME_IS_BIDI
;
3361 } // prev frame is bidi
3368 nsContinuingTextFrame::Destroy()
3371 if (mPrevContinuation
|| mNextContinuation
) {
3372 nsSplittableFrame::RemoveFromFlow(this);
3374 // Let the base class destroy the frame
3379 nsContinuingTextFrame::GetFirstInFlow() const
3381 // Can't cast to |nsContinuingTextFrame*| because the first one isn't.
3382 nsIFrame
*firstInFlow
,
3383 *previous
= const_cast<nsIFrame
*>
3384 (static_cast<const nsIFrame
*>(this));
3386 firstInFlow
= previous
;
3387 previous
= firstInFlow
->GetPrevInFlow();
3393 nsContinuingTextFrame::GetFirstContinuation() const
3395 // Can't cast to |nsContinuingTextFrame*| because the first one isn't.
3396 nsIFrame
*firstContinuation
,
3397 *previous
= const_cast<nsIFrame
*>
3398 (static_cast<const nsIFrame
*>(mPrevContinuation
));
3400 NS_ASSERTION(previous
, "How can an nsContinuingTextFrame be the first continuation?");
3403 firstContinuation
= previous
;
3404 previous
= firstContinuation
->GetPrevContinuation();
3406 return firstContinuation
;
3409 // XXX Do we want to do all the work for the first-in-flow or do the
3410 // work for each part? (Be careful of first-letter / first-line, though,
3411 // especially first-line!) Doing all the work on the first-in-flow has
3412 // the advantage of avoiding the potential for incremental reflow bugs,
3413 // but depends on our maintining the frame tree in reasonable ways even
3414 // for edge cases (block-within-inline splits, nextBidi, etc.)
3416 // XXX We really need to make :first-letter happen during frame
3419 // Needed for text frames in XUL.
3420 /* virtual */ nscoord
3421 nsTextFrame::GetMinWidth(nsIRenderingContext
*aRenderingContext
)
3423 return nsLayoutUtils::MinWidthFromInline(this, aRenderingContext
);
3426 // Needed for text frames in XUL.
3427 /* virtual */ nscoord
3428 nsTextFrame::GetPrefWidth(nsIRenderingContext
*aRenderingContext
)
3430 return nsLayoutUtils::PrefWidthFromInline(this, aRenderingContext
);
3434 nsContinuingTextFrame::AddInlineMinWidth(nsIRenderingContext
*aRenderingContext
,
3435 InlineMinWidthData
*aData
)
3437 // Do nothing, since the first-in-flow accounts for everything.
3442 nsContinuingTextFrame::AddInlinePrefWidth(nsIRenderingContext
*aRenderingContext
,
3443 InlinePrefWidthData
*aData
)
3445 // Do nothing, since the first-in-flow accounts for everything.
3450 DestroySelectionDetails(SelectionDetails
* aDetails
)
3453 SelectionDetails
* next
= aDetails
->mNext
;
3459 //----------------------------------------------------------------------
3461 #if defined(DEBUG_rbs) || defined(DEBUG_bzbarsky)
3463 VerifyNotDirty(nsFrameState state
)
3465 PRBool isZero
= state
& NS_FRAME_FIRST_REFLOW
;
3466 PRBool isDirty
= state
& NS_FRAME_IS_DIRTY
;
3467 if (!isZero
&& isDirty
)
3468 NS_WARNING("internal offsets may be out-of-sync");
3470 #define DEBUG_VERIFY_NOT_DIRTY(state) \
3471 VerifyNotDirty(state)
3473 #define DEBUG_VERIFY_NOT_DIRTY(state)
3477 NS_NewTextFrame(nsIPresShell
* aPresShell
, nsStyleContext
* aContext
)
3479 return new (aPresShell
) nsTextFrame(aContext
);
3483 NS_NewContinuingTextFrame(nsIPresShell
* aPresShell
, nsStyleContext
* aContext
)
3485 return new (aPresShell
) nsContinuingTextFrame(aContext
);
3488 nsTextFrame::~nsTextFrame()
3490 if (0 != (mState
& TEXT_BLINK_ON
))
3492 nsBlinkTimer::RemoveBlinkFrame(this);
3497 nsTextFrame::GetCursor(const nsPoint
& aPoint
,
3498 nsIFrame::Cursor
& aCursor
)
3500 FillCursorInformationFromStyle(GetStyleUserInterface(), aCursor
);
3501 if (NS_STYLE_CURSOR_AUTO
== aCursor
.mCursor
) {
3502 aCursor
.mCursor
= NS_STYLE_CURSOR_TEXT
;
3504 // If tabindex >= 0, use default cursor to indicate it's not selectable
3505 nsIFrame
*ancestorFrame
= this;
3506 while ((ancestorFrame
= ancestorFrame
->GetParent()) != nsnull
) {
3507 nsIContent
*ancestorContent
= ancestorFrame
->GetContent();
3508 if (ancestorContent
&& ancestorContent
->HasAttr(kNameSpaceID_None
, nsGkAtoms::tabindex
)) {
3509 nsAutoString tabIndexStr
;
3510 ancestorContent
->GetAttr(kNameSpaceID_None
, nsGkAtoms::tabindex
, tabIndexStr
);
3511 if (!tabIndexStr
.IsEmpty()) {
3512 PRInt32 rv
, tabIndexVal
= tabIndexStr
.ToInteger(&rv
);
3513 if (NS_SUCCEEDED(rv
) && tabIndexVal
>= 0) {
3514 aCursor
.mCursor
= NS_STYLE_CURSOR_DEFAULT
;
3526 nsTextFrame::GetLastInFlow() const
3528 nsTextFrame
* lastInFlow
= const_cast<nsTextFrame
*>(this);
3529 while (lastInFlow
->GetNextInFlow()) {
3530 lastInFlow
= static_cast<nsTextFrame
*>(lastInFlow
->GetNextInFlow());
3532 NS_POSTCONDITION(lastInFlow
, "illegal state in flow chain.");
3536 nsTextFrame::GetLastContinuation() const
3538 nsTextFrame
* lastInFlow
= const_cast<nsTextFrame
*>(this);
3539 while (lastInFlow
->mNextContinuation
) {
3540 lastInFlow
= static_cast<nsTextFrame
*>(lastInFlow
->mNextContinuation
);
3542 NS_POSTCONDITION(lastInFlow
, "illegal state in continuation chain.");
3547 nsTextFrame::ClearTextRun()
3549 // save textrun because ClearAllTextRunReferences will clear ours
3550 gfxTextRun
* textRun
= mTextRun
;
3555 UnhookTextRunFromFrames(textRun
);
3556 // see comments in BuildTextRunForFrames...
3557 // if (textRun->GetFlags() & gfxFontGroup::TEXT_IS_PERSISTENT) {
3558 // NS_ERROR("Shouldn't reach here for now...");
3559 // // the textrun's text may be referencing a DOM node that has changed,
3560 // // so we'd better kill this textrun now.
3561 // if (textRun->GetExpirationState()->IsTracked()) {
3562 // gTextRuns->RemoveFromCache(textRun);
3568 if (!(textRun
->GetFlags() & gfxTextRunWordCache::TEXT_IN_CACHE
)) {
3569 // Remove it now because it's not doing anything useful
3570 gTextRuns
->RemoveFromCache(textRun
);
3576 ClearTextRunsInFlowChain(nsTextFrame
* aFrame
)
3579 for (f
= aFrame
; f
; f
= static_cast<nsTextFrame
*>(f
->GetNextInFlow())) {
3585 nsTextFrame::CharacterDataChanged(nsPresContext
* aPresContext
,
3589 ClearTextRunsInFlowChain(this);
3591 nsTextFrame
* targetTextFrame
;
3592 PRInt32 nodeLength
= mContent
->GetText()->GetLength();
3595 targetTextFrame
= static_cast<nsTextFrame
*>(GetLastContinuation());
3596 targetTextFrame
->mState
&= ~TEXT_WHITESPACE_FLAGS
;
3598 // Mark all the continuation frames as dirty, and fix up content offsets to
3600 // Don't set NS_FRAME_IS_DIRTY on |this|, since we call FrameNeedsReflow
3602 nsTextFrame
* textFrame
= this;
3603 PRInt32 newLength
= nodeLength
;
3605 textFrame
->mState
&= ~TEXT_WHITESPACE_FLAGS
;
3606 // If the text node has shrunk, clip the frame contentlength as necessary
3607 if (textFrame
->mContentOffset
> newLength
) {
3608 textFrame
->mContentOffset
= newLength
;
3610 textFrame
= static_cast<nsTextFrame
*>(textFrame
->GetNextContinuation());
3614 textFrame
->mState
|= NS_FRAME_IS_DIRTY
;
3616 targetTextFrame
= this;
3619 // Ask the parent frame to reflow me.
3620 aPresContext
->GetPresShell()->FrameNeedsReflow(targetTextFrame
,
3621 nsIPresShell::eStyleChange
,
3628 nsTextFrame::DidSetStyleContext()
3634 class nsDisplayText
: public nsDisplayItem
{
3636 nsDisplayText(nsTextFrame
* aFrame
) : nsDisplayItem(aFrame
) {
3637 MOZ_COUNT_CTOR(nsDisplayText
);
3639 #ifdef NS_BUILD_REFCNT_LOGGING
3640 virtual ~nsDisplayText() {
3641 MOZ_COUNT_DTOR(nsDisplayText
);
3645 virtual nsRect
GetBounds(nsDisplayListBuilder
* aBuilder
) {
3646 return mFrame
->GetOverflowRect() + aBuilder
->ToReferenceFrame(mFrame
);
3648 virtual nsIFrame
* HitTest(nsDisplayListBuilder
* aBuilder
, nsPoint aPt
,
3649 HitTestState
* aState
) {
3650 return nsRect(aBuilder
->ToReferenceFrame(mFrame
), mFrame
->GetSize()).Contains(aPt
) ? mFrame
: nsnull
;
3652 virtual void Paint(nsDisplayListBuilder
* aBuilder
, nsIRenderingContext
* aCtx
,
3653 const nsRect
& aDirtyRect
);
3654 NS_DISPLAY_DECL_NAME("Text")
3658 nsDisplayText::Paint(nsDisplayListBuilder
* aBuilder
,
3659 nsIRenderingContext
* aCtx
, const nsRect
& aDirtyRect
) {
3660 static_cast<nsTextFrame
*>(mFrame
)->
3661 PaintText(aCtx
, aBuilder
->ToReferenceFrame(mFrame
), aDirtyRect
);
3665 nsTextFrame::BuildDisplayList(nsDisplayListBuilder
* aBuilder
,
3666 const nsRect
& aDirtyRect
,
3667 const nsDisplayListSet
& aLists
)
3669 if (!IsVisibleForPainting(aBuilder
))
3672 DO_GLOBAL_REFLOW_COUNT_DSP("nsTextFrame");
3674 if ((0 != (mState
& TEXT_BLINK_ON
)) && nsBlinkTimer::GetBlinkIsOff() &&
3675 PresContext()->IsDynamic())
3678 return aLists
.Content()->AppendNewToTop(new (aBuilder
) nsDisplayText(this));
3682 GetGeneratedContentOwner(nsIFrame
* aFrame
, PRBool
* aIsBefore
)
3684 *aIsBefore
= PR_FALSE
;
3685 while (aFrame
&& (aFrame
->GetStateBits() & NS_FRAME_GENERATED_CONTENT
)) {
3686 if (aFrame
->GetStyleContext()->GetPseudoType() == nsCSSPseudoElements::before
) {
3687 *aIsBefore
= PR_TRUE
;
3689 aFrame
= aFrame
->GetParent();
3695 nsTextFrame::GetSelectionDetails()
3697 const nsFrameSelection
* frameSelection
= GetConstFrameSelection();
3698 if (!(GetStateBits() & NS_FRAME_GENERATED_CONTENT
)) {
3699 SelectionDetails
* details
=
3700 frameSelection
->LookUpSelection(mContent
, GetContentOffset(),
3701 GetContentLength(), PR_FALSE
);
3702 SelectionDetails
* sd
;
3703 for (sd
= details
; sd
; sd
= sd
->mNext
) {
3704 sd
->mStart
+= mContentOffset
;
3705 sd
->mEnd
+= mContentOffset
;
3710 // Check if the beginning or end of the element is selected, depending on
3711 // whether we're :before content or :after content.
3713 nsIFrame
* owner
= GetGeneratedContentOwner(this, &isBefore
);
3714 if (!owner
|| !owner
->GetContent())
3717 SelectionDetails
* details
=
3718 frameSelection
->LookUpSelection(owner
->GetContent(),
3719 isBefore
? 0 : owner
->GetContent()->GetChildCount(), 0, PR_FALSE
);
3720 SelectionDetails
* sd
;
3721 for (sd
= details
; sd
; sd
= sd
->mNext
) {
3722 // The entire text is selected!
3723 sd
->mStart
= GetContentOffset();
3724 sd
->mEnd
= GetContentEnd();
3730 FillClippedRect(gfxContext
* aCtx
, nsPresContext
* aPresContext
,
3731 nscolor aColor
, const gfxRect
& aDirtyRect
, const gfxRect
& aRect
)
3733 gfxRect r
= aRect
.Intersect(aDirtyRect
);
3734 // For now, we need to put this in pixel coordinates
3735 PRInt32 app
= aPresContext
->AppUnitsPerDevPixel();
3738 aCtx
->Rectangle(gfxRect(r
.X() / app
, r
.Y() / app
,
3739 r
.Width() / app
, r
.Height() / app
), PR_TRUE
);
3740 aCtx
->SetColor(gfxRGBA(aColor
));
3744 nsTextFrame::TextDecorations
3745 nsTextFrame::GetTextDecorations(nsPresContext
* aPresContext
)
3747 TextDecorations decorations
;
3749 // Quirks mode text decoration are rendered by children; see bug 1777
3750 // In non-quirks mode, nsHTMLContainer::Paint and nsBlockFrame::Paint
3751 // does the painting of text decorations.
3752 if (eCompatibility_NavQuirks
!= aPresContext
->CompatibilityMode())
3755 PRBool useOverride
= PR_FALSE
;
3756 nscolor overrideColor
;
3758 // A mask of all possible decorations.
3759 PRUint8 decorMask
= NS_STYLE_TEXT_DECORATION_UNDERLINE
|
3760 NS_STYLE_TEXT_DECORATION_OVERLINE
|
3761 NS_STYLE_TEXT_DECORATION_LINE_THROUGH
;
3763 for (nsStyleContext
* context
= GetStyleContext();
3764 decorMask
&& context
&& context
->HasTextDecorations();
3765 context
= context
->GetParent()) {
3766 const nsStyleTextReset
* styleText
= context
->GetStyleTextReset();
3768 (NS_STYLE_TEXT_DECORATION_OVERRIDE_ALL
& styleText
->mTextDecoration
)) {
3769 // This handles the <a href="blah.html"><font color="green">La
3770 // la la</font></a> case. The link underline should be green.
3771 useOverride
= PR_TRUE
;
3772 overrideColor
= context
->GetStyleColor()->mColor
;
3775 PRUint8 useDecorations
= decorMask
& styleText
->mTextDecoration
;
3776 if (useDecorations
) {// a decoration defined here
3777 nscolor color
= context
->GetStyleColor()->mColor
;
3779 if (NS_STYLE_TEXT_DECORATION_UNDERLINE
& useDecorations
) {
3780 decorations
.mUnderColor
= useOverride
? overrideColor
: color
;
3781 decorMask
&= ~NS_STYLE_TEXT_DECORATION_UNDERLINE
;
3782 decorations
.mDecorations
|= NS_STYLE_TEXT_DECORATION_UNDERLINE
;
3784 if (NS_STYLE_TEXT_DECORATION_OVERLINE
& useDecorations
) {
3785 decorations
.mOverColor
= useOverride
? overrideColor
: color
;
3786 decorMask
&= ~NS_STYLE_TEXT_DECORATION_OVERLINE
;
3787 decorations
.mDecorations
|= NS_STYLE_TEXT_DECORATION_OVERLINE
;
3789 if (NS_STYLE_TEXT_DECORATION_LINE_THROUGH
& useDecorations
) {
3790 decorations
.mStrikeColor
= useOverride
? overrideColor
: color
;
3791 decorMask
&= ~NS_STYLE_TEXT_DECORATION_LINE_THROUGH
;
3792 decorations
.mDecorations
|= NS_STYLE_TEXT_DECORATION_LINE_THROUGH
;
3801 nsTextFrame::UnionTextDecorationOverflow(nsPresContext
* aPresContext
,
3802 PropertyProvider
& aProvider
,
3803 nsRect
* aOverflowRect
)
3805 // Text-shadow overflows
3806 nsRect shadowRect
= nsLayoutUtils::GetTextShadowRectsUnion(*aOverflowRect
, this);
3807 aOverflowRect
->UnionRect(*aOverflowRect
, shadowRect
);
3809 if (IsFloatingFirstLetterChild()) {
3810 // The underline/overline drawable area must be contained in the overflow
3811 // rect when this is in floating first letter frame at *both* modes.
3812 nscoord fontAscent
, fontHeight
;
3813 nsIFontMetrics
* fm
= aProvider
.GetFontMetrics();
3814 fm
->GetMaxAscent(fontAscent
);
3815 fm
->GetMaxHeight(fontHeight
);
3816 nsRect
fontRect(0, mAscent
- fontAscent
, GetSize().width
, fontHeight
);
3817 aOverflowRect
->UnionRect(*aOverflowRect
, fontRect
);
3820 // When this frame is not selected, the text-decoration area must be in
3823 if (!(GetStateBits() & NS_FRAME_SELECTED_CONTENT
) ||
3824 !HasSelectionOverflowingDecorations(aPresContext
, &ratio
))
3827 nsLineLayout::CombineTextDecorations(aPresContext
,
3828 NS_STYLE_TEXT_DECORATION_UNDERLINE
,
3829 this, *aOverflowRect
, mAscent
, ratio
);
3830 AddStateBits(TEXT_SELECTION_UNDERLINE_OVERFLOWED
);
3834 nsTextFrame::PaintTextDecorations(gfxContext
* aCtx
, const gfxRect
& aDirtyRect
,
3835 const gfxPoint
& aFramePt
,
3836 const gfxPoint
& aTextBaselinePt
,
3837 nsTextPaintStyle
& aTextPaintStyle
,
3838 PropertyProvider
& aProvider
,
3839 const nscolor
& aOverrideColor
)
3841 TextDecorations decorations
=
3842 GetTextDecorations(aTextPaintStyle
.PresContext());
3843 if (!decorations
.HasDecorationlines())
3846 gfxFont
* firstFont
= aProvider
.GetFontGroup()->GetFontAt(0);
3849 const gfxFont::Metrics
& fontMetrics
= firstFont
->GetMetrics();
3850 gfxFloat app
= aTextPaintStyle
.PresContext()->AppUnitsPerDevPixel();
3852 // XXX aFramePt is in AppUnits, shouldn't it be nsFloatPoint?
3853 gfxPoint
pt(aFramePt
.x
/ app
, (aTextBaselinePt
.y
- mAscent
) / app
);
3854 gfxSize
size(GetRect().width
/ app
, 0);
3855 gfxFloat ascent
= gfxFloat(mAscent
) / app
;
3858 if (decorations
.HasOverline()) {
3859 lineColor
= aOverrideColor
? aOverrideColor
: decorations
.mOverColor
;
3860 size
.height
= fontMetrics
.underlineSize
;
3861 nsCSSRendering::PaintDecorationLine(
3862 aCtx
, lineColor
, pt
, size
, ascent
, fontMetrics
.maxAscent
,
3863 NS_STYLE_TEXT_DECORATION_OVERLINE
, NS_STYLE_BORDER_STYLE_SOLID
);
3865 if (decorations
.HasUnderline()) {
3866 lineColor
= aOverrideColor
? aOverrideColor
: decorations
.mUnderColor
;
3867 size
.height
= fontMetrics
.underlineSize
;
3868 gfxFloat offset
= aProvider
.GetFontGroup()->GetUnderlineOffset();
3869 nsCSSRendering::PaintDecorationLine(
3870 aCtx
, lineColor
, pt
, size
, ascent
, offset
,
3871 NS_STYLE_TEXT_DECORATION_UNDERLINE
, NS_STYLE_BORDER_STYLE_SOLID
);
3873 if (decorations
.HasStrikeout()) {
3874 lineColor
= aOverrideColor
? aOverrideColor
: decorations
.mStrikeColor
;
3875 size
.height
= fontMetrics
.strikeoutSize
;
3876 gfxFloat offset
= fontMetrics
.strikeoutOffset
;
3877 nsCSSRendering::PaintDecorationLine(
3878 aCtx
, lineColor
, pt
, size
, ascent
, offset
,
3879 NS_STYLE_TEXT_DECORATION_LINE_THROUGH
, NS_STYLE_BORDER_STYLE_SOLID
);
3883 // Make sure this stays in sync with DrawSelectionDecorations below
3884 static const SelectionType SelectionTypesWithDecorations
=
3885 nsISelectionController::SELECTION_SPELLCHECK
|
3886 nsISelectionController::SELECTION_IME_RAWINPUT
|
3887 nsISelectionController::SELECTION_IME_SELECTEDRAWTEXT
|
3888 nsISelectionController::SELECTION_IME_CONVERTEDTEXT
|
3889 nsISelectionController::SELECTION_IME_SELECTEDCONVERTEDTEXT
;
3891 static void DrawIMEUnderline(gfxContext
* aContext
, PRInt32 aIndex
,
3892 nsTextPaintStyle
& aTextPaintStyle
, const gfxPoint
& aPt
, gfxFloat aWidth
,
3893 gfxFloat aAscent
, gfxFloat aSize
, gfxFloat aOffset
)
3898 if (!aTextPaintStyle
.GetIMEUnderline(aIndex
, &color
, &relativeSize
, &style
))
3901 gfxFloat actualSize
= relativeSize
* aSize
;
3902 gfxFloat width
= PR_MAX(0, aWidth
- 2.0 * aSize
);
3903 gfxPoint
pt(aPt
.x
+ 1.0, aPt
.y
);
3904 nsCSSRendering::PaintDecorationLine(
3905 aContext
, color
, pt
, gfxSize(width
, actualSize
), aAscent
, aOffset
,
3906 NS_STYLE_TEXT_DECORATION_UNDERLINE
, style
);
3910 * This, plus SelectionTypesWithDecorations, encapsulates all knowledge about
3911 * drawing text decoration for selections.
3913 static void DrawSelectionDecorations(gfxContext
* aContext
, SelectionType aType
,
3914 nsTextPaintStyle
& aTextPaintStyle
, const gfxPoint
& aPt
, gfxFloat aWidth
,
3915 gfxFloat aAscent
, const gfxFont::Metrics
& aFontMetrics
)
3917 gfxSize
size(aWidth
, aFontMetrics
.underlineSize
);
3920 case nsISelectionController::SELECTION_SPELLCHECK
: {
3921 nsCSSRendering::PaintDecorationLine(
3922 aContext
, NS_RGB(255,0,0),
3923 aPt
, size
, aAscent
, aFontMetrics
.underlineOffset
,
3924 NS_STYLE_TEXT_DECORATION_UNDERLINE
, NS_STYLE_BORDER_STYLE_DOTTED
);
3928 case nsISelectionController::SELECTION_IME_RAWINPUT
:
3929 DrawIMEUnderline(aContext
, nsTextPaintStyle::eIndexRawInput
,
3930 aTextPaintStyle
, aPt
, aWidth
, aAscent
, size
.height
,
3931 aFontMetrics
.underlineOffset
);
3933 case nsISelectionController::SELECTION_IME_SELECTEDRAWTEXT
:
3934 DrawIMEUnderline(aContext
, nsTextPaintStyle::eIndexSelRawText
,
3935 aTextPaintStyle
, aPt
, aWidth
, aAscent
, size
.height
,
3936 aFontMetrics
.underlineOffset
);
3938 case nsISelectionController::SELECTION_IME_CONVERTEDTEXT
:
3939 DrawIMEUnderline(aContext
, nsTextPaintStyle::eIndexConvText
,
3940 aTextPaintStyle
, aPt
, aWidth
, aAscent
, size
.height
,
3941 aFontMetrics
.underlineOffset
);
3943 case nsISelectionController::SELECTION_IME_SELECTEDCONVERTEDTEXT
:
3944 DrawIMEUnderline(aContext
, nsTextPaintStyle::eIndexSelConvText
,
3945 aTextPaintStyle
, aPt
, aWidth
, aAscent
, size
.height
,
3946 aFontMetrics
.underlineOffset
);
3950 NS_WARNING("Requested selection decorations when there aren't any");
3956 * This function encapsulates all knowledge of how selections affect foreground
3957 * and background colors.
3958 * @return true if the selection affects colors, false otherwise
3959 * @param aForeground the foreground color to use
3960 * @param aBackground the background color to use, or RGBA(0,0,0,0) if no
3961 * background should be painted
3963 static PRBool
GetSelectionTextColors(SelectionType aType
, nsTextPaintStyle
& aTextPaintStyle
,
3964 nscolor
* aForeground
, nscolor
* aBackground
)
3967 case nsISelectionController::SELECTION_NORMAL
:
3968 return aTextPaintStyle
.GetSelectionColors(aForeground
, aBackground
);
3969 case nsISelectionController::SELECTION_FIND
:
3970 aTextPaintStyle
.GetHighlightColors(aForeground
, aBackground
);
3972 case nsISelectionController::SELECTION_IME_RAWINPUT
:
3973 aTextPaintStyle
.GetIMESelectionColors(nsTextPaintStyle::eIndexRawInput
,
3974 aForeground
, aBackground
);
3976 case nsISelectionController::SELECTION_IME_SELECTEDRAWTEXT
:
3977 aTextPaintStyle
.GetIMESelectionColors(nsTextPaintStyle::eIndexSelRawText
,
3978 aForeground
, aBackground
);
3980 case nsISelectionController::SELECTION_IME_CONVERTEDTEXT
:
3981 aTextPaintStyle
.GetIMESelectionColors(nsTextPaintStyle::eIndexConvText
,
3982 aForeground
, aBackground
);
3984 case nsISelectionController::SELECTION_IME_SELECTEDCONVERTEDTEXT
:
3985 aTextPaintStyle
.GetIMESelectionColors(nsTextPaintStyle::eIndexSelConvText
,
3986 aForeground
, aBackground
);
3990 *aForeground
= aTextPaintStyle
.GetTextColor();
3991 *aBackground
= NS_RGBA(0,0,0,0);
3997 * This class lets us iterate over chunks of text in a uniform selection state,
3998 * observing cluster boundaries, in content order, maintaining the current
3999 * x-offset as we go, and telling whether the text chunk has a hyphen after
4000 * it or not. The caller is responsible for actually computing the advance
4001 * width of each chunk.
4003 class SelectionIterator
{
4006 * aStart and aLength are in the original string. aSelectionBuffer is
4007 * according to the original string.
4009 SelectionIterator(SelectionType
* aSelectionBuffer
, PRInt32 aStart
,
4010 PRInt32 aLength
, PropertyProvider
& aProvider
,
4011 gfxTextRun
* aTextRun
);
4014 * Returns the next segment of uniformly selected (or not) text.
4015 * @param aXOffset the offset from the origin of the frame to the start
4016 * of the text (the left baseline origin for LTR, the right baseline origin
4018 * @param aOffset the transformed string offset of the text for this segment
4019 * @param aLength the transformed string length of the text for this segment
4020 * @param aHyphenWidth if a hyphen is to be rendered after the text, the
4021 * width of the hyphen, otherwise zero
4022 * @param aType the selection type for this segment
4023 * @return false if there are no more segments
4025 PRBool
GetNextSegment(gfxFloat
* aXOffset
, PRUint32
* aOffset
, PRUint32
* aLength
,
4026 gfxFloat
* aHyphenWidth
, SelectionType
* aType
);
4027 void UpdateWithAdvance(gfxFloat aAdvance
) {
4028 mXOffset
+= aAdvance
*mTextRun
->GetDirection();
4032 SelectionType
* mSelectionBuffer
;
4033 PropertyProvider
& mProvider
;
4034 gfxTextRun
* mTextRun
;
4035 gfxSkipCharsIterator mIterator
;
4036 PRInt32 mOriginalStart
;
4037 PRInt32 mOriginalEnd
;
4041 SelectionIterator::SelectionIterator(SelectionType
* aSelectionBuffer
,
4042 PRInt32 aStart
, PRInt32 aLength
, PropertyProvider
& aProvider
,
4043 gfxTextRun
* aTextRun
)
4044 : mSelectionBuffer(aSelectionBuffer
), mProvider(aProvider
),
4045 mTextRun(aTextRun
), mIterator(aProvider
.GetStart()),
4046 mOriginalStart(aStart
), mOriginalEnd(aStart
+ aLength
),
4047 mXOffset(mTextRun
->IsRightToLeft() ? aProvider
.GetFrame()->GetSize().width
: 0)
4049 mIterator
.SetOriginalOffset(aStart
);
4052 PRBool
SelectionIterator::GetNextSegment(gfxFloat
* aXOffset
,
4053 PRUint32
* aOffset
, PRUint32
* aLength
, gfxFloat
* aHyphenWidth
, SelectionType
* aType
)
4055 if (mIterator
.GetOriginalOffset() >= mOriginalEnd
)
4058 // save offset into transformed string now
4059 PRUint32 runOffset
= mIterator
.GetSkippedOffset();
4061 PRInt32 index
= mIterator
.GetOriginalOffset() - mOriginalStart
;
4062 SelectionType type
= mSelectionBuffer
[index
];
4063 for (++index
; mOriginalStart
+ index
< mOriginalEnd
; ++index
) {
4064 if (mSelectionBuffer
[index
] != type
)
4067 mIterator
.SetOriginalOffset(index
+ mOriginalStart
);
4069 // Advance to the next cluster boundary
4070 while (mIterator
.GetOriginalOffset() < mOriginalEnd
&&
4071 !mIterator
.IsOriginalCharSkipped() &&
4072 !mTextRun
->IsClusterStart(mIterator
.GetSkippedOffset())) {
4073 mIterator
.AdvanceOriginal(1);
4076 PRBool haveHyphenBreak
=
4077 (mProvider
.GetFrame()->GetStateBits() & TEXT_HYPHEN_BREAK
) != 0;
4078 *aOffset
= runOffset
;
4079 *aLength
= mIterator
.GetSkippedOffset() - runOffset
;
4080 *aXOffset
= mXOffset
;
4082 if (mIterator
.GetOriginalOffset() == mOriginalEnd
&& haveHyphenBreak
) {
4083 *aHyphenWidth
= mProvider
.GetHyphenWidth();
4090 AddHyphenToMetrics(nsTextFrame
* aTextFrame
, gfxTextRun
* aBaseTextRun
,
4091 gfxTextRun::Metrics
* aMetrics
, PRBool aTightBoundingBox
,
4092 gfxContext
* aContext
)
4094 // Fix up metrics to include hyphen
4095 gfxTextRunCache::AutoTextRun
hyphenTextRun(
4096 GetHyphenTextRun(aBaseTextRun
, aContext
, aTextFrame
));
4097 if (!hyphenTextRun
.get())
4100 gfxTextRun::Metrics hyphenMetrics
=
4101 hyphenTextRun
->MeasureText(0, hyphenTextRun
->GetLength(), aTightBoundingBox
, aContext
, nsnull
);
4102 aMetrics
->CombineWith(hyphenMetrics
, aBaseTextRun
->IsRightToLeft());
4106 nsTextFrame::PaintOneShadow(PRUint32 aOffset
, PRUint32 aLength
,
4107 nsCSSShadowItem
* aShadowDetails
,
4108 PropertyProvider
* aProvider
, const gfxRect
& aDirtyRect
,
4109 const gfxPoint
& aFramePt
, const gfxPoint
& aTextBaselinePt
,
4110 gfxContext
* aCtx
, const nscolor
& aForegroundColor
)
4112 gfxPoint
shadowOffset(aShadowDetails
->mXOffset
, aShadowDetails
->mYOffset
);
4113 nscoord blurRadius
= PR_MAX(aShadowDetails
->mRadius
, 0);
4115 gfxTextRun::Metrics shadowMetrics
=
4116 mTextRun
->MeasureText(aOffset
, aLength
, PR_FALSE
,
4118 if (GetStateBits() & TEXT_HYPHEN_BREAK
) {
4119 AddHyphenToMetrics(this, mTextRun
, &shadowMetrics
, PR_FALSE
, aCtx
);
4122 // This rect is the box which is equivalent to where the shadow will be painted.
4123 // The origin of mBoundingBox is the text baseline left, so we must translate it by
4124 // that much in order to make the origin the top-left corner of the text bounding box.
4125 gfxRect shadowRect
= shadowMetrics
.mBoundingBox
+
4126 gfxPoint(aFramePt
.x
, aTextBaselinePt
.y
) + shadowOffset
;
4128 nsContextBoxBlur contextBoxBlur
;
4129 gfxContext
* shadowContext
= contextBoxBlur
.Init(shadowRect
, blurRadius
,
4130 PresContext()->AppUnitsPerDevPixel(),
4135 nscolor shadowColor
;
4136 if (aShadowDetails
->mHasColor
)
4137 shadowColor
= aShadowDetails
->mColor
;
4139 shadowColor
= aForegroundColor
;
4143 aCtx
->SetColor(gfxRGBA(shadowColor
));
4145 // Draw the text onto our alpha-only surface to capture the alpha values.
4146 // Remember that the box blur context has a device offset on it, so we don't need to
4147 // translate any coordinates to fit on the surface.
4148 gfxFloat advanceWidth
;
4149 DrawText(shadowContext
,
4150 aTextBaselinePt
+ shadowOffset
,
4151 aOffset
, aLength
, &aDirtyRect
, aProvider
, advanceWidth
,
4152 (GetStateBits() & TEXT_HYPHEN_BREAK
) != 0);
4154 // This will only have an effect in quirks mode. Standards mode text-decoration shadow painting
4155 // is handled in nsHTMLContainerFrame.cpp, so you must remember to consider that if you change
4156 // any code behaviour here.
4157 nsTextPaintStyle
textPaintStyle(this);
4158 PaintTextDecorations(shadowContext
, aDirtyRect
, aFramePt
+ shadowOffset
,
4159 aTextBaselinePt
+ shadowOffset
,
4160 textPaintStyle
, *aProvider
, shadowColor
);
4162 contextBoxBlur
.DoPaint();
4166 // Paints selection backgrounds and text in the correct colors. Also computes
4167 // aAllTypes, the union of all selection types that are applying to this text.
4169 nsTextFrame::PaintTextWithSelectionColors(gfxContext
* aCtx
,
4170 const gfxPoint
& aFramePt
,
4171 const gfxPoint
& aTextBaselinePt
, const gfxRect
& aDirtyRect
,
4172 PropertyProvider
& aProvider
, nsTextPaintStyle
& aTextPaintStyle
,
4173 SelectionDetails
* aDetails
, SelectionType
* aAllTypes
)
4175 PRInt32 contentOffset
= aProvider
.GetStart().GetOriginalOffset();
4176 PRInt32 contentLength
= aProvider
.GetOriginalLength();
4178 // Figure out which selections control the colors to use for each character.
4179 nsAutoTArray
<SelectionType
,BIG_TEXT_NODE_SIZE
> prevailingSelectionsBuffer
;
4180 if (!prevailingSelectionsBuffer
.AppendElements(contentLength
))
4182 SelectionType
* prevailingSelections
= prevailingSelectionsBuffer
.Elements();
4184 SelectionType allTypes
= 0;
4185 for (i
= 0; i
< contentLength
; ++i
) {
4186 prevailingSelections
[i
] = nsISelectionController::SELECTION_NONE
;
4189 SelectionDetails
*sdptr
= aDetails
;
4190 PRBool anyBackgrounds
= PR_FALSE
;
4192 PRInt32 start
= PR_MAX(0, sdptr
->mStart
- contentOffset
);
4193 PRInt32 end
= PR_MIN(contentLength
, sdptr
->mEnd
- contentOffset
);
4194 SelectionType type
= sdptr
->mType
;
4197 // Ignore selections that don't set colors
4198 nscolor foreground
, background
;
4199 if (GetSelectionTextColors(type
, aTextPaintStyle
, &foreground
, &background
)) {
4200 if (NS_GET_A(background
) > 0) {
4201 anyBackgrounds
= PR_TRUE
;
4203 for (i
= start
; i
< end
; ++i
) {
4204 PRInt16 currentPrevailingSelection
= prevailingSelections
[i
];
4205 // Favour normal selection over IME selections
4206 if (currentPrevailingSelection
== nsISelectionController::SELECTION_NONE
||
4207 type
< currentPrevailingSelection
) {
4208 prevailingSelections
[i
] = type
;
4213 sdptr
= sdptr
->mNext
;
4215 *aAllTypes
= allTypes
;
4217 gfxFloat xOffset
, hyphenWidth
;
4218 PRUint32 offset
, length
; // in transformed string
4220 // Draw background colors
4221 if (anyBackgrounds
) {
4222 SelectionIterator
iterator(prevailingSelections
, contentOffset
, contentLength
,
4223 aProvider
, mTextRun
);
4224 while (iterator
.GetNextSegment(&xOffset
, &offset
, &length
, &hyphenWidth
, &type
)) {
4225 nscolor foreground
, background
;
4226 GetSelectionTextColors(type
, aTextPaintStyle
, &foreground
, &background
);
4227 // Draw background color
4228 gfxFloat advance
= hyphenWidth
+
4229 mTextRun
->GetAdvanceWidth(offset
, length
, &aProvider
);
4230 if (NS_GET_A(background
) > 0) {
4231 gfxFloat x
= xOffset
- (mTextRun
->IsRightToLeft() ? advance
: 0);
4232 FillClippedRect(aCtx
, aTextPaintStyle
.PresContext(),
4233 background
, aDirtyRect
,
4234 gfxRect(aFramePt
.x
+ x
, aFramePt
.y
, advance
, GetSize().height
));
4236 iterator
.UpdateWithAdvance(advance
);
4241 SelectionIterator
iterator(prevailingSelections
, contentOffset
, contentLength
,
4242 aProvider
, mTextRun
);
4243 while (iterator
.GetNextSegment(&xOffset
, &offset
, &length
, &hyphenWidth
, &type
)) {
4244 nscolor foreground
, background
;
4245 GetSelectionTextColors(type
, aTextPaintStyle
, &foreground
, &background
);
4246 // Draw text segment
4247 aCtx
->SetColor(gfxRGBA(foreground
));
4250 DrawText(aCtx
, gfxPoint(aFramePt
.x
+ xOffset
, aTextBaselinePt
.y
),
4251 offset
, length
, &aDirtyRect
, &aProvider
,
4252 advance
, hyphenWidth
> 0);
4254 advance
+= hyphenWidth
;
4256 iterator
.UpdateWithAdvance(advance
);
4261 nsTextFrame::PaintTextSelectionDecorations(gfxContext
* aCtx
,
4262 const gfxPoint
& aFramePt
,
4263 const gfxPoint
& aTextBaselinePt
, const gfxRect
& aDirtyRect
,
4264 PropertyProvider
& aProvider
, nsTextPaintStyle
& aTextPaintStyle
,
4265 SelectionDetails
* aDetails
, SelectionType aSelectionType
)
4267 PRInt32 contentOffset
= aProvider
.GetStart().GetOriginalOffset();
4268 PRInt32 contentLength
= aProvider
.GetOriginalLength();
4270 // Figure out which characters will be decorated for this selection. Here
4271 // we just fill the buffer with either SELECTION_NONE or aSelectionType.
4272 nsAutoTArray
<SelectionType
,BIG_TEXT_NODE_SIZE
> selectedCharsBuffer
;
4273 if (!selectedCharsBuffer
.AppendElements(contentLength
))
4275 SelectionType
* selectedChars
= selectedCharsBuffer
.Elements();
4277 for (i
= 0; i
< contentLength
; ++i
) {
4278 selectedChars
[i
] = nsISelectionController::SELECTION_NONE
;
4281 SelectionDetails
*sdptr
= aDetails
;
4283 if (sdptr
->mType
== aSelectionType
) {
4284 PRInt32 start
= PR_MAX(0, sdptr
->mStart
- contentOffset
);
4285 PRInt32 end
= PR_MIN(contentLength
, sdptr
->mEnd
- contentOffset
);
4286 for (i
= start
; i
< end
; ++i
) {
4287 selectedChars
[i
] = aSelectionType
;
4290 sdptr
= sdptr
->mNext
;
4293 gfxFont
* firstFont
= aProvider
.GetFontGroup()->GetFontAt(0);
4296 gfxFont::Metrics
decorationMetrics(firstFont
->GetMetrics());
4297 decorationMetrics
.underlineOffset
=
4298 aProvider
.GetFontGroup()->GetUnderlineOffset();
4300 SelectionIterator
iterator(selectedChars
, contentOffset
, contentLength
,
4301 aProvider
, mTextRun
);
4302 gfxFloat xOffset
, hyphenWidth
;
4303 PRUint32 offset
, length
;
4304 PRInt32 app
= aTextPaintStyle
.PresContext()->AppUnitsPerDevPixel();
4305 // XXX aTextBaselinePt is in AppUnits, shouldn't it be nsFloatPoint?
4306 gfxPoint
pt(0.0, (aTextBaselinePt
.y
- mAscent
) / app
);
4308 while (iterator
.GetNextSegment(&xOffset
, &offset
, &length
, &hyphenWidth
, &type
)) {
4309 gfxFloat advance
= hyphenWidth
+
4310 mTextRun
->GetAdvanceWidth(offset
, length
, &aProvider
);
4311 if (type
== aSelectionType
) {
4312 pt
.x
= (aFramePt
.x
+ xOffset
-
4313 (mTextRun
->IsRightToLeft() ? advance
: 0)) / app
;
4314 gfxFloat width
= PR_ABS(advance
) / app
;
4315 DrawSelectionDecorations(aCtx
, aSelectionType
, aTextPaintStyle
,
4316 pt
, width
, mAscent
/ app
, decorationMetrics
);
4318 iterator
.UpdateWithAdvance(advance
);
4323 nsTextFrame::PaintTextWithSelection(gfxContext
* aCtx
,
4324 const gfxPoint
& aFramePt
,
4325 const gfxPoint
& aTextBaselinePt
, const gfxRect
& aDirtyRect
,
4326 PropertyProvider
& aProvider
, nsTextPaintStyle
& aTextPaintStyle
)
4328 SelectionDetails
* details
= GetSelectionDetails();
4332 SelectionType allTypes
;
4333 PaintTextWithSelectionColors(aCtx
, aFramePt
, aTextBaselinePt
, aDirtyRect
,
4334 aProvider
, aTextPaintStyle
, details
, &allTypes
);
4335 PaintTextDecorations(aCtx
, aDirtyRect
, aFramePt
, aTextBaselinePt
,
4336 aTextPaintStyle
, aProvider
);
4338 // Iterate through just the selection types that paint decorations and
4339 // paint decorations for any that actually occur in this frame. Paint
4340 // higher-numbered selection types below lower-numered ones on the
4341 // general principal that lower-numbered selections are higher priority.
4342 allTypes
&= SelectionTypesWithDecorations
;
4343 for (i
= nsISelectionController::NUM_SELECTIONTYPES
- 1; i
>= 1; --i
) {
4344 SelectionType type
= 1 << (i
- 1);
4345 if (allTypes
& type
) {
4346 // There is some selection of this type. Try to paint its decorations
4347 // (there might not be any for this type but that's OK,
4348 // PaintTextSelectionDecorations will exit early).
4349 PaintTextSelectionDecorations(aCtx
, aFramePt
, aTextBaselinePt
, aDirtyRect
,
4350 aProvider
, aTextPaintStyle
, details
, type
);
4354 DestroySelectionDetails(details
);
4359 ComputeTransformedLength(PropertyProvider
& aProvider
)
4361 gfxSkipCharsIterator
iter(aProvider
.GetStart());
4362 PRUint32 start
= iter
.GetSkippedOffset();
4363 iter
.AdvanceOriginal(aProvider
.GetOriginalLength());
4364 return iter
.GetSkippedOffset() - start
;
4368 nsTextFrame::GetSnappedBaselineY(gfxContext
* aContext
, gfxFloat aY
)
4370 gfxFloat appUnitsPerDevUnit
= mTextRun
->GetAppUnitsPerDevUnit();
4371 gfxFloat baseline
= aY
+ mAscent
;
4372 gfxRect
putativeRect(0, baseline
/appUnitsPerDevUnit
, 1, 1);
4373 if (!aContext
->UserToDevicePixelSnapped(putativeRect
))
4375 return aContext
->DeviceToUser(putativeRect
.pos
).y
*appUnitsPerDevUnit
;
4379 nsTextFrame::PaintText(nsIRenderingContext
* aRenderingContext
, nsPoint aPt
,
4380 const nsRect
& aDirtyRect
)
4382 // Don't pass in aRenderingContext here, because we need a *reference*
4383 // context and aRenderingContext might have some transform in it
4384 // XXX get the block and line passed to us somehow! This is slow!
4385 gfxSkipCharsIterator iter
= EnsureTextRun();
4389 nsTextPaintStyle
textPaintStyle(this);
4390 PropertyProvider
provider(this, iter
);
4391 // Trim trailing whitespace
4392 provider
.InitializeForDisplay(PR_TRUE
);
4394 gfxContext
* ctx
= aRenderingContext
->ThebesContext();
4396 gfxPoint
framePt(aPt
.x
, aPt
.y
);
4397 gfxPoint
textBaselinePt(
4398 mTextRun
->IsRightToLeft() ? gfxFloat(aPt
.x
+ GetSize().width
) : framePt
.x
,
4399 GetSnappedBaselineY(ctx
, aPt
.y
));
4401 gfxRect
dirtyRect(aDirtyRect
.x
, aDirtyRect
.y
,
4402 aDirtyRect
.width
, aDirtyRect
.height
);
4404 gfxFloat advanceWidth
;
4405 gfxRGBA foregroundColor
= gfxRGBA(textPaintStyle
.GetTextColor());
4407 // Paint the text shadow before doing any foreground stuff
4408 const nsStyleText
* textStyle
= GetStyleText();
4409 if (textStyle
->mTextShadow
) {
4410 // Text shadow happens with the last value being painted at the back,
4411 // ie. it is painted first.
4412 for (PRUint32 i
= textStyle
->mTextShadow
->Length(); i
> 0; --i
) {
4413 PaintOneShadow(provider
.GetStart().GetSkippedOffset(),
4414 ComputeTransformedLength(provider
),
4415 textStyle
->mTextShadow
->ShadowAt(i
- 1), &provider
,
4416 dirtyRect
, framePt
, textBaselinePt
, ctx
,
4417 textPaintStyle
.GetTextColor());
4421 // Fork off to the (slower) paint-with-selection path if necessary.
4422 if (GetNonGeneratedAncestor(this)->GetStateBits() & NS_FRAME_SELECTED_CONTENT
) {
4423 if (PaintTextWithSelection(ctx
, framePt
, textBaselinePt
,
4424 dirtyRect
, provider
, textPaintStyle
))
4428 ctx
->SetColor(foregroundColor
);
4430 DrawText(ctx
, textBaselinePt
, provider
.GetStart().GetSkippedOffset(),
4431 ComputeTransformedLength(provider
), &dirtyRect
,
4432 &provider
, advanceWidth
,
4433 (GetStateBits() & TEXT_HYPHEN_BREAK
) != 0);
4434 PaintTextDecorations(ctx
, dirtyRect
, framePt
, textBaselinePt
,
4435 textPaintStyle
, provider
);
4439 nsTextFrame::DrawText(gfxContext
* aCtx
, const gfxPoint
& aTextBaselinePt
,
4440 PRUint32 aOffset
, PRUint32 aLength
,
4441 const gfxRect
* aDirtyRect
, PropertyProvider
* aProvider
,
4442 gfxFloat
& aAdvanceWidth
, PRBool aDrawSoftHyphen
)
4444 // Paint the text and soft-hyphen (if any) onto the given graphics context
4445 mTextRun
->Draw(aCtx
, aTextBaselinePt
, aOffset
, aLength
,
4446 aDirtyRect
, aProvider
, &aAdvanceWidth
);
4448 if (aDrawSoftHyphen
) {
4449 // Don't use ctx as the context, because we need a reference context here,
4450 // ctx may be transformed.
4451 gfxTextRunCache::AutoTextRun
hyphenTextRun(GetHyphenTextRun(mTextRun
, nsnull
, this));
4452 if (hyphenTextRun
.get()) {
4453 // For right-to-left text runs, the soft-hyphen is positioned at the left
4454 // of the text, minus its own width
4455 gfxFloat hyphenBaselineX
= aTextBaselinePt
.x
+ mTextRun
->GetDirection() * aAdvanceWidth
-
4456 (mTextRun
->IsRightToLeft() ? hyphenTextRun
->GetAdvanceWidth(0, hyphenTextRun
->GetLength(), nsnull
) : 0);
4457 hyphenTextRun
->Draw(aCtx
, gfxPoint(hyphenBaselineX
, aTextBaselinePt
.y
),
4458 0, hyphenTextRun
->GetLength(), aDirtyRect
, nsnull
, nsnull
);
4464 nsTextFrame::GetSelectionStatus(PRInt16
* aSelectionFlags
)
4466 // get the selection controller
4467 nsCOMPtr
<nsISelectionController
> selectionController
;
4468 nsresult rv
= GetSelectionController(PresContext(),
4469 getter_AddRefs(selectionController
));
4470 if (NS_FAILED(rv
) || !selectionController
)
4471 return nsISelectionController::SELECTION_OFF
;
4473 selectionController
->GetSelectionFlags(aSelectionFlags
);
4475 PRInt16 selectionValue
;
4476 selectionController
->GetDisplaySelection(&selectionValue
);
4478 return selectionValue
;
4482 nsTextFrame::IsVisibleInSelection(nsISelection
* aSelection
)
4484 // Check the quick way first
4485 PRBool isSelected
= (mState
& NS_FRAME_SELECTED_CONTENT
) == NS_FRAME_SELECTED_CONTENT
;
4489 SelectionDetails
* details
= GetSelectionDetails();
4490 PRBool found
= PR_FALSE
;
4492 // where are the selection points "really"
4493 SelectionDetails
*sdptr
= details
;
4495 if (sdptr
->mEnd
> GetContentOffset() &&
4496 sdptr
->mStart
< GetContentEnd() &&
4497 sdptr
->mType
== nsISelectionController::SELECTION_NORMAL
) {
4501 sdptr
= sdptr
->mNext
;
4503 DestroySelectionDetails(details
);
4509 * Compute the longest prefix of text whose width is <= aWidth. Return
4510 * the length of the prefix. Also returns the width of the prefix in aFitWidth.
4513 CountCharsFit(gfxTextRun
* aTextRun
, PRUint32 aStart
, PRUint32 aLength
,
4514 gfxFloat aWidth
, PropertyProvider
* aProvider
,
4515 gfxFloat
* aFitWidth
)
4520 for (i
= 1; i
<= aLength
; ++i
) {
4521 if (i
== aLength
|| aTextRun
->IsClusterStart(aStart
+ i
)) {
4522 gfxFloat nextWidth
= width
+
4523 aTextRun
->GetAdvanceWidth(aStart
+ last
, i
- last
, aProvider
);
4524 if (nextWidth
> aWidth
)
4534 nsIFrame::ContentOffsets
4535 nsTextFrame::CalcContentOffsetsFromFramePoint(nsPoint aPoint
) {
4536 ContentOffsets offsets
;
4538 gfxSkipCharsIterator iter
= EnsureTextRun();
4542 PropertyProvider
provider(this, iter
);
4543 // Trim leading but not trailing whitespace if possible
4544 provider
.InitializeForDisplay(PR_FALSE
);
4545 gfxFloat width
= mTextRun
->IsRightToLeft() ? mRect
.width
- aPoint
.x
: aPoint
.x
;
4547 PRUint32 skippedLength
= ComputeTransformedLength(provider
);
4549 PRUint32 charsFit
= CountCharsFit(mTextRun
,
4550 provider
.GetStart().GetSkippedOffset(), skippedLength
, width
, &provider
, &fitWidth
);
4552 PRInt32 selectedOffset
;
4553 if (charsFit
< skippedLength
) {
4554 // charsFit characters fitted, but no more could fit. See if we're
4555 // more than halfway through the cluster.. If we are, choose the next
4557 gfxSkipCharsIterator
extraCluster(provider
.GetStart());
4558 extraCluster
.AdvanceSkipped(charsFit
);
4559 gfxSkipCharsIterator
extraClusterLastChar(extraCluster
);
4560 FindClusterEnd(mTextRun
,
4561 provider
.GetStart().GetOriginalOffset() + provider
.GetOriginalLength(),
4562 &extraClusterLastChar
);
4563 gfxFloat charWidth
=
4564 mTextRun
->GetAdvanceWidth(extraCluster
.GetSkippedOffset(),
4565 GetSkippedDistance(extraCluster
, extraClusterLastChar
) + 1,
4567 selectedOffset
= width
<= fitWidth
+ charWidth
/2
4568 ? extraCluster
.GetOriginalOffset()
4569 : extraClusterLastChar
.GetOriginalOffset() + 1;
4571 // All characters fitted, we're at (or beyond) the end of the text.
4572 // XXX This could be some pathological situation where negative spacing
4573 // caused characters to move backwards. We can't really handle that
4574 // in the current frame system because frames can't have negative
4575 // intrinsic widths.
4577 provider
.GetStart().GetOriginalOffset() + provider
.GetOriginalLength();
4580 offsets
.content
= GetContent();
4581 offsets
.offset
= offsets
.secondaryOffset
= selectedOffset
;
4582 offsets
.associateWithNext
= mContentOffset
== offsets
.offset
;
4587 nsTextFrame::HasSelectionOverflowingDecorations(nsPresContext
* aPresContext
,
4591 nsILookAndFeel
* look
= aPresContext
->LookAndFeel();
4592 look
->GetMetric(nsILookAndFeel::eMetricFloat_IMEUnderlineRelativeSize
, ratio
);
4598 SelectionDetails
*details
= GetSelectionDetails();
4599 PRBool retval
= PR_FALSE
;
4600 for (SelectionDetails
*sd
= details
; sd
; sd
= sd
->mNext
) {
4601 if (sd
->mStart
!= sd
->mEnd
&&
4602 sd
->mType
& SelectionTypesWithDecorations
) {
4607 DestroySelectionDetails(details
);
4612 //null range means the whole thing
4614 nsTextFrame::SetSelected(nsPresContext
* aPresContext
,
4615 nsIDOMRange
*aRange
,
4618 SelectionType aType
)
4620 DEBUG_VERIFY_NOT_DIRTY(mState
);
4621 #if 0 //XXXrbs disable due to bug 310318
4622 if (mState
& NS_FRAME_IS_DIRTY
)
4623 return NS_ERROR_UNEXPECTED
;
4626 if (aSelected
&& ParentDisablesSelection())
4629 if (aType
== nsISelectionController::SELECTION_NORMAL
) {
4630 // check whether style allows selection
4632 IsSelectable(&selectable
, nsnull
);
4634 return NS_OK
;//do not continue no selection for this frame.
4637 PRBool found
= PR_FALSE
;
4639 //lets see if the range contains us, if so we must redraw!
4640 nsCOMPtr
<nsIDOMNode
> endNode
;
4642 nsCOMPtr
<nsIDOMNode
> startNode
;
4643 PRInt32 startOffset
;
4644 aRange
->GetEndContainer(getter_AddRefs(endNode
));
4645 aRange
->GetEndOffset(&endOffset
);
4646 aRange
->GetStartContainer(getter_AddRefs(startNode
));
4647 aRange
->GetStartOffset(&startOffset
);
4648 nsCOMPtr
<nsIDOMNode
> thisNode
= do_QueryInterface(GetContent());
4650 if (thisNode
== startNode
)
4652 if (GetContentEnd() >= startOffset
)
4655 if (thisNode
== endNode
)
4657 if (endOffset
== startOffset
) //no need to redraw since drawing takes place with cursor
4660 if (mContentOffset
> endOffset
)
4665 else if (thisNode
== endNode
)
4667 if (mContentOffset
< endOffset
)
4680 // null range means the whole thing
4685 AddStateBits(NS_FRAME_SELECTED_CONTENT
);
4687 { //we need to see if any other selection is available.
4688 SelectionDetails
*details
= GetSelectionDetails();
4690 RemoveStateBits(NS_FRAME_SELECTED_CONTENT
);
4692 DestroySelectionDetails(details
);
4696 // If the selection state is changed in this content, we need to reflow
4697 // to recompute the overflow area for underline of spellchecking or IME if
4698 // their underline is thicker than normal decoration line.
4699 PRBool didHaveSelectionUnderline
=
4700 !!(mState
& TEXT_SELECTION_UNDERLINE_OVERFLOWED
);
4701 PRBool willHaveSelectionUnderline
=
4702 aSelected
&& HasSelectionOverflowingDecorations(PresContext());
4703 if (didHaveSelectionUnderline
!= willHaveSelectionUnderline
) {
4704 PresContext()->PresShell()->FrameNeedsReflow(this,
4705 nsIPresShell::eStyleChange
,
4708 // Selection might change anything. Invalidate the overflow area.
4709 InvalidateOverflowRect();
4711 if (aSpread
== eSpreadDown
)
4713 nsIFrame
* frame
= GetPrevContinuation();
4715 frame
->SetSelected(aPresContext
, aRange
,aSelected
,eSpreadNone
, aType
);
4716 frame
= frame
->GetPrevContinuation();
4718 frame
= GetNextContinuation();
4720 frame
->SetSelected(aPresContext
, aRange
,aSelected
,eSpreadNone
, aType
);
4721 frame
= frame
->GetNextContinuation();
4728 nsTextFrame::GetPointFromOffset(PRInt32 inOffset
,
4732 return NS_ERROR_NULL_POINTER
;
4737 DEBUG_VERIFY_NOT_DIRTY(mState
);
4738 if (mState
& NS_FRAME_IS_DIRTY
)
4739 return NS_ERROR_UNEXPECTED
;
4741 if (GetContentLength() <= 0) {
4745 gfxSkipCharsIterator iter
= EnsureTextRun();
4747 return NS_ERROR_FAILURE
;
4749 PropertyProvider
properties(this, iter
);
4750 // Don't trim trailing whitespace, we want the caret to appear in the right
4751 // place if it's positioned there
4752 properties
.InitializeForDisplay(PR_FALSE
);
4754 if (inOffset
< GetContentOffset()){
4755 NS_WARNING("offset before this frame's content");
4756 inOffset
= GetContentOffset();
4757 } else if (inOffset
> GetContentEnd()) {
4758 NS_WARNING("offset after this frame's content");
4759 inOffset
= GetContentEnd();
4761 PRInt32 trimmedOffset
= properties
.GetStart().GetOriginalOffset();
4762 PRInt32 trimmedEnd
= trimmedOffset
+ properties
.GetOriginalLength();
4763 inOffset
= PR_MAX(inOffset
, trimmedOffset
);
4764 inOffset
= PR_MIN(inOffset
, trimmedEnd
);
4766 iter
.SetOriginalOffset(inOffset
);
4768 if (inOffset
< trimmedEnd
&&
4769 !iter
.IsOriginalCharSkipped() &&
4770 !mTextRun
->IsClusterStart(iter
.GetSkippedOffset())) {
4771 NS_WARNING("GetPointFromOffset called for non-cluster boundary");
4772 FindClusterStart(mTextRun
, &iter
);
4775 gfxFloat advanceWidth
=
4776 mTextRun
->GetAdvanceWidth(properties
.GetStart().GetSkippedOffset(),
4777 GetSkippedDistance(properties
.GetStart(), iter
),
4779 nscoord width
= NSToCoordCeil(advanceWidth
);
4781 if (mTextRun
->IsRightToLeft()) {
4782 outPoint
->x
= mRect
.width
- width
;
4784 outPoint
->x
= width
;
4792 nsTextFrame::GetChildFrameContainingOffset(PRInt32 aContentOffset
,
4794 PRInt32
* aOutOffset
,
4795 nsIFrame
**aOutFrame
)
4797 DEBUG_VERIFY_NOT_DIRTY(mState
);
4798 #if 0 //XXXrbs disable due to bug 310227
4799 if (mState
& NS_FRAME_IS_DIRTY
)
4800 return NS_ERROR_UNEXPECTED
;
4803 NS_ASSERTION(aOutOffset
&& aOutFrame
, "Bad out parameters");
4804 NS_ASSERTION(aContentOffset
>= 0, "Negative content offset, existing code was very broken!");
4806 nsTextFrame
* f
= this;
4807 if (aContentOffset
>= mContentOffset
) {
4809 nsTextFrame
* next
= static_cast<nsTextFrame
*>(f
->GetNextContinuation());
4810 if (!next
|| aContentOffset
< next
->GetContentOffset())
4812 if (aContentOffset
== next
->GetContentOffset()) {
4822 nsTextFrame
* prev
= static_cast<nsTextFrame
*>(f
->GetPrevContinuation());
4823 if (!prev
|| aContentOffset
> f
->GetContentOffset())
4825 if (aContentOffset
== f
->GetContentOffset()) {
4835 *aOutOffset
= aContentOffset
- f
->GetContentOffset();
4841 nsTextFrame::PeekOffsetNoAmount(PRBool aForward
, PRInt32
* aOffset
)
4843 NS_ASSERTION(aOffset
&& *aOffset
<= GetContentLength(), "aOffset out of range");
4845 gfxSkipCharsIterator iter
= EnsureTextRun();
4849 TrimmedOffsets trimmed
= GetTrimmedOffsets(mContent
->GetText(), PR_TRUE
);
4850 // Check whether there are nonskipped characters in the trimmmed range
4851 return iter
.ConvertOriginalToSkipped(trimmed
.GetEnd()) >
4852 iter
.ConvertOriginalToSkipped(trimmed
.mStart
);
4856 * This class iterates through the clusters before or after the given
4857 * aPosition (which is a content offset). You can test each cluster
4858 * to see if it's whitespace (as far as selection/caret movement is concerned),
4859 * or punctuation, or if there is a word break before the cluster. ("Before"
4860 * is interpreted according to aDirection, so if aDirection is -1, "before"
4861 * means actually *after* the cluster content.)
4863 class NS_STACK_CLASS ClusterIterator
{
4865 ClusterIterator(nsTextFrame
* aTextFrame
, PRInt32 aPosition
, PRInt32 aDirection
,
4866 nsString
& aContext
);
4868 PRBool
NextCluster();
4869 PRBool
IsWhitespace();
4870 PRBool
IsPunctuation();
4871 PRBool
HaveWordBreakBefore() { return mHaveWordBreak
; }
4872 PRInt32
GetAfterOffset();
4873 PRInt32
GetBeforeOffset();
4876 nsCOMPtr
<nsIUGenCategory
> mCategories
;
4877 gfxSkipCharsIterator mIterator
;
4878 const nsTextFragment
* mFrag
;
4879 nsTextFrame
* mTextFrame
;
4882 nsTextFrame::TrimmedOffsets mTrimmed
;
4883 nsTArray
<PRPackedBool
> mWordBreaks
;
4884 PRPackedBool mHaveWordBreak
;
4888 IsAcceptableCaretPosition(const gfxSkipCharsIterator
& aIter
, gfxTextRun
* aTextRun
,
4891 if (aIter
.IsOriginalCharSkipped())
4893 PRUint32 index
= aIter
.GetSkippedOffset();
4894 if (!aTextRun
->IsClusterStart(index
))
4896 return !(aFrame
->GetStyleText()->NewlineIsSignificant() &&
4897 aTextRun
->GetChar(index
) == '\n');
4901 nsTextFrame::PeekOffsetCharacter(PRBool aForward
, PRInt32
* aOffset
)
4903 PRInt32 contentLength
= GetContentLength();
4904 NS_ASSERTION(aOffset
&& *aOffset
<= contentLength
, "aOffset out of range");
4907 PRUint8 selectStyle
;
4908 IsSelectable(&selectable
, &selectStyle
);
4909 if (selectStyle
== NS_STYLE_USER_SELECT_ALL
)
4912 gfxSkipCharsIterator iter
= EnsureTextRun();
4916 TrimmedOffsets trimmed
= GetTrimmedOffsets(mContent
->GetText(), PR_FALSE
);
4918 // A negative offset means "end of frame".
4919 PRInt32 startOffset
= GetContentOffset() + (*aOffset
< 0 ? contentLength
: *aOffset
);
4923 for (i
= PR_MIN(trimmed
.GetEnd(), startOffset
) - 1;
4924 i
>= trimmed
.mStart
; --i
) {
4925 iter
.SetOriginalOffset(i
);
4926 if (IsAcceptableCaretPosition(iter
, mTextRun
, this)) {
4927 *aOffset
= i
- mContentOffset
;
4934 for (i
= startOffset
+ 1; i
<= trimmed
.GetEnd(); ++i
) {
4935 iter
.SetOriginalOffset(i
);
4936 // XXX we can't necessarily stop at the end of this frame,
4937 // but we really have no choice right now. We need to do a deeper
4938 // fix/restructuring of PeekOffsetCharacter
4939 if (i
== trimmed
.GetEnd() ||
4940 IsAcceptableCaretPosition(iter
, mTextRun
, this)) {
4941 *aOffset
= i
- mContentOffset
;
4945 *aOffset
= contentLength
;
4952 ClusterIterator::IsWhitespace()
4954 NS_ASSERTION(mCharIndex
>= 0, "No cluster selected");
4955 return IsSelectionSpace(mFrag
, mCharIndex
);
4959 ClusterIterator::IsPunctuation()
4961 NS_ASSERTION(mCharIndex
>= 0, "No cluster selected");
4964 nsIUGenCategory::nsUGenCategory c
= mCategories
->Get(mFrag
->CharAt(mCharIndex
));
4965 return c
== nsIUGenCategory::kPunctuation
|| c
== nsIUGenCategory::kSymbol
;
4969 ClusterIterator::GetBeforeOffset()
4971 NS_ASSERTION(mCharIndex
>= 0, "No cluster selected");
4972 return mCharIndex
+ (mDirection
> 0 ? 0 : 1);
4976 ClusterIterator::GetAfterOffset()
4978 NS_ASSERTION(mCharIndex
>= 0, "No cluster selected");
4979 return mCharIndex
+ (mDirection
> 0 ? 1 : 0);
4983 ClusterIterator::NextCluster()
4987 gfxTextRun
* textRun
= mTextFrame
->GetTextRun();
4989 mHaveWordBreak
= PR_FALSE
;
4991 PRBool keepGoing
= PR_FALSE
;
4992 if (mDirection
> 0) {
4993 if (mIterator
.GetOriginalOffset() >= mTrimmed
.GetEnd())
4995 keepGoing
= mIterator
.IsOriginalCharSkipped() ||
4996 mIterator
.GetOriginalOffset() < mTrimmed
.mStart
||
4997 !textRun
->IsClusterStart(mIterator
.GetSkippedOffset());
4998 mCharIndex
= mIterator
.GetOriginalOffset();
4999 mIterator
.AdvanceOriginal(1);
5001 if (mIterator
.GetOriginalOffset() <= mTrimmed
.mStart
)
5003 mIterator
.AdvanceOriginal(-1);
5004 keepGoing
= mIterator
.IsOriginalCharSkipped() ||
5005 mIterator
.GetOriginalOffset() >= mTrimmed
.GetEnd() ||
5006 !textRun
->IsClusterStart(mIterator
.GetSkippedOffset());
5007 mCharIndex
= mIterator
.GetOriginalOffset();
5010 if (mWordBreaks
[GetBeforeOffset() - mTextFrame
->GetContentOffset()]) {
5011 mHaveWordBreak
= PR_TRUE
;
5018 ClusterIterator::ClusterIterator(nsTextFrame
* aTextFrame
, PRInt32 aPosition
,
5019 PRInt32 aDirection
, nsString
& aContext
)
5020 : mTextFrame(aTextFrame
), mDirection(aDirection
), mCharIndex(-1)
5022 mIterator
= aTextFrame
->EnsureTextRun();
5023 if (!aTextFrame
->GetTextRun()) {
5024 mDirection
= 0; // signal failure
5027 mIterator
.SetOriginalOffset(aPosition
);
5029 mCategories
= do_GetService(NS_UNICHARCATEGORY_CONTRACTID
);
5031 mFrag
= aTextFrame
->GetContent()->GetText();
5032 mTrimmed
= aTextFrame
->GetTrimmedOffsets(mFrag
, PR_TRUE
);
5034 PRInt32 textOffset
= aTextFrame
->GetContentOffset();
5035 PRInt32 textLen
= aTextFrame
->GetContentLength();
5036 if (!mWordBreaks
.AppendElements(textLen
+ 1)) {
5037 mDirection
= 0; // signal failure
5040 memset(mWordBreaks
.Elements(), PR_FALSE
, textLen
+ 1);
5042 if (aDirection
> 0) {
5043 if (aContext
.IsEmpty()) {
5044 // No previous context, so it must be the start of a line or text run
5045 mWordBreaks
[0] = PR_TRUE
;
5047 textStart
= aContext
.Length();
5048 mFrag
->AppendTo(aContext
, textOffset
, textLen
);
5050 if (aContext
.IsEmpty()) {
5051 // No following context, so it must be the end of a line or text run
5052 mWordBreaks
[textLen
] = PR_TRUE
;
5056 mFrag
->AppendTo(str
, textOffset
, textLen
);
5057 aContext
.Insert(str
, 0);
5059 nsIWordBreaker
* wordBreaker
= nsContentUtils::WordBreaker();
5061 for (i
= 0; i
<= textLen
; ++i
) {
5062 PRInt32 indexInText
= i
+ textStart
;
5064 wordBreaker
->BreakInBetween(aContext
.get(), indexInText
,
5065 aContext
.get() + indexInText
,
5066 aContext
.Length() - indexInText
);
5071 nsTextFrame::PeekOffsetWord(PRBool aForward
, PRBool aWordSelectEatSpace
, PRBool aIsKeyboardSelect
,
5072 PRInt32
* aOffset
, PeekWordState
* aState
)
5074 PRInt32 contentLength
= GetContentLength();
5075 NS_ASSERTION (aOffset
&& *aOffset
<= contentLength
, "aOffset out of range");
5078 PRUint8 selectStyle
;
5079 IsSelectable(&selectable
, &selectStyle
);
5080 if (selectStyle
== NS_STYLE_USER_SELECT_ALL
)
5083 PRInt32 offset
= GetContentOffset() + (*aOffset
< 0 ? contentLength
: *aOffset
);
5084 ClusterIterator
cIter(this, offset
, aForward
? 1 : -1, aState
->mContext
);
5086 if (!cIter
.NextCluster())
5090 PRBool isPunctuation
= cIter
.IsPunctuation();
5091 PRBool isWhitespace
= cIter
.IsWhitespace();
5092 PRBool isWordBreakBefore
= cIter
.HaveWordBreakBefore();
5093 if (aWordSelectEatSpace
== isWhitespace
&& !aState
->mSawBeforeType
) {
5094 aState
->SetSawBeforeType();
5095 aState
->Update(isPunctuation
, isWhitespace
);
5098 // See if we can break before the current cluster
5099 if (!aState
->mAtStart
) {
5101 if (isPunctuation
!= aState
->mLastCharWasPunctuation
) {
5102 canBreak
= BreakWordBetweenPunctuation(aState
, aForward
,
5103 isPunctuation
, isWhitespace
, aIsKeyboardSelect
);
5104 } else if (!aState
->mLastCharWasWhitespace
&&
5105 !isWhitespace
&& !isPunctuation
&& isWordBreakBefore
) {
5106 // if both the previous and the current character are not white
5107 // space but this can be word break before, we don't need to eat
5108 // a white space in this case. This case happens in some languages
5109 // that their words are not separated by white spaces. E.g.,
5110 // Japanese and Chinese.
5113 canBreak
= isWordBreakBefore
&& aState
->mSawBeforeType
;
5116 *aOffset
= cIter
.GetBeforeOffset() - mContentOffset
;
5120 aState
->Update(isPunctuation
, isWhitespace
);
5121 } while (cIter
.NextCluster());
5123 *aOffset
= cIter
.GetAfterOffset() - mContentOffset
;
5127 // TODO this needs to be deCOMtaminated with the interface fixed in
5128 // nsIFrame.h, but we won't do that until the old textframe is gone.
5130 nsTextFrame::CheckVisibility(nsPresContext
* aContext
, PRInt32 aStartIndex
,
5131 PRInt32 aEndIndex
, PRBool aRecurse
, PRBool
*aFinished
, PRBool
*aRetval
)
5134 return NS_ERROR_NULL_POINTER
;
5136 // Text in the range is visible if there is at least one character in the range
5137 // that is not skipped and is mapped by this frame (which is the primary frame)
5138 // or one of its continuations.
5139 for (nsTextFrame
* f
= this; f
;
5140 f
= static_cast<nsTextFrame
*>(GetNextContinuation())) {
5141 if (f
->PeekOffsetNoAmount(PR_TRUE
, nsnull
)) {
5147 *aRetval
= PR_FALSE
;
5152 nsTextFrame::GetOffsets(PRInt32
&start
, PRInt32
&end
) const
5154 start
= GetContentOffset();
5155 end
= GetContentEnd();
5160 FindEndOfPunctuationRun(const nsTextFragment
* aFrag
,
5161 gfxTextRun
* aTextRun
,
5162 gfxSkipCharsIterator
* aIter
,
5169 for (i
= aStart
; i
< aEnd
- aOffset
; ++i
) {
5170 if (nsContentUtils::IsPunctuationMarkAt(aFrag
, aOffset
+ i
)) {
5171 aIter
->SetOriginalOffset(aOffset
+ i
);
5172 FindClusterEnd(aTextRun
, aEnd
, aIter
);
5173 i
= aIter
->GetOriginalOffset() - aOffset
;
5182 * Returns PR_TRUE if this text frame completes the first-letter, PR_FALSE
5183 * if it does not contain a true "letter".
5184 * If returns PR_TRUE, then it also updates aLength to cover just the first-letter
5187 * XXX :first-letter should be handled during frame construction
5188 * (and it has a good bit in common with nextBidi)
5190 * @param aLength an in/out parameter: on entry contains the maximum length to
5191 * return, on exit returns length of the first-letter fragment (which may
5192 * include leading and trailing punctuation, for example)
5195 FindFirstLetterRange(const nsTextFragment
* aFrag
,
5196 gfxTextRun
* aTextRun
,
5197 PRInt32 aOffset
, const gfxSkipCharsIterator
& aIter
,
5201 PRInt32 length
= *aLength
;
5202 PRInt32 endOffset
= aOffset
+ length
;
5203 gfxSkipCharsIterator
iter(aIter
);
5205 // skip leading whitespace, then consume clusters that start with punctuation
5206 i
= FindEndOfPunctuationRun(aFrag
, aTextRun
, &iter
, aOffset
,
5207 GetTrimmableWhitespaceCount(aFrag
, aOffset
, length
, 1),
5212 // If the next character is not a letter or number, there is no first-letter.
5213 // Return PR_TRUE so that we don't go on looking, but set aLength to 0.
5214 if (!nsContentUtils::IsAlphanumericAt(aFrag
, aOffset
+ i
)) {
5219 // consume another cluster (the actual first letter)
5220 iter
.SetOriginalOffset(aOffset
+ i
);
5221 FindClusterEnd(aTextRun
, endOffset
, &iter
);
5222 i
= iter
.GetOriginalOffset() - aOffset
;
5223 if (i
+ 1 == length
)
5226 // consume clusters that start with punctuation
5227 i
= FindEndOfPunctuationRun(aFrag
, aTextRun
, &iter
, aOffset
, i
+ 1, endOffset
);
5234 FindStartAfterSkippingWhitespace(PropertyProvider
* aProvider
,
5235 nsIFrame::InlineIntrinsicWidthData
* aData
,
5236 const nsStyleText
* aTextStyle
,
5237 gfxSkipCharsIterator
* aIterator
,
5238 PRUint32 aFlowEndInTextRun
)
5240 if (aData
->skipWhitespace
) {
5241 while (aIterator
->GetSkippedOffset() < aFlowEndInTextRun
&&
5242 IsTrimmableSpace(aProvider
->GetFragment(), aIterator
->GetOriginalOffset(), aTextStyle
)) {
5243 aIterator
->AdvanceOriginal(1);
5246 return aIterator
->GetSkippedOffset();
5250 void nsTextFrame::MarkIntrinsicWidthsDirty()
5253 nsFrame::MarkIntrinsicWidthsDirty();
5256 // XXX this doesn't handle characters shaped by line endings. We need to
5257 // temporarily override the "current line ending" settings.
5259 nsTextFrame::AddInlineMinWidthForFlow(nsIRenderingContext
*aRenderingContext
,
5260 nsIFrame::InlineMinWidthData
*aData
)
5262 PRUint32 flowEndInTextRun
;
5263 gfxContext
* ctx
= aRenderingContext
->ThebesContext();
5264 gfxSkipCharsIterator iter
=
5265 EnsureTextRun(ctx
, nsnull
, aData
->line
, &flowEndInTextRun
);
5269 // Pass null for the line container. This will disable tab spacing, but that's
5270 // OK since we can't really handle tabs for intrinsic sizing anyway.
5271 const nsStyleText
* textStyle
= GetStyleText();
5272 const nsTextFragment
* frag
= mContent
->GetText();
5273 PropertyProvider
provider(mTextRun
, textStyle
, frag
, this,
5274 iter
, PR_INT32_MAX
, nsnull
, 0);
5276 PRBool collapseWhitespace
= !textStyle
->WhiteSpaceIsSignificant();
5277 PRBool preformatNewlines
= textStyle
->NewlineIsSignificant();
5278 PRBool preformatTabs
= textStyle
->WhiteSpaceIsSignificant();
5279 gfxFloat tabWidth
= -1;
5281 FindStartAfterSkippingWhitespace(&provider
, aData
, textStyle
, &iter
, flowEndInTextRun
);
5282 if (start
>= flowEndInTextRun
)
5285 // XXX Should we consider hyphenation here?
5286 for (PRUint32 i
= start
, wordStart
= start
; i
<= flowEndInTextRun
; ++i
) {
5287 PRBool preformattedNewline
= PR_FALSE
;
5288 PRBool preformattedTab
= PR_FALSE
;
5289 if (i
< flowEndInTextRun
) {
5290 // XXXldb Shouldn't we be including the newline as part of the
5291 // segment that it ends rather than part of the segment that it
5293 preformattedNewline
= preformatNewlines
&& mTextRun
->GetChar(i
) == '\n';
5294 preformattedTab
= preformatTabs
&& mTextRun
->GetChar(i
) == '\t';
5295 if (!mTextRun
->CanBreakLineBefore(i
) && !preformattedNewline
&&
5297 // we can't break here (and it's not the end of the flow)
5302 if (i
> wordStart
) {
5304 NSToCoordCeil(mTextRun
->GetAdvanceWidth(wordStart
, i
- wordStart
, &provider
));
5305 aData
->currentLine
+= width
;
5306 aData
->atStartOfLine
= PR_FALSE
;
5308 if (collapseWhitespace
) {
5309 PRUint32 trimStart
= GetEndOfTrimmedText(frag
, textStyle
, wordStart
, i
, &iter
);
5310 if (trimStart
== start
) {
5311 // This is *all* trimmable whitespace, so whatever trailingWhitespace
5312 // we saw previously is still trailing...
5313 aData
->trailingWhitespace
+= width
;
5315 // Some non-whitespace so the old trailingWhitespace is no longer trailing
5316 aData
->trailingWhitespace
=
5317 NSToCoordCeil(mTextRun
->GetAdvanceWidth(trimStart
, i
- trimStart
, &provider
));
5320 aData
->trailingWhitespace
= 0;
5324 if (preformattedTab
) {
5325 PropertyProvider::Spacing spacing
;
5326 provider
.GetSpacing(i
, 1, &spacing
);
5327 aData
->currentLine
+= nscoord(spacing
.mBefore
);
5329 AdvanceToNextTab(aData
->currentLine
, FindLineContainer(this),
5330 mTextRun
, &tabWidth
);
5331 aData
->currentLine
= nscoord(afterTab
+ spacing
.mAfter
);
5333 } else if (i
< flowEndInTextRun
||
5334 (i
== mTextRun
->GetLength() &&
5335 (mTextRun
->GetFlags() & nsTextFrameUtils::TEXT_HAS_TRAILING_BREAK
))) {
5336 if (preformattedNewline
) {
5337 aData
->ForceBreak(aRenderingContext
);
5339 aData
->OptionallyBreak(aRenderingContext
);
5345 // Check if we have collapsible whitespace at the end
5346 aData
->skipWhitespace
=
5347 IsTrimmableSpace(provider
.GetFragment(),
5348 iter
.ConvertSkippedToOriginal(flowEndInTextRun
- 1),
5352 // XXX Need to do something here to avoid incremental reflow bugs due to
5353 // first-line and first-letter changing min-width
5355 nsTextFrame::AddInlineMinWidth(nsIRenderingContext
*aRenderingContext
,
5356 nsIFrame::InlineMinWidthData
*aData
)
5359 gfxTextRun
* lastTextRun
= nsnull
;
5360 // nsContinuingTextFrame does nothing for AddInlineMinWidth; all text frames
5361 // in the flow are handled right here.
5362 for (f
= this; f
; f
= static_cast<nsTextFrame
*>(f
->GetNextContinuation())) {
5363 // f->mTextRun could be null if we haven't set up textruns yet for f.
5364 // Except in OOM situations, lastTextRun will only be null for the first
5366 if (f
== this || f
->mTextRun
!= lastTextRun
) {
5367 // This will process all the text frames that share the same textrun as f.
5368 f
->AddInlineMinWidthForFlow(aRenderingContext
, aData
);
5369 lastTextRun
= f
->mTextRun
;
5374 // XXX this doesn't handle characters shaped by line endings. We need to
5375 // temporarily override the "current line ending" settings.
5377 nsTextFrame::AddInlinePrefWidthForFlow(nsIRenderingContext
*aRenderingContext
,
5378 nsIFrame::InlinePrefWidthData
*aData
)
5380 PRUint32 flowEndInTextRun
;
5381 gfxContext
* ctx
= aRenderingContext
->ThebesContext();
5382 gfxSkipCharsIterator iter
=
5383 EnsureTextRun(ctx
, nsnull
, aData
->line
, &flowEndInTextRun
);
5387 // Pass null for the line container. This will disable tab spacing, but that's
5388 // OK since we can't really handle tabs for intrinsic sizing anyway.
5390 const nsStyleText
* textStyle
= GetStyleText();
5391 const nsTextFragment
* frag
= mContent
->GetText();
5392 PropertyProvider
provider(mTextRun
, textStyle
, frag
, this,
5393 iter
, PR_INT32_MAX
, nsnull
, 0);
5395 PRBool collapseWhitespace
= !textStyle
->WhiteSpaceIsSignificant();
5396 PRBool preformatNewlines
= textStyle
->NewlineIsSignificant();
5397 PRBool preformatTabs
= textStyle
->WhiteSpaceIsSignificant();
5398 gfxFloat tabWidth
= -1;
5400 FindStartAfterSkippingWhitespace(&provider
, aData
, textStyle
, &iter
, flowEndInTextRun
);
5401 if (start
>= flowEndInTextRun
)
5404 // XXX Should we consider hyphenation here?
5405 // If newlines and tabs aren't preformatted, nothing to do inside
5406 // the loop so make i skip to the end
5407 PRUint32 loopStart
= (preformatNewlines
|| preformatTabs
) ? start
: flowEndInTextRun
;
5408 for (PRUint32 i
= loopStart
, lineStart
= start
; i
<= flowEndInTextRun
; ++i
) {
5409 PRBool preformattedNewline
= PR_FALSE
;
5410 PRBool preformattedTab
= PR_FALSE
;
5411 if (i
< flowEndInTextRun
) {
5412 // XXXldb Shouldn't we be including the newline as part of the
5413 // segment that it ends rather than part of the segment that it
5415 NS_ASSERTION(preformatNewlines
, "We can't be here unless newlines are hard breaks");
5416 preformattedNewline
= preformatNewlines
&& mTextRun
->GetChar(i
) == '\n';
5417 preformattedTab
= preformatTabs
&& mTextRun
->GetChar(i
) == '\t';
5418 if (!preformattedNewline
&& !preformattedTab
) {
5419 // we needn't break here (and it's not the end of the flow)
5424 if (i
> lineStart
) {
5426 NSToCoordCeil(mTextRun
->GetAdvanceWidth(lineStart
, i
- lineStart
, &provider
));
5427 aData
->currentLine
= NSCoordSaturatingAdd(aData
->currentLine
, width
);
5429 if (collapseWhitespace
) {
5430 PRUint32 trimStart
= GetEndOfTrimmedText(frag
, textStyle
, lineStart
, i
, &iter
);
5431 if (trimStart
== start
) {
5432 // This is *all* trimmable whitespace, so whatever trailingWhitespace
5433 // we saw previously is still trailing...
5434 aData
->trailingWhitespace
+= width
;
5436 // Some non-whitespace so the old trailingWhitespace is no longer trailing
5437 aData
->trailingWhitespace
=
5438 NSToCoordCeil(mTextRun
->GetAdvanceWidth(trimStart
, i
- trimStart
, &provider
));
5441 aData
->trailingWhitespace
= 0;
5445 if (preformattedTab
) {
5446 PropertyProvider::Spacing spacing
;
5447 provider
.GetSpacing(i
, 1, &spacing
);
5448 aData
->currentLine
+= nscoord(spacing
.mBefore
);
5450 AdvanceToNextTab(aData
->currentLine
, FindLineContainer(this),
5451 mTextRun
, &tabWidth
);
5452 aData
->currentLine
= nscoord(afterTab
+ spacing
.mAfter
);
5454 } else if (preformattedNewline
) {
5455 aData
->ForceBreak(aRenderingContext
);
5460 // Check if we have collapsible whitespace at the end
5461 aData
->skipWhitespace
=
5462 IsTrimmableSpace(provider
.GetFragment(),
5463 iter
.ConvertSkippedToOriginal(flowEndInTextRun
- 1),
5467 // XXX Need to do something here to avoid incremental reflow bugs due to
5468 // first-line and first-letter changing pref-width
5470 nsTextFrame::AddInlinePrefWidth(nsIRenderingContext
*aRenderingContext
,
5471 nsIFrame::InlinePrefWidthData
*aData
)
5474 gfxTextRun
* lastTextRun
= nsnull
;
5475 // nsContinuingTextFrame does nothing for AddInlineMinWidth; all text frames
5476 // in the flow are handled right here.
5477 for (f
= this; f
; f
= static_cast<nsTextFrame
*>(f
->GetNextContinuation())) {
5478 // f->mTextRun could be null if we haven't set up textruns yet for f.
5479 // Except in OOM situations, lastTextRun will only be null for the first
5481 if (f
== this || f
->mTextRun
!= lastTextRun
) {
5482 // This will process all the text frames that share the same textrun as f.
5483 f
->AddInlinePrefWidthForFlow(aRenderingContext
, aData
);
5484 lastTextRun
= f
->mTextRun
;
5489 /* virtual */ nsSize
5490 nsTextFrame::ComputeSize(nsIRenderingContext
*aRenderingContext
,
5491 nsSize aCBSize
, nscoord aAvailableWidth
,
5492 nsSize aMargin
, nsSize aBorder
, nsSize aPadding
,
5495 // Inlines and text don't compute size before reflow.
5496 return nsSize(NS_UNCONSTRAINEDSIZE
, NS_UNCONSTRAINEDSIZE
);
5500 RoundOut(const gfxRect
& aRect
)
5503 r
.x
= NSToCoordFloor(aRect
.X());
5504 r
.y
= NSToCoordFloor(aRect
.Y());
5505 r
.width
= NSToCoordCeil(aRect
.XMost()) - r
.x
;
5506 r
.height
= NSToCoordCeil(aRect
.YMost()) - r
.y
;
5511 nsTextFrame::ComputeTightBounds(gfxContext
* aContext
) const
5513 if ((GetStyleContext()->HasTextDecorations() &&
5514 eCompatibility_NavQuirks
== PresContext()->CompatibilityMode()) ||
5515 (GetStateBits() & TEXT_HYPHEN_BREAK
)) {
5516 // This is conservative, but OK.
5517 return GetOverflowRect();
5520 gfxSkipCharsIterator iter
= const_cast<nsTextFrame
*>(this)->EnsureTextRun();
5522 return nsRect(0, 0, 0, 0);
5524 PropertyProvider
provider(const_cast<nsTextFrame
*>(this), iter
);
5525 // Trim trailing whitespace
5526 provider
.InitializeForDisplay(PR_TRUE
);
5528 gfxTextRun::Metrics metrics
=
5529 mTextRun
->MeasureText(provider
.GetStart().GetSkippedOffset(),
5530 ComputeTransformedLength(provider
), PR_TRUE
,
5531 aContext
, &provider
);
5532 // mAscent should be the same as metrics.mAscent, but it's what we use to
5533 // paint so that's the one we'll use.
5534 return RoundOut(metrics
.mBoundingBox
) + nsPoint(0, mAscent
);
5538 AddCharToMetrics(gfxTextRun
* aCharTextRun
, gfxTextRun
* aBaseTextRun
,
5539 gfxTextRun::Metrics
* aMetrics
, PRBool aTightBoundingBox
,
5540 gfxContext
* aContext
)
5542 gfxTextRun::Metrics charMetrics
=
5543 aCharTextRun
->MeasureText(0, aCharTextRun
->GetLength(), aTightBoundingBox
, aContext
, nsnull
);
5545 aMetrics
->CombineWith(charMetrics
, aBaseTextRun
->IsRightToLeft());
5549 HasSoftHyphenBefore(const nsTextFragment
* aFrag
, gfxTextRun
* aTextRun
,
5550 PRInt32 aStartOffset
, const gfxSkipCharsIterator
& aIter
)
5552 if (!(aTextRun
->GetFlags() & nsTextFrameUtils::TEXT_HAS_SHY
))
5554 gfxSkipCharsIterator iter
= aIter
;
5555 while (iter
.GetOriginalOffset() > aStartOffset
) {
5556 iter
.AdvanceOriginal(-1);
5557 if (!iter
.IsOriginalCharSkipped())
5559 if (aFrag
->CharAt(iter
.GetOriginalOffset()) == CH_SHY
)
5566 nsTextFrame::SetLength(PRInt32 aLength
)
5568 mContentLengthHint
= aLength
;
5569 PRInt32 end
= GetContentOffset() + aLength
;
5570 nsTextFrame
* f
= static_cast<nsTextFrame
*>(GetNextInFlow());
5573 if (end
< f
->mContentOffset
) {
5574 // Our frame is shrinking. Give the text to our next in flow.
5575 f
->mContentOffset
= end
;
5576 if (f
->GetTextRun() != mTextRun
) {
5582 while (f
&& f
->mContentOffset
< end
) {
5583 // Our frame is growing. Take text from our in-flow.
5584 f
->mContentOffset
= end
;
5585 if (f
->GetTextRun() != mTextRun
) {
5589 f
= static_cast<nsTextFrame
*>(f
->GetNextInFlow());
5592 f
= static_cast<nsTextFrame
*>(this->GetFirstContinuation());
5594 f
->GetContentLength(); // Assert if negative length
5595 f
= static_cast<nsTextFrame
*>(f
->GetNextContinuation());
5601 nsTextFrame::IsFloatingFirstLetterChild()
5603 if (!GetStateBits() & TEXT_FIRST_LETTER
)
5605 nsIFrame
* frame
= GetParent();
5606 if (!frame
|| frame
->GetType() != nsGkAtoms::letterFrame
)
5608 return frame
->GetStyleDisplay()->IsFloating();
5612 nsTextFrame::Reflow(nsPresContext
* aPresContext
,
5613 nsHTMLReflowMetrics
& aMetrics
,
5614 const nsHTMLReflowState
& aReflowState
,
5615 nsReflowStatus
& aStatus
)
5617 DO_GLOBAL_REFLOW_COUNT("nsTextFrame");
5618 DISPLAY_REFLOW(aPresContext
, this, aReflowState
, aMetrics
, aStatus
);
5621 printf(": BeginReflow: availableSize=%d,%d\n",
5622 aReflowState
.availableWidth
, aReflowState
.availableHeight
);
5625 /////////////////////////////////////////////////////////////////////
5626 // Set up flags and clear out state
5627 /////////////////////////////////////////////////////////////////////
5629 // Clear out the reflow state flags in mState (without destroying
5630 // the TEXT_BLINK_ON bit). We also clear the whitespace flags because this
5631 // can change whether the frame maps whitespace-only text or not.
5632 RemoveStateBits(TEXT_REFLOW_FLAGS
| TEXT_WHITESPACE_FLAGS
);
5634 // Temporarily map all possible content while we construct our new textrun.
5635 // so that when doing reflow our styles prevail over any part of the
5636 // textrun we look at. Note that next-in-flows may be mapping the same
5637 // content; gfxTextRun construction logic will ensure that we take priority.
5638 PRInt32 maxContentLength
= GetInFlowContentLength();
5640 // XXX If there's no line layout, we shouldn't even have created this
5641 // frame. This may happen if, for example, this is text inside a table
5642 // but not inside a cell. For now, just don't reflow. We also don't need to
5643 // reflow if there is no content.
5644 if (!aReflowState
.mLineLayout
|| !maxContentLength
) {
5645 ClearMetrics(aMetrics
);
5646 aStatus
= NS_FRAME_COMPLETE
;
5650 nsLineLayout
& lineLayout
= *aReflowState
.mLineLayout
;
5652 if (aReflowState
.mFlags
.mBlinks
) {
5653 if (0 == (mState
& TEXT_BLINK_ON
)) {
5654 mState
|= TEXT_BLINK_ON
;
5655 nsBlinkTimer::AddBlinkFrame(aPresContext
, this);
5659 if (0 != (mState
& TEXT_BLINK_ON
)) {
5660 mState
&= ~TEXT_BLINK_ON
;
5661 nsBlinkTimer::RemoveBlinkFrame(this);
5665 const nsStyleText
* textStyle
= GetStyleText();
5667 PRBool atStartOfLine
= lineLayout
.LineIsEmpty();
5668 if (atStartOfLine
) {
5669 AddStateBits(TEXT_START_OF_LINE
);
5672 PRUint32 flowEndInTextRun
;
5673 nsIFrame
* lineContainer
= lineLayout
.GetLineContainerFrame();
5674 gfxContext
* ctx
= aReflowState
.rendContext
->ThebesContext();
5675 const nsTextFragment
* frag
= mContent
->GetText();
5677 // DOM offsets of the text range we need to measure, after trimming
5678 // whitespace, restricting to first-letter, and restricting preformatted text
5679 // to nearest newline
5680 PRInt32 length
= maxContentLength
;
5681 PRInt32 offset
= GetContentOffset();
5683 // Restrict preformatted text to the nearest newline
5684 PRInt32 newLineOffset
= -1; // this will be -1 or a content offset
5685 if (textStyle
->NewlineIsSignificant()) {
5686 newLineOffset
= FindChar(frag
, offset
, length
, '\n');
5687 if (newLineOffset
>= 0) {
5688 length
= newLineOffset
+ 1 - offset
;
5691 if (atStartOfLine
&& !textStyle
->WhiteSpaceIsSignificant()) {
5692 // Skip leading whitespace. Make sure we don't skip a 'pre-line'
5693 // newline if there is one.
5694 PRInt32 skipLength
= newLineOffset
>= 0 ? length
- 1 : length
;
5695 PRInt32 whitespaceCount
=
5696 GetTrimmableWhitespaceCount(frag
, offset
, skipLength
, 1);
5697 offset
+= whitespaceCount
;
5698 length
-= whitespaceCount
;
5701 PRBool completedFirstLetter
= PR_FALSE
;
5702 // Layout dependent styles are a problem because we need to reconstruct
5703 // the gfxTextRun based on our layout.
5704 if (lineLayout
.GetFirstLetterStyleOK() || lineLayout
.GetInFirstLine()) {
5705 SetLength(maxContentLength
);
5707 if (lineLayout
.GetFirstLetterStyleOK()) {
5708 // floating first-letter boundaries are significant in textrun
5709 // construction, so clear the textrun out every time we hit a first-letter
5710 // and have changed our length (which controls the first-letter boundary)
5712 // Find the length of the first-letter. We need a textrun for this.
5713 gfxSkipCharsIterator iter
=
5714 EnsureTextRun(ctx
, lineContainer
, lineLayout
.GetLine(), &flowEndInTextRun
);
5717 PRInt32 firstLetterLength
= length
;
5718 completedFirstLetter
=
5719 FindFirstLetterRange(frag
, mTextRun
, offset
, iter
, &firstLetterLength
);
5720 if (newLineOffset
>= 0) {
5721 // Don't allow a preformatted newline to be part of a first-letter.
5722 firstLetterLength
= PR_MIN(firstLetterLength
, length
- 1);
5724 // There is no text to be consumed by the first-letter before the
5725 // preformatted newline. Note that the first letter is therefore
5726 // complete (FindFirstLetterRange will have returned false).
5727 completedFirstLetter
= PR_TRUE
;
5730 length
= firstLetterLength
;
5732 AddStateBits(TEXT_FIRST_LETTER
);
5734 // Change this frame's length to the first-letter length right now
5735 // so that when we rebuild the textrun it will be built with the
5736 // right first-letter boundary
5737 SetLength(offset
+ length
- GetContentOffset());
5738 // Ensure that the textrun will be rebuilt
5744 gfxSkipCharsIterator iter
=
5745 EnsureTextRun(ctx
, lineContainer
, lineLayout
.GetLine(), &flowEndInTextRun
);
5747 PRInt32 skippedRunLength
;
5748 if (mTextRun
&& mTextRun
->GetLength() == iter
.GetSkippedOffset() &&
5750 (!iter
.IsOriginalCharSkipped(&skippedRunLength
) || skippedRunLength
< length
)) {
5751 // The textrun does not map enough text for this frame. This can happen
5752 // when the textrun was ended in the middle of a text node because a
5753 // preformatted newline was encountered, and prev-in-flow frames have
5754 // consumed all the text of the textrun. We need a new textrun.
5756 iter
= EnsureTextRun(ctx
, lineContainer
,
5757 lineLayout
.GetLine(), &flowEndInTextRun
);
5761 ClearMetrics(aMetrics
);
5762 aStatus
= NS_FRAME_COMPLETE
;
5766 NS_ASSERTION(gfxSkipCharsIterator(iter
).ConvertOriginalToSkipped(offset
+ length
)
5767 <= mTextRun
->GetLength(),
5768 "Text run does not map enough text for our reflow");
5770 /////////////////////////////////////////////////////////////////////
5771 // See how much text should belong to this text frame, and measure it
5772 /////////////////////////////////////////////////////////////////////
5774 iter
.SetOriginalOffset(offset
);
5775 nscoord xOffsetForTabs
= (mTextRun
->GetFlags() & nsTextFrameUtils::TEXT_HAS_TAB
) ?
5776 (lineLayout
.GetCurrentFrameXDistanceFromBlock() -
5777 lineContainer
->GetUsedBorderAndPadding().left
)
5779 PropertyProvider
provider(mTextRun
, textStyle
, frag
, this, iter
, length
,
5780 lineContainer
, xOffsetForTabs
);
5782 PRUint32 transformedOffset
= provider
.GetStart().GetSkippedOffset();
5784 // The metrics for the text go in here
5785 gfxTextRun::Metrics textMetrics
;
5786 PRBool needTightBoundingBox
= IsFloatingFirstLetterChild();
5788 NS_ASSERTION(!(NS_REFLOW_CALC_BOUNDING_METRICS
& aMetrics
.mFlags
),
5789 "We shouldn't be passed NS_REFLOW_CALC_BOUNDING_METRICS anymore");
5792 PRInt32 limitLength
= length
;
5793 PRInt32 forceBreak
= lineLayout
.GetForcedBreakPosition(mContent
);
5794 PRBool forceBreakAfter
= PR_FALSE
;
5795 if (forceBreak
>= offset
+ length
) {
5796 forceBreakAfter
= forceBreak
== offset
+ length
;
5797 // The break is not within the text considered for this textframe.
5800 if (forceBreak
>= 0) {
5801 limitLength
= forceBreak
- offset
;
5802 NS_ASSERTION(limitLength
>= 0, "Weird break found!");
5804 // This is the heart of text reflow right here! We don't know where
5805 // to break, so we need to see how much text fits in the available width.
5806 PRUint32 transformedLength
;
5807 if (offset
+ limitLength
>= PRInt32(frag
->GetLength())) {
5808 NS_ASSERTION(offset
+ limitLength
== PRInt32(frag
->GetLength()),
5809 "Content offset/length out of bounds");
5810 NS_ASSERTION(flowEndInTextRun
>= transformedOffset
,
5811 "Negative flow length?");
5812 transformedLength
= flowEndInTextRun
- transformedOffset
;
5814 // we're not looking at all the content, so we need to compute the
5815 // length of the transformed substring we're looking at
5816 gfxSkipCharsIterator
iter(provider
.GetStart());
5817 iter
.SetOriginalOffset(offset
+ limitLength
);
5818 transformedLength
= iter
.GetSkippedOffset() - transformedOffset
;
5820 PRUint32 transformedLastBreak
= 0;
5821 PRBool usedHyphenation
;
5822 gfxFloat trimmedWidth
= 0;
5823 gfxFloat availWidth
= aReflowState
.availableWidth
;
5824 PRBool canTrimTrailingWhitespace
= !textStyle
->WhiteSpaceIsSignificant();
5825 PRInt32 unusedOffset
;
5826 gfxBreakPriority breakPriority
;
5827 lineLayout
.GetLastOptionalBreakPosition(&unusedOffset
, &breakPriority
);
5828 PRUint32 transformedCharsFit
=
5829 mTextRun
->BreakAndMeasureText(transformedOffset
, transformedLength
,
5830 (GetStateBits() & TEXT_START_OF_LINE
) != 0,
5832 &provider
, !lineLayout
.LineIsBreakable(),
5833 canTrimTrailingWhitespace
? &trimmedWidth
: nsnull
,
5834 &textMetrics
, needTightBoundingBox
, ctx
,
5835 &usedHyphenation
, &transformedLastBreak
,
5836 textStyle
->WordCanWrap(), &breakPriority
);
5837 // The "end" iterator points to the first character after the string mapped
5838 // by this frame. Basically, its original-string offset is offset+charsFit
5839 // after we've computed charsFit.
5840 gfxSkipCharsIterator
end(provider
.GetEndHint());
5841 end
.SetSkippedOffset(transformedOffset
+ transformedCharsFit
);
5842 PRInt32 charsFit
= end
.GetOriginalOffset() - offset
;
5843 if (offset
+ charsFit
== newLineOffset
) {
5844 // We broke before a trailing preformatted '\n'. The newline should
5845 // be assigned to this frame. Note that newLineOffset will be -1 if
5846 // there was no preformatted newline, so we wouldn't get here in that
5850 // That might have taken us beyond our assigned content range (because
5851 // we might have advanced over some skipped chars that extend outside
5852 // this frame), so get back in.
5853 PRInt32 lastBreak
= -1;
5854 if (charsFit
>= limitLength
) {
5855 charsFit
= limitLength
;
5856 if (transformedLastBreak
!= PR_UINT32_MAX
) {
5857 // lastBreak is needed.
5858 // This may set lastBreak greater than 'length', but that's OK
5859 lastBreak
= end
.ConvertSkippedToOriginal(transformedOffset
+ transformedLastBreak
);
5861 end
.SetOriginalOffset(offset
+ charsFit
);
5862 // If we were forced to fit, and the break position is after a soft hyphen,
5863 // note that this is a hyphenation break.
5864 if ((forceBreak
>= 0 || forceBreakAfter
) &&
5865 HasSoftHyphenBefore(frag
, mTextRun
, offset
, end
)) {
5866 usedHyphenation
= PR_TRUE
;
5869 if (usedHyphenation
) {
5870 // Fix up metrics to include hyphen
5871 AddHyphenToMetrics(this, mTextRun
, &textMetrics
, needTightBoundingBox
, ctx
);
5872 AddStateBits(TEXT_HYPHEN_BREAK
| TEXT_HAS_NONCOLLAPSED_CHARACTERS
);
5875 gfxFloat trimmableWidth
= 0;
5876 PRBool brokeText
= forceBreak
>= 0 || transformedCharsFit
< transformedLength
;
5877 if (canTrimTrailingWhitespace
) {
5878 // Optimization: if we trimmed trailing whitespace, and we can be sure
5879 // this frame will be at the end of the line, then leave it trimmed off.
5880 // Otherwise we have to undo the trimming, in case we're not at the end of
5881 // the line. (If we actually do end up at the end of the line, we'll have
5882 // to trim it off again in TrimTrailingWhiteSpace, and we'd like to avoid
5883 // having to re-do it.)
5885 // We're definitely going to break so our trailing whitespace should
5886 // definitely be timmed. Record that we've already done it.
5887 AddStateBits(TEXT_TRIMMED_TRAILING_WHITESPACE
);
5889 // We might not be at the end of the line. (Note that even if this frame
5890 // ends in breakable whitespace, it might not be at the end of the line
5891 // because it might be followed by breakable, but preformatted, whitespace.)
5892 // Undo the trimming.
5893 textMetrics
.mAdvanceWidth
+= trimmedWidth
;
5894 trimmableWidth
= trimmedWidth
;
5895 if (mTextRun
->IsRightToLeft()) {
5896 // Space comes before text, so the bounding box is moved to the
5897 // right by trimmdWidth
5898 textMetrics
.mBoundingBox
.MoveBy(gfxPoint(trimmedWidth
, 0));
5903 if (!brokeText
&& lastBreak
>= 0) {
5904 // Since everything fit and no break was forced,
5905 // record the last break opportunity
5906 NS_ASSERTION(textMetrics
.mAdvanceWidth
- trimmableWidth
<= aReflowState
.availableWidth
,
5907 "If the text doesn't fit, and we have a break opportunity, why didn't MeasureText use it?");
5908 lineLayout
.NotifyOptionalBreakPosition(mContent
, lastBreak
, PR_TRUE
, breakPriority
);
5911 PRInt32 contentLength
= offset
+ charsFit
- GetContentOffset();
5913 /////////////////////////////////////////////////////////////////////
5914 // Compute output metrics
5915 /////////////////////////////////////////////////////////////////////
5917 // first-letter frames should use the tight bounding box metrics for ascent/descent
5918 // for good drop-cap effects
5919 if (GetStateBits() & TEXT_FIRST_LETTER
) {
5920 textMetrics
.mAscent
= PR_MAX(0, -textMetrics
.mBoundingBox
.Y());
5921 textMetrics
.mDescent
= PR_MAX(0, textMetrics
.mBoundingBox
.YMost());
5924 // Setup metrics for caller
5925 // Disallow negative widths
5926 aMetrics
.width
= NSToCoordCeil(PR_MAX(0, textMetrics
.mAdvanceWidth
));
5928 if (transformedCharsFit
== 0 && !usedHyphenation
) {
5929 aMetrics
.ascent
= 0;
5930 aMetrics
.height
= 0;
5931 } else if (needTightBoundingBox
) {
5932 // Use actual text metrics for floating first letter frame.
5933 aMetrics
.ascent
= NSToCoordCeil(textMetrics
.mAscent
);
5934 aMetrics
.height
= aMetrics
.ascent
+ NSToCoordCeil(textMetrics
.mDescent
);
5936 // Otherwise, ascent should contain the overline drawable area.
5937 // And also descent should contain the underline drawable area.
5938 // nsIFontMetrics::GetMaxAscent/GetMaxDescent contains them.
5939 nscoord fontAscent
, fontDescent
;
5940 nsIFontMetrics
* fm
= provider
.GetFontMetrics();
5941 fm
->GetMaxAscent(fontAscent
);
5942 fm
->GetMaxDescent(fontDescent
);
5943 aMetrics
.ascent
= PR_MAX(NSToCoordCeil(textMetrics
.mAscent
), fontAscent
);
5944 nscoord descent
= PR_MAX(NSToCoordCeil(textMetrics
.mDescent
), fontDescent
);
5945 aMetrics
.height
= aMetrics
.ascent
+ descent
;
5948 NS_ASSERTION(aMetrics
.ascent
>= 0, "Negative ascent???");
5949 NS_ASSERTION(aMetrics
.height
- aMetrics
.ascent
>= 0, "Negative descent???");
5951 mAscent
= aMetrics
.ascent
;
5953 // Handle text that runs outside its normal bounds.
5954 nsRect boundingBox
= RoundOut(textMetrics
.mBoundingBox
) + nsPoint(0, mAscent
);
5955 aMetrics
.mOverflowArea
.UnionRect(boundingBox
,
5956 nsRect(0, 0, aMetrics
.width
, aMetrics
.height
));
5958 UnionTextDecorationOverflow(aPresContext
, provider
, &aMetrics
.mOverflowArea
);
5960 /////////////////////////////////////////////////////////////////////
5961 // Clean up, update state
5962 /////////////////////////////////////////////////////////////////////
5964 // If all our characters are discarded or collapsed, then trimmable width
5965 // from the last textframe should be preserved. Otherwise the trimmable width
5966 // from this textframe overrides. (Currently in CSS trimmable width can be
5967 // at most one space so there's no way for trimmable width from a previous
5968 // frame to accumulate with trimmable width from this frame.)
5969 if (transformedCharsFit
> 0) {
5970 lineLayout
.SetTrimmableWidth(NSToCoordFloor(trimmableWidth
));
5971 AddStateBits(TEXT_HAS_NONCOLLAPSED_CHARACTERS
);
5973 if (charsFit
> 0 && charsFit
== length
&&
5974 HasSoftHyphenBefore(frag
, mTextRun
, offset
, end
)) {
5975 // Record a potential break after final soft hyphen
5976 lineLayout
.NotifyOptionalBreakPosition(mContent
, offset
+ length
,
5977 textMetrics
.mAdvanceWidth
+ provider
.GetHyphenWidth() <= availWidth
,
5980 PRBool breakAfter
= forceBreakAfter
;
5981 if (!breakAfter
&& charsFit
== length
&&
5982 transformedOffset
+ transformedLength
== mTextRun
->GetLength() &&
5983 (mTextRun
->GetFlags() & nsTextFrameUtils::TEXT_HAS_TRAILING_BREAK
)) {
5984 // We placed all the text in the textrun and we have a break opportunity at
5985 // the end of the textrun. We need to record it because the following
5986 // content may not care about nsLineBreaker.
5988 // Note that because we didn't break, we can be sure that (thanks to the
5989 // code up above) textMetrics.mAdvanceWidth includes the width of any
5990 // trailing whitespace. So we need to subtract trimmableWidth here
5991 // because if we did break at this point, that much width would be trimmed.
5992 if (textMetrics
.mAdvanceWidth
- trimmableWidth
> availWidth
) {
5993 breakAfter
= PR_TRUE
;
5995 lineLayout
.NotifyOptionalBreakPosition(mContent
, offset
+ length
, PR_TRUE
,
5999 if (completedFirstLetter
) {
6000 lineLayout
.SetFirstLetterStyleOK(PR_FALSE
);
6003 // Compute reflow status
6004 aStatus
= contentLength
== maxContentLength
6005 ? NS_FRAME_COMPLETE
: NS_FRAME_NOT_COMPLETE
;
6007 if (charsFit
== 0 && length
> 0) {
6008 // Couldn't place any text
6009 aStatus
= NS_INLINE_LINE_BREAK_BEFORE();
6010 } else if (contentLength
> 0 && mContentOffset
+ contentLength
- 1 == newLineOffset
) {
6012 aStatus
= NS_INLINE_LINE_BREAK_AFTER(aStatus
);
6013 lineLayout
.SetLineEndsInBR(PR_TRUE
);
6014 } else if (breakAfter
) {
6015 aStatus
= NS_INLINE_LINE_BREAK_AFTER(aStatus
);
6017 if (completedFirstLetter
) {
6018 lineLayout
.SetFirstLetterStyleOK(PR_FALSE
);
6019 aStatus
|= NS_INLINE_BREAK_FIRST_LETTER_COMPLETE
;
6022 // Compute space and letter counts for justification, if required
6023 if (!textStyle
->WhiteSpaceIsSignificant() &&
6024 lineContainer
->GetStyleText()->mTextAlign
== NS_STYLE_TEXT_ALIGN_JUSTIFY
) {
6025 AddStateBits(TEXT_JUSTIFICATION_ENABLED
); // This will include a space for trailing whitespace, if any is present.
6026 // This is corrected for in nsLineLayout::TrimWhiteSpaceIn.
6027 PRInt32 numJustifiableCharacters
=
6028 provider
.ComputeJustifiableCharacters(offset
, charsFit
);
6030 NS_ASSERTION(numJustifiableCharacters
<= charsFit
,
6031 "Bad justifiable character count");
6032 lineLayout
.SetTextJustificationWeights(numJustifiableCharacters
,
6033 charsFit
- numJustifiableCharacters
);
6036 SetLength(contentLength
);
6038 Invalidate(nsRect(nsPoint(0, 0), GetSize()));
6042 printf(": desiredSize=%d,%d(b=%d) status=%x\n",
6043 aMetrics
.width
, aMetrics
.height
, aMetrics
.ascent
,
6046 NS_FRAME_SET_TRUNCATION(aStatus
, aReflowState
, aMetrics
);
6050 /* virtual */ PRBool
6051 nsTextFrame::CanContinueTextRun() const
6053 // We can continue a text run through a text frame
6057 nsTextFrame::TrimOutput
6058 nsTextFrame::TrimTrailingWhiteSpace(nsIRenderingContext
* aRC
)
6061 result
.mChanged
= PR_FALSE
;
6062 result
.mLastCharIsJustifiable
= PR_FALSE
;
6063 result
.mDeltaWidth
= 0;
6065 AddStateBits(TEXT_END_OF_LINE
);
6067 PRInt32 contentLength
= GetContentLength();
6071 gfxContext
* ctx
= aRC
->ThebesContext();
6072 gfxSkipCharsIterator start
= EnsureTextRun(ctx
);
6073 NS_ENSURE_TRUE(mTextRun
, result
);
6075 PRUint32 trimmedStart
= start
.GetSkippedOffset();
6077 const nsTextFragment
* frag
= mContent
->GetText();
6078 TrimmedOffsets trimmed
= GetTrimmedOffsets(frag
, PR_TRUE
);
6079 gfxSkipCharsIterator trimmedEndIter
= start
;
6080 const nsStyleText
* textStyle
= GetStyleText();
6082 PRUint32 trimmedEnd
= trimmedEndIter
.ConvertOriginalToSkipped(trimmed
.GetEnd());
6084 if (GetStateBits() & TEXT_TRIMMED_TRAILING_WHITESPACE
) {
6085 // We pre-trimmed this frame, so the last character is justifiable
6086 result
.mLastCharIsJustifiable
= PR_TRUE
;
6087 } else if (trimmed
.GetEnd() < GetContentEnd()) {
6088 gfxSkipCharsIterator end
= trimmedEndIter
;
6089 PRUint32 endOffset
= end
.ConvertOriginalToSkipped(GetContentOffset() + contentLength
);
6090 if (trimmedEnd
< endOffset
) {
6091 // We can't be dealing with tabs here ... they wouldn't be trimmed. So it's
6092 // OK to pass null for the line container.
6093 PropertyProvider
provider(mTextRun
, textStyle
, frag
, this, start
, contentLength
,
6095 delta
= mTextRun
->GetAdvanceWidth(trimmedEnd
, endOffset
- trimmedEnd
, &provider
);
6096 // non-compressed whitespace being skipped at end of line -> justifiable
6097 // XXX should we actually *count* justifiable characters that should be
6098 // removed from the overall count? I think so...
6099 result
.mLastCharIsJustifiable
= PR_TRUE
;
6100 result
.mChanged
= PR_TRUE
;
6104 if (!result
.mLastCharIsJustifiable
&&
6105 (GetStateBits() & TEXT_JUSTIFICATION_ENABLED
)) {
6106 // Check if any character in the last cluster is justifiable
6107 PropertyProvider
provider(mTextRun
, textStyle
, frag
, this, start
, contentLength
,
6109 PRBool isCJK
= IsChineseJapaneseLangGroup(this);
6110 gfxSkipCharsIterator
justificationStart(start
), justificationEnd(trimmedEndIter
);
6111 provider
.FindJustificationRange(&justificationStart
, &justificationEnd
);
6114 for (i
= justificationEnd
.GetOriginalOffset(); i
< trimmed
.GetEnd(); ++i
) {
6115 if (IsJustifiableCharacter(frag
, i
, isCJK
)) {
6116 result
.mLastCharIsJustifiable
= PR_TRUE
;
6121 gfxFloat advanceDelta
;
6122 mTextRun
->SetLineBreaks(trimmedStart
, trimmedEnd
- trimmedStart
,
6123 (GetStateBits() & TEXT_START_OF_LINE
) != 0, PR_TRUE
,
6124 &advanceDelta
, ctx
);
6125 if (advanceDelta
!= 0) {
6126 result
.mChanged
= PR_TRUE
;
6129 // aDeltaWidth is *subtracted* from our width.
6130 // If advanceDelta is positive then setting the line break made us longer,
6131 // so aDeltaWidth could go negative.
6132 result
.mDeltaWidth
= NSToCoordFloor(delta
- advanceDelta
);
6133 // If aDeltaWidth goes negative, that means this frame might not actually fit
6134 // anymore!!! We need higher level line layout to recover somehow.
6135 // If it's because the frame has a soft hyphen that is now being displayed,
6136 // this should actually be OK, because our reflow recorded the break
6137 // opportunity that allowed the soft hyphen to be used, and we wouldn't
6138 // have recorded the opportunity unless the hyphen fit (or was the first
6139 // opportunity on the line).
6140 // Otherwise this can/ really only happen when we have glyphs with special
6141 // shapes at the end of lines, I think. Breaking inside a kerning pair won't
6142 // do it because that would mean we broke inside this textrun, and
6143 // BreakAndMeasureText should make sure the resulting shaped substring fits.
6144 // Maybe if we passed a maxTextLength? But that only happens at direction
6145 // changes (so we wouldn't kern across the boundary) or for first-letter
6146 // (which always fits because it starts the line!).
6147 NS_WARN_IF_FALSE(result
.mDeltaWidth
>= 0,
6148 "Negative deltawidth, something odd is happening");
6152 printf(": trim => %d\n", result
.mDeltaWidth
);
6158 nsTextFrame::RecomputeOverflowRect()
6160 gfxSkipCharsIterator iter
= EnsureTextRun();
6162 return nsRect(nsPoint(0,0), GetSize());
6164 PropertyProvider
provider(this, iter
);
6165 provider
.InitializeForDisplay(PR_TRUE
);
6167 gfxTextRun::Metrics textMetrics
=
6168 mTextRun
->MeasureText(provider
.GetStart().GetSkippedOffset(),
6169 ComputeTransformedLength(provider
), PR_FALSE
, nsnull
,
6172 nsRect boundingBox
= RoundOut(textMetrics
.mBoundingBox
) + nsPoint(0, mAscent
);
6173 boundingBox
.UnionRect(boundingBox
,
6174 nsRect(nsPoint(0,0), GetSize()));
6176 UnionTextDecorationOverflow(PresContext(), provider
, &boundingBox
);
6181 static PRUnichar
TransformChar(const nsStyleText
* aStyle
, gfxTextRun
* aTextRun
,
6182 PRUint32 aSkippedOffset
, PRUnichar aChar
)
6184 if (aChar
== '\n') {
6185 return aStyle
->NewlineIsSignificant() ? aChar
: ' ';
6187 switch (aStyle
->mTextTransform
) {
6188 case NS_STYLE_TEXT_TRANSFORM_LOWERCASE
:
6189 nsContentUtils::GetCaseConv()->ToLower(aChar
, &aChar
);
6191 case NS_STYLE_TEXT_TRANSFORM_UPPERCASE
:
6192 nsContentUtils::GetCaseConv()->ToUpper(aChar
, &aChar
);
6194 case NS_STYLE_TEXT_TRANSFORM_CAPITALIZE
:
6195 if (aTextRun
->CanBreakLineBefore(aSkippedOffset
)) {
6196 nsContentUtils::GetCaseConv()->ToTitle(aChar
, &aChar
);
6204 nsresult
nsTextFrame::GetRenderedText(nsAString
* aAppendToString
,
6205 gfxSkipChars
* aSkipChars
,
6206 gfxSkipCharsIterator
* aSkipIter
,
6207 PRUint32 aSkippedStartOffset
,
6208 PRUint32 aSkippedMaxLength
)
6210 // The handling of aSkippedStartOffset and aSkippedMaxLength could be more efficient...
6211 gfxSkipCharsBuilder skipCharsBuilder
;
6212 nsTextFrame
* textFrame
;
6213 const nsTextFragment
* textFrag
= mContent
->GetText();
6214 PRUint32 keptCharsLength
= 0;
6215 PRUint32 validCharsLength
= 0;
6217 // Build skipChars and copy text, for each text frame in this continuation block
6218 for (textFrame
= this; textFrame
;
6219 textFrame
= static_cast<nsTextFrame
*>(textFrame
->GetNextContinuation())) {
6220 // For each text frame continuation in this block ...
6222 // Ensure the text run and grab the gfxSkipCharsIterator for it
6223 gfxSkipCharsIterator iter
= textFrame
->EnsureTextRun();
6224 if (!textFrame
->mTextRun
)
6225 return NS_ERROR_FAILURE
;
6227 // Skip to the start of the text run, past ignored chars at start of line
6228 // XXX In the future we may decide to trim extra spaces before a hard line
6229 // break, in which case we need to accurately detect those sitations and
6230 // call GetTrimmedOffsets() with PR_TRUE to trim whitespace at the line's end
6231 TrimmedOffsets trimmedContentOffsets
= textFrame
->GetTrimmedOffsets(textFrag
, PR_FALSE
);
6232 PRInt32 startOfLineSkipChars
= trimmedContentOffsets
.mStart
- textFrame
->mContentOffset
;
6233 if (startOfLineSkipChars
> 0) {
6234 skipCharsBuilder
.SkipChars(startOfLineSkipChars
);
6235 iter
.SetOriginalOffset(trimmedContentOffsets
.mStart
);
6238 // Keep and copy the appropriate chars withing the caller's requested range
6239 const nsStyleText
* textStyle
= textFrame
->GetStyleText();
6240 while (iter
.GetOriginalOffset() < trimmedContentOffsets
.GetEnd() &&
6241 keptCharsLength
< aSkippedMaxLength
) {
6242 // For each original char from content text
6243 if (iter
.IsOriginalCharSkipped() || ++validCharsLength
<= aSkippedStartOffset
) {
6244 skipCharsBuilder
.SkipChar();
6247 skipCharsBuilder
.KeepChar();
6248 if (aAppendToString
) {
6249 aAppendToString
->Append(
6250 TransformChar(textStyle
, textFrame
->mTextRun
, iter
.GetSkippedOffset(),
6251 textFrag
->CharAt(iter
.GetOriginalOffset())));
6254 iter
.AdvanceOriginal(1);
6256 if (keptCharsLength
>= aSkippedMaxLength
) {
6257 break; // Already past the end, don't build string or gfxSkipCharsIter anymore
6262 aSkipChars
->TakeFrom(&skipCharsBuilder
); // Copy skipChars into aSkipChars
6264 // Caller must provide both pointers in order to retrieve a gfxSkipCharsIterator,
6265 // because the gfxSkipCharsIterator holds a weak pointer to the gfxSkipCars.
6266 *aSkipIter
= gfxSkipCharsIterator(*aSkipChars
, GetContentLength());
6274 // Translate the mapped content into a string that's printable
6276 nsTextFrame::ToCString(nsCString
& aBuf
, PRInt32
* aTotalContentLength
) const
6278 // Get the frames text content
6279 const nsTextFragment
* frag
= mContent
->GetText();
6284 // Compute the total length of the text content.
6285 *aTotalContentLength
= frag
->GetLength();
6287 PRInt32 contentLength
= GetContentLength();
6288 // Set current fragment and current fragment offset
6289 if (0 == contentLength
) {
6292 PRInt32 fragOffset
= GetContentOffset();
6293 PRInt32 n
= fragOffset
+ contentLength
;
6294 while (fragOffset
< n
) {
6295 PRUnichar ch
= frag
->CharAt(fragOffset
++);
6297 aBuf
.AppendLiteral("\\r");
6298 } else if (ch
== '\n') {
6299 aBuf
.AppendLiteral("\\n");
6300 } else if (ch
== '\t') {
6301 aBuf
.AppendLiteral("\\t");
6302 } else if ((ch
< ' ') || (ch
>= 127)) {
6303 aBuf
.Append(nsPrintfCString("\\u%04x", ch
));
6312 nsTextFrame::GetType() const
6314 return nsGkAtoms::textFrame
;
6317 /* virtual */ PRBool
6318 nsTextFrame::IsEmpty()
6320 NS_ASSERTION(!(mState
& TEXT_IS_ONLY_WHITESPACE
) ||
6321 !(mState
& TEXT_ISNOT_ONLY_WHITESPACE
),
6324 // XXXldb Should this check compatibility mode as well???
6325 const nsStyleText
* textStyle
= GetStyleText();
6326 if (textStyle
->WhiteSpaceIsSignificant()) {
6327 // XXX shouldn't we return true if the length is zero?
6331 if (mState
& TEXT_ISNOT_ONLY_WHITESPACE
) {
6335 if (mState
& TEXT_IS_ONLY_WHITESPACE
) {
6339 PRBool isEmpty
= IsAllWhitespace(mContent
->GetText(),
6340 textStyle
->mWhiteSpace
!= NS_STYLE_WHITESPACE_PRE_LINE
);
6341 mState
|= (isEmpty
? TEXT_IS_ONLY_WHITESPACE
: TEXT_ISNOT_ONLY_WHITESPACE
);
6347 nsTextFrame::GetFrameName(nsAString
& aResult
) const
6349 return MakeFrameName(NS_LITERAL_STRING("Text"), aResult
);
6352 NS_IMETHODIMP_(nsFrameState
)
6353 nsTextFrame::GetDebugStateBits() const
6355 // mask out our emptystate flags; those are just caches
6356 return nsFrame::GetDebugStateBits() &
6357 ~(TEXT_WHITESPACE_FLAGS
| TEXT_REFLOW_FLAGS
);
6361 nsTextFrame::List(FILE* out
, PRInt32 aIndent
) const
6364 IndentBy(out
, aIndent
);
6366 #ifdef DEBUG_waterson
6367 fprintf(out
, " [parent=%p]", mParent
);
6370 fprintf(out
, " [view=%p]", static_cast<void*>(GetView()));
6373 PRInt32 totalContentLength
;
6375 ToCString(tmp
, &totalContentLength
);
6377 // Output the first/last content offset and prev/next in flow info
6378 PRBool isComplete
= GetContentEnd() == totalContentLength
;
6379 fprintf(out
, "[%d,%d,%c] ",
6380 GetContentOffset(), GetContentLength(),
6381 isComplete
? 'T':'F');
6383 if (nsnull
!= mNextSibling
) {
6384 fprintf(out
, " next=%p", static_cast<void*>(mNextSibling
));
6386 nsIFrame
* prevContinuation
= GetPrevContinuation();
6387 if (nsnull
!= prevContinuation
) {
6388 fprintf(out
, " prev-continuation=%p", static_cast<void*>(prevContinuation
));
6390 if (nsnull
!= mNextContinuation
) {
6391 fprintf(out
, " next-continuation=%p", static_cast<void*>(mNextContinuation
));
6394 // Output the rect and state
6395 fprintf(out
, " {%d,%d,%d,%d}", mRect
.x
, mRect
.y
, mRect
.width
, mRect
.height
);
6397 if (mState
& NS_FRAME_SELECTED_CONTENT
) {
6398 fprintf(out
, " [state=%08x] SELECTED", mState
);
6400 fprintf(out
, " [state=%08x]", mState
);
6403 fprintf(out
, " [content=%p]", static_cast<void*>(mContent
));
6404 if (GetStateBits() & NS_FRAME_OUTSIDE_CHILDREN
) {
6405 nsRect overflowArea
= GetOverflowRect();
6406 fprintf(out
, " [overflow=%d,%d,%d,%d]", overflowArea
.x
, overflowArea
.y
,
6407 overflowArea
.width
, overflowArea
.height
);
6409 fprintf(out
, " sc=%p", static_cast<void*>(mStyleContext
));
6410 nsIAtom
* pseudoTag
= mStyleContext
->GetPseudoType();
6412 nsAutoString atomString
;
6413 pseudoTag
->ToString(atomString
);
6414 fprintf(out
, " pst=%s",
6415 NS_LossyConvertUTF16toASCII(atomString
).get());
6422 IndentBy(out
, aIndent
);
6424 fputs(tmp
.get(), out
);
6428 IndentBy(out
, aIndent
);
6436 nsTextFrame::AdjustOffsetsForBidi(PRInt32 aStart
, PRInt32 aEnd
)
6438 AddStateBits(NS_FRAME_IS_BIDI
);
6441 * After Bidi resolution we may need to reassign text runs.
6442 * This is called during bidi resolution from the block container, so we
6443 * shouldn't be holding a local reference to a textrun anywhere.
6447 nsTextFrame
* prev
= static_cast<nsTextFrame
*>(GetPrevContinuation());
6449 // the bidi resolver can be very evil when columns/pages are involved. Don't
6450 // let it violate our invariants.
6451 PRInt32 prevOffset
= prev
->GetContentOffset();
6452 aStart
= PR_MAX(aStart
, prevOffset
);
6453 aEnd
= PR_MAX(aEnd
, prevOffset
);
6454 prev
->ClearTextRun();
6457 mContentOffset
= aStart
;
6458 SetLength(aEnd
- aStart
);
6462 * @return PR_TRUE if this text frame ends with a newline character. It should return
6463 * PR_FALSE if it is not a text frame.
6466 nsTextFrame::HasTerminalNewline() const
6468 return ::HasTerminalNewline(this);
6472 nsTextFrame::IsAtEndOfLine() const
6474 return (GetStateBits() & TEXT_END_OF_LINE
) != 0;