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 <matspal@gmail.com>
33 * Uri Bernstein <uriber@gmail.com>
34 * Stephen Blackheath <entangled.mooched.stephen@blacksapphire.com>
35 * Michael Ventnor <m.ventnor@gmail.com>
36 * Ehsan Akhgari <ehsan.akhgari@gmail.com>
38 * Alternatively, the contents of this file may be used under the terms of
39 * either of the GNU General Public License Version 2 or later (the "GPL"),
40 * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
41 * in which case the provisions of the GPL or the LGPL are applicable instead
42 * of those above. If you wish to allow use of your version of this file only
43 * under the terms of either the GPL or the LGPL, and not to allow others to
44 * use your version of this file under the terms of the MPL, indicate your
45 * decision by deleting the provisions above and replace them with the notice
46 * and other provisions required by the GPL or the LGPL. If you do not delete
47 * the provisions above, a recipient may use your version of this file under
48 * the terms of any one of the MPL, the GPL or the LGPL.
50 * ***** END LICENSE BLOCK ***** */
52 /* rendering object for textual content of elements */
55 #include "nsHTMLParts.h"
57 #include "nsSplittableFrame.h"
58 #include "nsLineLayout.h"
60 #include "nsUnicharUtils.h"
61 #include "nsPresContext.h"
62 #include "nsIContent.h"
63 #include "nsStyleConsts.h"
64 #include "nsStyleContext.h"
66 #include "nsIFontMetrics.h"
67 #include "nsIRenderingContext.h"
68 #include "nsIPresShell.h"
71 #include "nsIDOMText.h"
72 #include "nsIDocument.h"
73 #include "nsIDeviceContext.h"
74 #include "nsCSSPseudoElements.h"
75 #include "nsCSSFrameConstructor.h"
76 #include "nsCompatibility.h"
77 #include "nsCSSColorUtils.h"
78 #include "nsLayoutUtils.h"
79 #include "nsDisplayList.h"
81 #include "nsPlaceholderFrame.h"
82 #include "nsTextFrameUtils.h"
83 #include "nsTextRunTransformations.h"
84 #include "nsFrameManager.h"
85 #include "nsTextFrameTextRunCache.h"
86 #include "nsExpirationTracker.h"
87 #include "nsTextFrame.h"
88 #include "nsIUGenCategory.h"
89 #include "nsUnicharUtilCIID.h"
91 #include "nsTextFragment.h"
92 #include "nsGkAtoms.h"
93 #include "nsFrameSelection.h"
94 #include "nsISelection.h"
95 #include "nsIDOMRange.h"
96 #include "nsILookAndFeel.h"
97 #include "nsCSSRendering.h"
98 #include "nsContentUtils.h"
99 #include "nsLineBreaker.h"
100 #include "nsIWordBreaker.h"
101 #include "nsGenericDOMDataNode.h"
103 #include "nsILineIterator.h"
105 #include "nsIServiceManager.h"
107 #include "nsAccessibilityService.h"
109 #include "nsAutoPtr.h"
111 #include "nsBidiFrames.h"
112 #include "nsBidiPresUtils.h"
113 #include "nsBidiUtils.h"
115 #include "nsIThebesFontMetrics.h"
117 #include "gfxContext.h"
118 #include "gfxTextRunWordCache.h"
119 #include "gfxImageSurface.h"
120 #include "mozilla/dom/Element.h"
132 using namespace mozilla
;
133 using namespace mozilla::dom
;
135 static void DestroyTabWidth(void* aPropertyValue
)
137 delete static_cast<nsTArray
<gfxFloat
>*>(aPropertyValue
);
140 NS_DECLARE_FRAME_PROPERTY(TabWidthProperty
, DestroyTabWidth
)
142 NS_DECLARE_FRAME_PROPERTY(OffsetToFrameProperty
, nsnull
)
144 // The following flags are set during reflow
146 // This bit is set on the first frame in a continuation indicating
147 // that it was chopped short because of :first-letter style.
148 #define TEXT_FIRST_LETTER NS_FRAME_STATE_BIT(20)
149 // This bit is set on frames that are logically adjacent to the start of the
150 // line (i.e. no prior frame on line with actual displayed in-flow content).
151 #define TEXT_START_OF_LINE NS_FRAME_STATE_BIT(21)
152 // This bit is set on frames that are logically adjacent to the end of the
153 // line (i.e. no following on line with actual displayed in-flow content).
154 #define TEXT_END_OF_LINE NS_FRAME_STATE_BIT(22)
155 // This bit is set on frames that end with a hyphenated break.
156 #define TEXT_HYPHEN_BREAK NS_FRAME_STATE_BIT(23)
157 // This bit is set on frames that trimmed trailing whitespace characters when
158 // calculating their width during reflow.
159 #define TEXT_TRIMMED_TRAILING_WHITESPACE NS_FRAME_STATE_BIT(24)
160 // This bit is set on frames that have justification enabled. We record
161 // this in a state bit because we don't always have the containing block
162 // easily available to check text-align on.
163 #define TEXT_JUSTIFICATION_ENABLED NS_FRAME_STATE_BIT(25)
164 // Set this bit if the textframe has overflow area for IME/spellcheck underline.
165 #define TEXT_SELECTION_UNDERLINE_OVERFLOWED NS_FRAME_STATE_BIT(26)
167 #define TEXT_REFLOW_FLAGS \
168 (TEXT_FIRST_LETTER|TEXT_START_OF_LINE|TEXT_END_OF_LINE|TEXT_HYPHEN_BREAK| \
169 TEXT_TRIMMED_TRAILING_WHITESPACE|TEXT_JUSTIFICATION_ENABLED| \
170 TEXT_HAS_NONCOLLAPSED_CHARACTERS|TEXT_SELECTION_UNDERLINE_OVERFLOWED)
172 // Cache bits for IsEmpty().
173 // Set this bit if the textframe is known to be only collapsible whitespace.
174 #define TEXT_IS_ONLY_WHITESPACE NS_FRAME_STATE_BIT(27)
175 // Set this bit if the textframe is known to be not only collapsible whitespace.
176 #define TEXT_ISNOT_ONLY_WHITESPACE NS_FRAME_STATE_BIT(28)
178 #define TEXT_WHITESPACE_FLAGS (TEXT_IS_ONLY_WHITESPACE | \
179 TEXT_ISNOT_ONLY_WHITESPACE)
180 // This bit is set while the frame is registered as a blinking frame.
181 #define TEXT_BLINK_ON NS_FRAME_STATE_BIT(29)
183 // Set when this text frame is mentioned in the userdata for a textrun
184 #define TEXT_IN_TEXTRUN_USER_DATA NS_FRAME_STATE_BIT(30)
187 // #define TEXT_HAS_NONCOLLAPSED_CHARACTERS NS_FRAME_STATE_BIT(31)
189 // Whether this frame is cached in the Offset Frame Cache (OffsetToFrameProperty)
190 #define TEXT_IN_OFFSET_CACHE NS_FRAME_STATE_BIT(63)
195 * Text frames delegate work to gfxTextRun objects. The gfxTextRun object
196 * transforms text to positioned glyphs. It can report the geometry of the
197 * glyphs and paint them. Text frames configure gfxTextRuns by providing text,
198 * spacing, language, and other information.
200 * A gfxTextRun can cover more than one DOM text node. This is necessary to
201 * get kerning, ligatures and shaping for text that spans multiple text nodes
202 * but is all the same font. The userdata for a gfxTextRun object is a
203 * TextRunUserData* or an nsIFrame*.
205 * We go to considerable effort to make sure things work even if in-flow
206 * siblings have different style contexts (i.e., first-letter and first-line).
208 * Our convention is that unsigned integer character offsets are offsets into
209 * the transformed string. Signed integer character offsets are offsets into
212 * XXX currently we don't handle hyphenated breaks between text frames where the
213 * hyphen occurs at the end of the first text frame, e.g.
218 * We use an array of these objects to record which text frames
219 * are associated with the textrun. mStartFrame is the start of a list of
220 * text frames. Some sequence of its continuations are covered by the textrun.
221 * A content textnode can have at most one TextRunMappedFlow associated with it
222 * for a given textrun.
224 * mDOMOffsetToBeforeTransformOffset is added to DOM offsets for those frames to obtain
225 * the offset into the before-transformation text of the textrun. It can be
226 * positive (when a text node starts in the middle of a text run) or
227 * negative (when a text run starts in the middle of a text node). Of course
228 * it can also be zero.
230 struct TextRunMappedFlow
{
231 nsTextFrame
* mStartFrame
;
232 PRInt32 mDOMOffsetToBeforeTransformOffset
;
233 // The text mapped starts at mStartFrame->GetContentOffset() and is this long
234 PRUint32 mContentLength
;
238 * This is our user data for the textrun, when textRun->GetFlags() does not
239 * have TEXT_IS_SIMPLE_FLOW set. When TEXT_IS_SIMPLE_FLOW is set, there is
240 * just one flow, the textrun's user data pointer is a pointer to mStartFrame
241 * for that flow, mDOMOffsetToBeforeTransformOffset is zero, and mContentLength
242 * is the length of the text node.
244 struct TextRunUserData
{
245 TextRunMappedFlow
* mMappedFlows
;
246 PRInt32 mMappedFlowCount
;
248 PRUint32 mLastFlowIndex
;
252 * This helper object computes colors used for painting, and also IME
253 * underline information. The data is computed lazily and cached as necessary.
254 * These live for just the duration of one paint operation.
256 class nsTextPaintStyle
{
258 nsTextPaintStyle(nsTextFrame
* aFrame
);
260 nscolor
GetTextColor();
262 * Compute the colors for normally-selected text. Returns false if
263 * the normal selection is not being displayed.
265 PRBool
GetSelectionColors(nscolor
* aForeColor
,
266 nscolor
* aBackColor
);
267 void GetHighlightColors(nscolor
* aForeColor
,
268 nscolor
* aBackColor
);
269 void GetIMESelectionColors(PRInt32 aIndex
,
271 nscolor
* aBackColor
);
272 // if this returns PR_FALSE, we don't need to draw underline.
273 PRBool
GetSelectionUnderlineForPaint(PRInt32 aIndex
,
275 float* aRelativeSize
,
278 // if this returns PR_FALSE, we don't need to draw underline.
279 static PRBool
GetSelectionUnderline(nsPresContext
* aPresContext
,
282 float* aRelativeSize
,
285 nsPresContext
* PresContext() { return mPresContext
; }
295 static PRInt32
GetUnderlineStyleIndexForSelectionType(PRInt32 aSelectionType
)
297 switch (aSelectionType
) {
298 case nsISelectionController::SELECTION_IME_RAWINPUT
:
299 return eIndexRawInput
;
300 case nsISelectionController::SELECTION_IME_SELECTEDRAWTEXT
:
301 return eIndexSelRawText
;
302 case nsISelectionController::SELECTION_IME_CONVERTEDTEXT
:
303 return eIndexConvText
;
304 case nsISelectionController::SELECTION_IME_SELECTEDCONVERTEDTEXT
:
305 return eIndexSelConvText
;
306 case nsISelectionController::SELECTION_SPELLCHECK
:
307 return eIndexSpellChecker
;
309 NS_WARNING("non-IME selection type");
310 return eIndexRawInput
;
316 nsPresContext
* mPresContext
;
317 PRPackedBool mInitCommonColors
;
318 PRPackedBool mInitSelectionColors
;
322 PRInt16 mSelectionStatus
; // see nsIDocument.h SetDisplaySelection()
323 nscolor mSelectionTextColor
;
324 nscolor mSelectionBGColor
;
328 PRInt32 mSufficientContrast
;
329 nscolor mFrameBackgroundColor
;
331 // selection colors and underline info, the colors are resolved colors,
332 // i.e., the foreground color and background color are swapped if it's needed.
333 // And also line color will be resolved from them.
334 struct nsSelectionStyle
{
338 nscolor mUnderlineColor
;
339 PRUint8 mUnderlineStyle
;
340 float mUnderlineRelativeSize
;
342 nsSelectionStyle mSelectionStyle
[5];
344 // Color initializations
345 void InitCommonColors();
346 PRBool
InitSelectionColors();
348 nsSelectionStyle
* GetSelectionStyle(PRInt32 aIndex
);
349 void InitSelectionStyle(PRInt32 aIndex
);
351 PRBool
EnsureSufficientContrast(nscolor
*aForeColor
, nscolor
*aBackColor
);
353 nscolor
GetResolvedForeColor(nscolor aColor
, nscolor aDefaultForeColor
,
358 DestroyUserData(void* aUserData
)
360 TextRunUserData
* userData
= static_cast<TextRunUserData
*>(aUserData
);
362 nsMemory::Free(userData
);
367 * Remove |aTextRun| from the frame continuation chain starting at
368 * |aStartContinuation| if non-null, otherwise starting at |aFrame|.
369 * Unmark |aFrame| as a text run owner if it's the frame we start at.
370 * Return PR_TRUE if |aStartContinuation| is non-null and was found
371 * in the next-continuation chain of |aFrame|.
374 ClearAllTextRunReferences(nsTextFrame
* aFrame
, gfxTextRun
* aTextRun
,
375 nsTextFrame
* aStartContinuation
)
377 NS_PRECONDITION(aFrame
, "");
378 NS_PRECONDITION(!aStartContinuation
||
379 !aStartContinuation
->GetTextRun() ||
380 aStartContinuation
->GetTextRun() == aTextRun
,
381 "wrong aStartContinuation for this text run");
383 if (!aStartContinuation
|| aStartContinuation
== aFrame
) {
384 aFrame
->RemoveStateBits(TEXT_IN_TEXTRUN_USER_DATA
);
387 NS_ASSERTION(aFrame
->GetType() == nsGkAtoms::textFrame
, "Bad frame");
388 aFrame
= static_cast<nsTextFrame
*>(aFrame
->GetNextContinuation());
389 } while (aFrame
&& aFrame
!= aStartContinuation
);
391 PRBool found
= aStartContinuation
== aFrame
;
393 NS_ASSERTION(aFrame
->GetType() == nsGkAtoms::textFrame
, "Bad frame");
394 if (aFrame
->GetTextRun() != aTextRun
)
396 aFrame
->SetTextRun(nsnull
);
397 aFrame
= static_cast<nsTextFrame
*>(aFrame
->GetNextContinuation());
399 NS_POSTCONDITION(!found
|| aStartContinuation
, "how did we find null?");
404 * Kill all references to |aTextRun| starting at |aStartContinuation|.
405 * It could be referenced by any of its owners, and all their in-flows.
406 * If |aStartContinuation| is null then process all userdata frames
407 * and their continuations.
408 * @note the caller is expected to take care of possibly destroying the
409 * text run if all userdata frames were reset (userdata is deallocated
410 * by this function though). The caller can detect this has occured by
411 * checking |aTextRun->GetUserData() == nsnull|.
414 UnhookTextRunFromFrames(gfxTextRun
* aTextRun
, nsTextFrame
* aStartContinuation
)
416 if (!aTextRun
->GetUserData())
419 if (aTextRun
->GetFlags() & nsTextFrameUtils::TEXT_IS_SIMPLE_FLOW
) {
420 nsIFrame
* userDataFrame
= static_cast<nsIFrame
*>(aTextRun
->GetUserData());
422 ClearAllTextRunReferences(static_cast<nsTextFrame
*>(userDataFrame
),
423 aTextRun
, aStartContinuation
);
424 NS_ASSERTION(!aStartContinuation
|| found
,
425 "aStartContinuation wasn't found in simple flow text run");
426 if (!(userDataFrame
->GetStateBits() & TEXT_IN_TEXTRUN_USER_DATA
)) {
427 aTextRun
->SetUserData(nsnull
);
430 TextRunUserData
* userData
=
431 static_cast<TextRunUserData
*>(aTextRun
->GetUserData());
432 PRInt32 destroyFromIndex
= aStartContinuation
? -1 : 0;
433 for (PRInt32 i
= 0; i
< userData
->mMappedFlowCount
; ++i
) {
434 nsTextFrame
* userDataFrame
= userData
->mMappedFlows
[i
].mStartFrame
;
436 ClearAllTextRunReferences(userDataFrame
, aTextRun
,
439 if (userDataFrame
->GetStateBits() & TEXT_IN_TEXTRUN_USER_DATA
) {
440 destroyFromIndex
= i
+ 1;
443 destroyFromIndex
= i
;
445 aStartContinuation
= nsnull
;
448 NS_ASSERTION(destroyFromIndex
>= 0,
449 "aStartContinuation wasn't found in multi flow text run");
450 if (destroyFromIndex
== 0) {
451 DestroyUserData(userData
);
452 aTextRun
->SetUserData(nsnull
);
455 userData
->mMappedFlowCount
= destroyFromIndex
;
456 if (userData
->mLastFlowIndex
>= destroyFromIndex
) {
457 userData
->mLastFlowIndex
= destroyFromIndex
- 1;
463 class FrameTextRunCache
;
465 static FrameTextRunCache
*gTextRuns
= nsnull
;
468 * Cache textruns and expire them after 3*10 seconds of no use.
470 class FrameTextRunCache
: public nsExpirationTracker
<gfxTextRun
,3> {
472 enum { TIMEOUT_SECONDS
= 10 };
474 : nsExpirationTracker
<gfxTextRun
,3>(TIMEOUT_SECONDS
*1000) {}
475 ~FrameTextRunCache() {
479 void RemoveFromCache(gfxTextRun
* aTextRun
) {
480 if (aTextRun
->GetExpirationState()->IsTracked()) {
481 RemoveObject(aTextRun
);
483 if (aTextRun
->GetFlags() & gfxTextRunWordCache::TEXT_IN_CACHE
) {
484 gfxTextRunWordCache::RemoveTextRun(aTextRun
);
488 // This gets called when the timeout has expired on a gfxTextRun
489 virtual void NotifyExpired(gfxTextRun
* aTextRun
) {
490 UnhookTextRunFromFrames(aTextRun
, nsnull
);
491 RemoveFromCache(aTextRun
);
497 MakeTextRun(const PRUnichar
*aText
, PRUint32 aLength
,
498 gfxFontGroup
*aFontGroup
, const gfxFontGroup::Parameters
* aParams
,
501 nsAutoPtr
<gfxTextRun
> textRun
;
503 textRun
= aFontGroup
->MakeEmptyTextRun(aParams
, aFlags
);
504 } else if (aLength
== 1 && aText
[0] == ' ') {
505 textRun
= aFontGroup
->MakeSpaceTextRun(aParams
, aFlags
);
507 textRun
= gfxTextRunWordCache::MakeTextRun(aText
, aLength
, aFontGroup
,
512 nsresult rv
= gTextRuns
->AddObject(textRun
);
514 gTextRuns
->RemoveFromCache(textRun
);
517 return textRun
.forget();
521 MakeTextRun(const PRUint8
*aText
, PRUint32 aLength
,
522 gfxFontGroup
*aFontGroup
, const gfxFontGroup::Parameters
* aParams
,
525 nsAutoPtr
<gfxTextRun
> textRun
;
527 textRun
= aFontGroup
->MakeEmptyTextRun(aParams
, aFlags
);
528 } else if (aLength
== 1 && aText
[0] == ' ') {
529 textRun
= aFontGroup
->MakeSpaceTextRun(aParams
, aFlags
);
531 textRun
= gfxTextRunWordCache::MakeTextRun(aText
, aLength
, aFontGroup
,
536 nsresult rv
= gTextRuns
->AddObject(textRun
);
538 gTextRuns
->RemoveFromCache(textRun
);
541 return textRun
.forget();
545 nsTextFrameTextRunCache::Init() {
546 gTextRuns
= new FrameTextRunCache();
547 return gTextRuns
? NS_OK
: NS_ERROR_OUT_OF_MEMORY
;
551 nsTextFrameTextRunCache::Shutdown() {
556 PRInt32
nsTextFrame::GetContentEnd() const {
557 nsTextFrame
* next
= static_cast<nsTextFrame
*>(GetNextContinuation());
558 return next
? next
->GetContentOffset() : mContent
->GetText()->GetLength();
561 PRInt32
nsTextFrame::GetInFlowContentLength() {
563 nsTextFrame
* nextBidi
= nsnull
;
564 PRInt32 start
= -1, end
;
566 if (mState
& NS_FRAME_IS_BIDI
) {
567 nextBidi
= static_cast<nsTextFrame
*>(GetLastInFlow()->GetNextContinuation());
569 nextBidi
->GetOffsets(start
, end
);
570 return start
- mContentOffset
;
574 return mContent
->TextLength() - mContentOffset
;
577 // Smarter versions of XP_IS_SPACE.
578 // Unicode is really annoying; sometimes a space character isn't whitespace ---
579 // when it combines with another character
580 // So we have several versions of IsSpace for use in different contexts.
582 static PRBool
IsSpaceCombiningSequenceTail(const nsTextFragment
* aFrag
, PRUint32 aPos
)
584 NS_ASSERTION(aPos
<= aFrag
->GetLength(), "Bad offset");
587 return nsTextFrameUtils::IsSpaceCombiningSequenceTail(
588 aFrag
->Get2b() + aPos
, aFrag
->GetLength() - aPos
);
591 // Check whether aPos is a space for CSS 'word-spacing' purposes
592 static PRBool
IsCSSWordSpacingSpace(const nsTextFragment
* aFrag
,
593 PRUint32 aPos
, const nsStyleText
* aStyleText
)
595 NS_ASSERTION(aPos
< aFrag
->GetLength(), "No text for IsSpace!");
597 PRUnichar ch
= aFrag
->CharAt(aPos
);
601 return !IsSpaceCombiningSequenceTail(aFrag
, aPos
+ 1);
603 case '\t': return !aStyleText
->WhiteSpaceIsSignificant();
604 case '\n': return !aStyleText
->NewlineIsSignificant();
605 default: return PR_FALSE
;
609 // Check whether the string aChars/aLength starts with space that's
610 // trimmable according to CSS 'white-space:normal/nowrap'.
611 static PRBool
IsTrimmableSpace(const PRUnichar
* aChars
, PRUint32 aLength
)
613 NS_ASSERTION(aLength
> 0, "No text for IsSpace!");
615 PRUnichar ch
= *aChars
;
617 return !nsTextFrameUtils::IsSpaceCombiningSequenceTail(aChars
+ 1, aLength
- 1);
618 return ch
== '\t' || ch
== '\f' || ch
== '\n' || ch
== '\r';
621 // Check whether the character aCh is trimmable according to CSS
622 // 'white-space:normal/nowrap'
623 static PRBool
IsTrimmableSpace(char aCh
)
625 return aCh
== ' ' || aCh
== '\t' || aCh
== '\f' || aCh
== '\n' || aCh
== '\r';
628 static PRBool
IsTrimmableSpace(const nsTextFragment
* aFrag
, PRUint32 aPos
,
629 const nsStyleText
* aStyleText
)
631 NS_ASSERTION(aPos
< aFrag
->GetLength(), "No text for IsSpace!");
633 switch (aFrag
->CharAt(aPos
)) {
634 case ' ': return !aStyleText
->WhiteSpaceIsSignificant() &&
635 !IsSpaceCombiningSequenceTail(aFrag
, aPos
+ 1);
636 case '\n': return !aStyleText
->NewlineIsSignificant();
639 case '\f': return !aStyleText
->WhiteSpaceIsSignificant();
640 default: return PR_FALSE
;
644 static PRBool
IsSelectionSpace(const nsTextFragment
* aFrag
, PRUint32 aPos
)
646 NS_ASSERTION(aPos
< aFrag
->GetLength(), "No text for IsSpace!");
647 PRUnichar ch
= aFrag
->CharAt(aPos
);
648 if (ch
== ' ' || ch
== CH_NBSP
)
649 return !IsSpaceCombiningSequenceTail(aFrag
, aPos
+ 1);
650 return ch
== '\t' || ch
== '\n' || ch
== '\f' || ch
== '\r';
653 // Count the amount of trimmable whitespace (as per CSS
654 // 'white-space:normal/nowrap') in a text fragment. The first
655 // character is at offset aStartOffset; the maximum number of characters
656 // to check is aLength. aDirection is -1 or 1 depending on whether we should
657 // progress backwards or forwards.
659 GetTrimmableWhitespaceCount(const nsTextFragment
* aFrag
,
660 PRInt32 aStartOffset
, PRInt32 aLength
,
665 const PRUnichar
* str
= aFrag
->Get2b() + aStartOffset
;
666 PRInt32 fragLen
= aFrag
->GetLength() - aStartOffset
;
667 for (; count
< aLength
; ++count
) {
668 if (!IsTrimmableSpace(str
, fragLen
))
671 fragLen
-= aDirection
;
674 const char* str
= aFrag
->Get1b() + aStartOffset
;
675 for (; count
< aLength
; ++count
) {
676 if (!IsTrimmableSpace(*str
))
685 IsAllWhitespace(const nsTextFragment
* aFrag
, PRBool aAllowNewline
)
689 PRInt32 len
= aFrag
->GetLength();
690 const char* str
= aFrag
->Get1b();
691 for (PRInt32 i
= 0; i
< len
; ++i
) {
693 if (ch
== ' ' || ch
== '\t' || ch
== '\r' || (ch
== '\n' && aAllowNewline
))
701 * This class accumulates state as we scan a paragraph of text. It detects
702 * textrun boundaries (changes from text to non-text, hard
703 * line breaks, and font changes) and builds a gfxTextRun at each boundary.
704 * It also detects linebreaker run boundaries (changes from text to non-text,
705 * and hard line breaks) and at each boundary runs the linebreaker to compute
706 * potential line breaks. It also records actual line breaks to store them in
709 class BuildTextRunsScanner
{
711 BuildTextRunsScanner(nsPresContext
* aPresContext
, gfxContext
* aContext
,
712 nsIFrame
* aLineContainer
) :
713 mCurrentFramesAllSameTextRun(nsnull
),
715 mLineContainer(aLineContainer
),
716 mBidiEnabled(aPresContext
->BidiEnabled()),
717 mSkipIncompleteTextRuns(PR_FALSE
),
718 mNextRunContextInfo(nsTextFrameUtils::INCOMING_NONE
),
719 mCurrentRunContextInfo(nsTextFrameUtils::INCOMING_NONE
) {
722 ~BuildTextRunsScanner() {
723 NS_ASSERTION(mBreakSinks
.IsEmpty(), "Should have been cleared");
724 NS_ASSERTION(mTextRunsToDelete
.IsEmpty(), "Should have been cleared");
725 NS_ASSERTION(mLineBreakBeforeFrames
.IsEmpty(), "Should have been cleared");
726 NS_ASSERTION(mMappedFlows
.IsEmpty(), "Should have been cleared");
729 void SetAtStartOfLine() {
730 mStartOfLine
= PR_TRUE
;
731 mCanStopOnThisLine
= PR_FALSE
;
733 void SetSkipIncompleteTextRuns(PRBool aSkip
) {
734 mSkipIncompleteTextRuns
= aSkip
;
736 void SetCommonAncestorWithLastFrame(nsIFrame
* aFrame
) {
737 mCommonAncestorWithLastFrame
= aFrame
;
739 PRBool
CanStopOnThisLine() {
740 return mCanStopOnThisLine
;
742 nsIFrame
* GetCommonAncestorWithLastFrame() {
743 return mCommonAncestorWithLastFrame
;
745 void LiftCommonAncestorWithLastFrameToParent(nsIFrame
* aFrame
) {
746 if (mCommonAncestorWithLastFrame
&&
747 mCommonAncestorWithLastFrame
->GetParent() == aFrame
) {
748 mCommonAncestorWithLastFrame
= aFrame
;
751 void ScanFrame(nsIFrame
* aFrame
);
752 PRBool
IsTextRunValidForMappedFlows(gfxTextRun
* aTextRun
);
753 void FlushFrames(PRBool aFlushLineBreaks
, PRBool aSuppressTrailingBreak
);
754 void FlushLineBreaks(gfxTextRun
* aTrailingTextRun
);
755 void ResetRunInfo() {
757 mMappedFlows
.Clear();
758 mLineBreakBeforeFrames
.Clear();
760 mDoubleByteText
= PR_FALSE
;
762 void AccumulateRunInfo(nsTextFrame
* aFrame
);
764 * @return null to indicate either textrun construction failed or
765 * we constructed just a partial textrun to set up linebreaker and other
766 * state for following textruns.
768 gfxTextRun
* BuildTextRunForFrames(void* aTextBuffer
);
769 void AssignTextRun(gfxTextRun
* aTextRun
);
770 nsTextFrame
* GetNextBreakBeforeFrame(PRUint32
* aIndex
);
771 void SetupBreakSinksForTextRun(gfxTextRun
* aTextRun
, PRBool aIsExistingTextRun
,
772 PRBool aSuppressSink
);
773 struct FindBoundaryState
{
774 nsIFrame
* mStopAtFrame
;
775 nsTextFrame
* mFirstTextFrame
;
776 nsTextFrame
* mLastTextFrame
;
777 PRPackedBool mSeenTextRunBoundaryOnLaterLine
;
778 PRPackedBool mSeenTextRunBoundaryOnThisLine
;
779 PRPackedBool mSeenSpaceForLineBreakingOnThisLine
;
781 enum FindBoundaryResult
{
783 FB_STOPPED_AT_STOP_FRAME
,
784 FB_FOUND_VALID_TEXTRUN_BOUNDARY
786 FindBoundaryResult
FindBoundaries(nsIFrame
* aFrame
, FindBoundaryState
* aState
);
788 PRBool
ContinueTextRunAcrossFrames(nsTextFrame
* aFrame1
, nsTextFrame
* aFrame2
);
790 // Like TextRunMappedFlow but with some differences. mStartFrame to mEndFrame
791 // (exclusive) are a sequence of in-flow frames (if mEndFrame is null, then
792 // continuations starting from mStartFrame are a sequence of in-flow frames).
794 nsTextFrame
* mStartFrame
;
795 nsTextFrame
* mEndFrame
;
796 // When we consider breaking between elements, the nearest common
797 // ancestor of the elements containing the characters is the one whose
798 // CSS 'white-space' property governs. So this records the nearest common
799 // ancestor of mStartFrame and the previous text frame, or null if there
800 // was no previous text frame on this line.
801 nsIFrame
* mAncestorControllingInitialBreak
;
803 PRInt32
GetContentEnd() {
804 return mEndFrame
? mEndFrame
->GetContentOffset()
805 : mStartFrame
->GetContent()->GetText()->GetLength();
809 class BreakSink
: public nsILineBreakSink
{
811 BreakSink(gfxTextRun
* aTextRun
, gfxContext
* aContext
, PRUint32 aOffsetIntoTextRun
,
812 PRBool aExistingTextRun
) :
813 mTextRun(aTextRun
), mContext(aContext
),
814 mOffsetIntoTextRun(aOffsetIntoTextRun
),
815 mChangedBreaks(PR_FALSE
), mExistingTextRun(aExistingTextRun
) {}
817 virtual void SetBreaks(PRUint32 aOffset
, PRUint32 aLength
,
818 PRPackedBool
* aBreakBefore
) {
819 if (mTextRun
->SetPotentialLineBreaks(aOffset
+ mOffsetIntoTextRun
, aLength
,
820 aBreakBefore
, mContext
)) {
821 mChangedBreaks
= PR_TRUE
;
822 // Be conservative and assume that some breaks have been set
823 mTextRun
->ClearFlagBits(nsTextFrameUtils::TEXT_NO_BREAKS
);
827 virtual void SetCapitalization(PRUint32 aOffset
, PRUint32 aLength
,
828 PRPackedBool
* aCapitalize
) {
829 NS_ASSERTION(mTextRun
->GetFlags() & nsTextFrameUtils::TEXT_IS_TRANSFORMED
,
830 "Text run should be transformed!");
831 nsTransformedTextRun
* transformedTextRun
=
832 static_cast<nsTransformedTextRun
*>(mTextRun
);
833 transformedTextRun
->SetCapitalization(aOffset
+ mOffsetIntoTextRun
, aLength
,
834 aCapitalize
, mContext
);
838 NS_ASSERTION(!(mTextRun
->GetFlags() &
839 (gfxTextRunWordCache::TEXT_UNUSED_FLAGS
|
840 nsTextFrameUtils::TEXT_UNUSED_FLAG
)),
841 "Flag set that should never be set! (memory safety error?)");
842 if (mTextRun
->GetFlags() & nsTextFrameUtils::TEXT_IS_TRANSFORMED
) {
843 nsTransformedTextRun
* transformedTextRun
=
844 static_cast<nsTransformedTextRun
*>(mTextRun
);
845 transformedTextRun
->FinishSettingProperties(mContext
);
849 gfxTextRun
* mTextRun
;
850 gfxContext
* mContext
;
851 PRUint32 mOffsetIntoTextRun
;
852 PRPackedBool mChangedBreaks
;
853 PRPackedBool mExistingTextRun
;
857 nsAutoTArray
<MappedFlow
,10> mMappedFlows
;
858 nsAutoTArray
<nsTextFrame
*,50> mLineBreakBeforeFrames
;
859 nsAutoTArray
<nsAutoPtr
<BreakSink
>,10> mBreakSinks
;
860 nsAutoTArray
<gfxTextRun
*,5> mTextRunsToDelete
;
861 nsLineBreaker mLineBreaker
;
862 gfxTextRun
* mCurrentFramesAllSameTextRun
;
863 gfxContext
* mContext
;
864 nsIFrame
* mLineContainer
;
865 nsTextFrame
* mLastFrame
;
866 // The common ancestor of the current frame and the previous leaf frame
867 // on the line, or null if there was no previous leaf frame.
868 nsIFrame
* mCommonAncestorWithLastFrame
;
869 // mMaxTextLength is an upper bound on the size of the text in all mapped frames
870 // The value PR_UINT32_MAX represents overflow; text will be discarded
871 PRUint32 mMaxTextLength
;
872 PRPackedBool mDoubleByteText
;
873 PRPackedBool mBidiEnabled
;
874 PRPackedBool mStartOfLine
;
875 PRPackedBool mSkipIncompleteTextRuns
;
876 PRPackedBool mCanStopOnThisLine
;
877 PRUint8 mNextRunContextInfo
;
878 PRUint8 mCurrentRunContextInfo
;
882 FindLineContainer(nsIFrame
* aFrame
)
884 while (aFrame
&& aFrame
->CanContinueTextRun()) {
885 aFrame
= aFrame
->GetParent();
891 IsLineBreakingWhiteSpace(PRUnichar aChar
)
893 // 0x0A (\n) is not handled as white-space by the line breaker, since
894 // we break before it, if it isn't transformed to a normal space.
895 // (If we treat it as normal white-space then we'd only break after it.)
896 // However, it does induce a line break or is converted to a regular
897 // space, and either way it can be used to bound the region of text
898 // that needs to be analyzed for line breaking.
899 return nsLineBreaker::IsSpace(aChar
) || aChar
== 0x0A;
903 TextContainsLineBreakerWhiteSpace(const void* aText
, PRUint32 aLength
,
904 PRBool aIsDoubleByte
)
908 const PRUnichar
* chars
= static_cast<const PRUnichar
*>(aText
);
909 for (i
= 0; i
< aLength
; ++i
) {
910 if (IsLineBreakingWhiteSpace(chars
[i
]))
915 const PRUint8
* chars
= static_cast<const PRUint8
*>(aText
);
916 for (i
= 0; i
< aLength
; ++i
) {
917 if (IsLineBreakingWhiteSpace(chars
[i
]))
924 struct FrameTextTraversal
{
925 // These fields identify which frames should be recursively scanned
926 // The first normal frame to scan (or null, if no such frame should be scanned)
927 nsIFrame
* mFrameToScan
;
928 // The first overflow frame to scan (or null, if no such frame should be scanned)
929 nsIFrame
* mOverflowFrameToScan
;
930 // Whether to scan the siblings of mFrameToDescendInto/mOverflowFrameToDescendInto
931 PRPackedBool mScanSiblings
;
933 // These identify the boundaries of the context required for
934 // line breaking or textrun construction
935 PRPackedBool mLineBreakerCanCrossFrameBoundary
;
936 PRPackedBool mTextRunCanCrossFrameBoundary
;
938 nsIFrame
* NextFrameToScan() {
942 mFrameToScan
= mScanSiblings
? f
->GetNextSibling() : nsnull
;
943 } else if (mOverflowFrameToScan
) {
944 f
= mOverflowFrameToScan
;
945 mOverflowFrameToScan
= mScanSiblings
? f
->GetNextSibling() : nsnull
;
953 static FrameTextTraversal
954 CanTextCrossFrameBoundary(nsIFrame
* aFrame
, nsIAtom
* aType
)
956 NS_ASSERTION(aType
== aFrame
->GetType(), "Wrong type");
958 FrameTextTraversal result
;
960 PRBool continuesTextRun
= aFrame
->CanContinueTextRun();
961 if (aType
== nsGkAtoms::placeholderFrame
) {
962 // placeholders are "invisible", so a text run should be able to span
963 // across one. But don't descend into the out-of-flow.
964 result
.mLineBreakerCanCrossFrameBoundary
= PR_TRUE
;
965 result
.mOverflowFrameToScan
= nsnull
;
966 if (continuesTextRun
) {
967 // ... Except for first-letter floats, which are really in-flow
968 // from the point of view of capitalization etc, so we'd better
969 // descend into them. But we actually need to break the textrun for
970 // first-letter floats since things look bad if, say, we try to make a
971 // ligature across the float boundary.
972 result
.mFrameToScan
=
973 (static_cast<nsPlaceholderFrame
*>(aFrame
))->GetOutOfFlowFrame();
974 result
.mScanSiblings
= PR_FALSE
;
975 result
.mTextRunCanCrossFrameBoundary
= PR_FALSE
;
977 result
.mFrameToScan
= nsnull
;
978 result
.mTextRunCanCrossFrameBoundary
= PR_TRUE
;
981 if (continuesTextRun
) {
982 result
.mFrameToScan
= aFrame
->GetFirstChild(nsnull
);
983 result
.mOverflowFrameToScan
= aFrame
->GetFirstChild(nsGkAtoms::overflowList
);
984 NS_WARN_IF_FALSE(!result
.mOverflowFrameToScan
,
985 "Scanning overflow inline frames is something we should avoid");
986 result
.mScanSiblings
= PR_TRUE
;
987 result
.mTextRunCanCrossFrameBoundary
= PR_TRUE
;
988 result
.mLineBreakerCanCrossFrameBoundary
= PR_TRUE
;
990 result
.mFrameToScan
= nsnull
;
991 result
.mOverflowFrameToScan
= nsnull
;
992 result
.mTextRunCanCrossFrameBoundary
= PR_FALSE
;
993 result
.mLineBreakerCanCrossFrameBoundary
= PR_FALSE
;
999 BuildTextRunsScanner::FindBoundaryResult
1000 BuildTextRunsScanner::FindBoundaries(nsIFrame
* aFrame
, FindBoundaryState
* aState
)
1002 nsIAtom
* frameType
= aFrame
->GetType();
1003 nsTextFrame
* textFrame
= frameType
== nsGkAtoms::textFrame
1004 ? static_cast<nsTextFrame
*>(aFrame
) : nsnull
;
1006 if (aState
->mLastTextFrame
&&
1007 textFrame
!= aState
->mLastTextFrame
->GetNextInFlow() &&
1008 !ContinueTextRunAcrossFrames(aState
->mLastTextFrame
, textFrame
)) {
1009 aState
->mSeenTextRunBoundaryOnThisLine
= PR_TRUE
;
1010 if (aState
->mSeenSpaceForLineBreakingOnThisLine
)
1011 return FB_FOUND_VALID_TEXTRUN_BOUNDARY
;
1013 if (!aState
->mFirstTextFrame
) {
1014 aState
->mFirstTextFrame
= textFrame
;
1016 aState
->mLastTextFrame
= textFrame
;
1019 if (aFrame
== aState
->mStopAtFrame
)
1020 return FB_STOPPED_AT_STOP_FRAME
;
1023 if (!aState
->mSeenSpaceForLineBreakingOnThisLine
) {
1024 const nsTextFragment
* frag
= textFrame
->GetContent()->GetText();
1025 PRUint32 start
= textFrame
->GetContentOffset();
1026 const void* text
= frag
->Is2b()
1027 ? static_cast<const void*>(frag
->Get2b() + start
)
1028 : static_cast<const void*>(frag
->Get1b() + start
);
1029 if (TextContainsLineBreakerWhiteSpace(text
, textFrame
->GetContentLength(),
1031 aState
->mSeenSpaceForLineBreakingOnThisLine
= PR_TRUE
;
1032 if (aState
->mSeenTextRunBoundaryOnLaterLine
)
1033 return FB_FOUND_VALID_TEXTRUN_BOUNDARY
;
1039 FrameTextTraversal traversal
=
1040 CanTextCrossFrameBoundary(aFrame
, frameType
);
1041 if (!traversal
.mTextRunCanCrossFrameBoundary
) {
1042 aState
->mSeenTextRunBoundaryOnThisLine
= PR_TRUE
;
1043 if (aState
->mSeenSpaceForLineBreakingOnThisLine
)
1044 return FB_FOUND_VALID_TEXTRUN_BOUNDARY
;
1047 for (nsIFrame
* f
= traversal
.NextFrameToScan(); f
;
1048 f
= traversal
.NextFrameToScan()) {
1049 FindBoundaryResult result
= FindBoundaries(f
, aState
);
1050 if (result
!= FB_CONTINUE
)
1054 if (!traversal
.mTextRunCanCrossFrameBoundary
) {
1055 aState
->mSeenTextRunBoundaryOnThisLine
= PR_TRUE
;
1056 if (aState
->mSeenSpaceForLineBreakingOnThisLine
)
1057 return FB_FOUND_VALID_TEXTRUN_BOUNDARY
;
1063 // build text runs for the 200 lines following aForFrame, and stop after that
1064 // when we get a chance.
1065 #define NUM_LINES_TO_BUILD_TEXT_RUNS 200
1068 * General routine for building text runs. This is hairy because of the need
1069 * to build text runs that span content nodes.
1071 * @param aForFrameLine the line containing aForFrame; if null, we'll figure
1072 * out the line (slowly)
1073 * @param aLineContainer the line container containing aForFrame; if null,
1074 * we'll walk the ancestors to find it. It's required to be non-null when
1075 * aForFrameLine is non-null.
1078 BuildTextRuns(gfxContext
* aContext
, nsTextFrame
* aForFrame
,
1079 nsIFrame
* aLineContainer
,
1080 const nsLineList::iterator
* aForFrameLine
)
1082 NS_ASSERTION(aForFrame
|| aLineContainer
,
1083 "One of aForFrame or aLineContainer must be set!");
1084 NS_ASSERTION(!aForFrameLine
|| aLineContainer
,
1085 "line but no line container");
1087 if (!aLineContainer
) {
1088 aLineContainer
= FindLineContainer(aForFrame
);
1090 NS_ASSERTION(!aForFrame
||
1091 (aLineContainer
== FindLineContainer(aForFrame
) ||
1092 (aLineContainer
->GetType() == nsGkAtoms::letterFrame
&&
1093 aLineContainer
->GetStyleDisplay()->IsFloating())),
1094 "Wrong line container hint");
1097 nsPresContext
* presContext
= aLineContainer
->PresContext();
1098 BuildTextRunsScanner
scanner(presContext
, aContext
, aLineContainer
);
1100 nsBlockFrame
* block
= nsLayoutUtils::GetAsBlock(aLineContainer
);
1103 NS_ASSERTION(!aLineContainer
->GetPrevInFlow() && !aLineContainer
->GetNextInFlow(),
1104 "Breakable non-block line containers not supported");
1105 // Just loop through all the children of the linecontainer ... it's really
1107 scanner
.SetAtStartOfLine();
1108 scanner
.SetCommonAncestorWithLastFrame(nsnull
);
1109 nsIFrame
* child
= aLineContainer
->GetFirstChild(nsnull
);
1111 scanner
.ScanFrame(child
);
1112 child
= child
->GetNextSibling();
1114 // Set mStartOfLine so FlushFrames knows its textrun ends a line
1115 scanner
.SetAtStartOfLine();
1116 scanner
.FlushFrames(PR_TRUE
, PR_FALSE
);
1120 // Find the line containing aForFrame
1122 PRBool isValid
= PR_TRUE
;
1123 nsBlockInFlowLineIterator
backIterator(block
, &isValid
);
1124 if (aForFrameLine
) {
1125 backIterator
= nsBlockInFlowLineIterator(block
, *aForFrameLine
, PR_FALSE
);
1127 backIterator
= nsBlockInFlowLineIterator(block
, aForFrame
, &isValid
);
1128 NS_ASSERTION(isValid
, "aForFrame not found in block, someone lied to us");
1129 NS_ASSERTION(backIterator
.GetContainer() == block
,
1130 "Someone lied to us about the block");
1132 nsBlockFrame::line_iterator startLine
= backIterator
.GetLine();
1134 // Find a line where we can start building text runs. We choose the last line
1136 // -- there is a textrun boundary between the start of the line and the
1137 // start of aForFrame
1138 // -- there is a space between the start of the line and the textrun boundary
1139 // (this is so we can be sure the line breaks will be set properly
1140 // on the textruns we construct).
1141 // The possibly-partial text runs up to and including the first space
1142 // are not reconstructed. We construct partial text runs for that text ---
1143 // for the sake of simplifying the code and feeding the linebreaker ---
1144 // but we discard them instead of assigning them to frames.
1145 // This is a little awkward because we traverse lines in the reverse direction
1146 // but we traverse the frames in each line in the forward direction.
1147 nsBlockInFlowLineIterator forwardIterator
= backIterator
;
1148 nsTextFrame
* stopAtFrame
= aForFrame
;
1149 nsTextFrame
* nextLineFirstTextFrame
= nsnull
;
1150 PRBool seenTextRunBoundaryOnLaterLine
= PR_FALSE
;
1151 PRBool mayBeginInTextRun
= PR_TRUE
;
1153 forwardIterator
= backIterator
;
1154 nsBlockFrame::line_iterator line
= backIterator
.GetLine();
1155 if (!backIterator
.Prev() || backIterator
.GetLine()->IsBlock()) {
1156 mayBeginInTextRun
= PR_FALSE
;
1160 BuildTextRunsScanner::FindBoundaryState state
= { stopAtFrame
, nsnull
, nsnull
,
1161 PRPackedBool(seenTextRunBoundaryOnLaterLine
), PR_FALSE
, PR_FALSE
};
1162 nsIFrame
* child
= line
->mFirstChild
;
1163 PRBool foundBoundary
= PR_FALSE
;
1165 for (i
= line
->GetChildCount() - 1; i
>= 0; --i
) {
1166 BuildTextRunsScanner::FindBoundaryResult result
=
1167 scanner
.FindBoundaries(child
, &state
);
1168 if (result
== BuildTextRunsScanner::FB_FOUND_VALID_TEXTRUN_BOUNDARY
) {
1169 foundBoundary
= PR_TRUE
;
1171 } else if (result
== BuildTextRunsScanner::FB_STOPPED_AT_STOP_FRAME
) {
1174 child
= child
->GetNextSibling();
1178 if (!stopAtFrame
&& state
.mLastTextFrame
&& nextLineFirstTextFrame
&&
1179 !scanner
.ContinueTextRunAcrossFrames(state
.mLastTextFrame
, nextLineFirstTextFrame
)) {
1180 // Found a usable textrun boundary at the end of the line
1181 if (state
.mSeenSpaceForLineBreakingOnThisLine
)
1183 seenTextRunBoundaryOnLaterLine
= PR_TRUE
;
1184 } else if (state
.mSeenTextRunBoundaryOnThisLine
) {
1185 seenTextRunBoundaryOnLaterLine
= PR_TRUE
;
1187 stopAtFrame
= nsnull
;
1188 if (state
.mFirstTextFrame
) {
1189 nextLineFirstTextFrame
= state
.mFirstTextFrame
;
1192 scanner
.SetSkipIncompleteTextRuns(mayBeginInTextRun
);
1194 // Now iterate over all text frames starting from the current line. First-in-flow
1195 // text frames will be accumulated into textRunFrames as we go. When a
1196 // text run boundary is required we flush textRunFrames ((re)building their
1197 // gfxTextRuns as necessary).
1198 PRBool seenStartLine
= PR_FALSE
;
1199 PRUint32 linesAfterStartLine
= 0;
1201 nsBlockFrame::line_iterator line
= forwardIterator
.GetLine();
1202 if (line
->IsBlock())
1204 line
->SetInvalidateTextRuns(PR_FALSE
);
1205 scanner
.SetAtStartOfLine();
1206 scanner
.SetCommonAncestorWithLastFrame(nsnull
);
1207 nsIFrame
* child
= line
->mFirstChild
;
1209 for (i
= line
->GetChildCount() - 1; i
>= 0; --i
) {
1210 scanner
.ScanFrame(child
);
1211 child
= child
->GetNextSibling();
1213 if (line
.get() == startLine
.get()) {
1214 seenStartLine
= PR_TRUE
;
1216 if (seenStartLine
) {
1217 ++linesAfterStartLine
;
1218 if (linesAfterStartLine
>= NUM_LINES_TO_BUILD_TEXT_RUNS
&& scanner
.CanStopOnThisLine()) {
1219 // Don't flush frames; we may be in the middle of a textrun
1220 // that we can't end here. That's OK, we just won't build it.
1221 // Note that we must already have finished the textrun for aForFrame,
1222 // because we've seen the end of a textrun in a line after the line
1223 // containing aForFrame.
1224 scanner
.FlushLineBreaks(nsnull
);
1225 // This flushes out mMappedFlows and mLineBreakBeforeFrames, which
1226 // silences assertions in the scanner destructor.
1227 scanner
.ResetRunInfo();
1231 } while (forwardIterator
.Next());
1233 // Set mStartOfLine so FlushFrames knows its textrun ends a line
1234 scanner
.SetAtStartOfLine();
1235 scanner
.FlushFrames(PR_TRUE
, PR_FALSE
);
1239 ExpandBuffer(PRUnichar
* aDest
, PRUint8
* aSrc
, PRUint32 aCount
)
1250 PRBool
BuildTextRunsScanner::IsTextRunValidForMappedFlows(gfxTextRun
* aTextRun
)
1252 if (aTextRun
->GetFlags() & nsTextFrameUtils::TEXT_IS_SIMPLE_FLOW
)
1253 return mMappedFlows
.Length() == 1 &&
1254 mMappedFlows
[0].mStartFrame
== static_cast<nsTextFrame
*>(aTextRun
->GetUserData()) &&
1255 mMappedFlows
[0].mEndFrame
== nsnull
;
1257 TextRunUserData
* userData
= static_cast<TextRunUserData
*>(aTextRun
->GetUserData());
1258 if (userData
->mMappedFlowCount
!= PRInt32(mMappedFlows
.Length()))
1261 for (i
= 0; i
< mMappedFlows
.Length(); ++i
) {
1262 if (userData
->mMappedFlows
[i
].mStartFrame
!= mMappedFlows
[i
].mStartFrame
||
1263 PRInt32(userData
->mMappedFlows
[i
].mContentLength
) !=
1264 mMappedFlows
[i
].GetContentEnd() - mMappedFlows
[i
].mStartFrame
->GetContentOffset())
1271 * This gets called when we need to make a text run for the current list of
1274 void BuildTextRunsScanner::FlushFrames(PRBool aFlushLineBreaks
, PRBool aSuppressTrailingBreak
)
1276 gfxTextRun
* textRun
= nsnull
;
1277 if (!mMappedFlows
.IsEmpty()) {
1278 if (!mSkipIncompleteTextRuns
&& mCurrentFramesAllSameTextRun
&&
1279 ((mCurrentFramesAllSameTextRun
->GetFlags() & nsTextFrameUtils::TEXT_INCOMING_WHITESPACE
) != 0) ==
1280 ((mCurrentRunContextInfo
& nsTextFrameUtils::INCOMING_WHITESPACE
) != 0) &&
1281 ((mCurrentFramesAllSameTextRun
->GetFlags() & gfxTextRunWordCache::TEXT_INCOMING_ARABICCHAR
) != 0) ==
1282 ((mCurrentRunContextInfo
& nsTextFrameUtils::INCOMING_ARABICCHAR
) != 0) &&
1283 IsTextRunValidForMappedFlows(mCurrentFramesAllSameTextRun
)) {
1284 // Optimization: We do not need to (re)build the textrun.
1285 textRun
= mCurrentFramesAllSameTextRun
;
1287 // Feed this run's text into the linebreaker to provide context. This also
1288 // updates mNextRunContextInfo appropriately.
1289 SetupBreakSinksForTextRun(textRun
, PR_TRUE
, PR_FALSE
);
1290 mNextRunContextInfo
= nsTextFrameUtils::INCOMING_NONE
;
1291 if (textRun
->GetFlags() & nsTextFrameUtils::TEXT_TRAILING_WHITESPACE
) {
1292 mNextRunContextInfo
|= nsTextFrameUtils::INCOMING_WHITESPACE
;
1294 if (textRun
->GetFlags() & gfxTextRunWordCache::TEXT_TRAILING_ARABICCHAR
) {
1295 mNextRunContextInfo
|= nsTextFrameUtils::INCOMING_ARABICCHAR
;
1298 nsAutoTArray
<PRUint8
,BIG_TEXT_NODE_SIZE
> buffer
;
1299 PRUint32 bufferSize
= mMaxTextLength
*(mDoubleByteText
? 2 : 1);
1300 if (bufferSize
< mMaxTextLength
|| bufferSize
== PR_UINT32_MAX
||
1301 !buffer
.AppendElements(bufferSize
)) {
1304 textRun
= BuildTextRunForFrames(buffer
.Elements());
1308 if (aFlushLineBreaks
) {
1309 FlushLineBreaks(aSuppressTrailingBreak
? nsnull
: textRun
);
1312 mCanStopOnThisLine
= PR_TRUE
;
1316 void BuildTextRunsScanner::FlushLineBreaks(gfxTextRun
* aTrailingTextRun
)
1318 PRBool trailingLineBreak
;
1319 nsresult rv
= mLineBreaker
.Reset(&trailingLineBreak
);
1320 // textRun may be null for various reasons, including because we constructed
1321 // a partial textrun just to get the linebreaker and other state set up
1322 // to build the next textrun.
1323 if (NS_SUCCEEDED(rv
) && trailingLineBreak
&& aTrailingTextRun
) {
1324 aTrailingTextRun
->SetFlagBits(nsTextFrameUtils::TEXT_HAS_TRAILING_BREAK
);
1328 for (i
= 0; i
< mBreakSinks
.Length(); ++i
) {
1329 if (!mBreakSinks
[i
]->mExistingTextRun
|| mBreakSinks
[i
]->mChangedBreaks
) {
1330 // TODO cause frames associated with the textrun to be reflowed, if they
1331 // aren't being reflowed already!
1333 mBreakSinks
[i
]->Finish();
1335 mBreakSinks
.Clear();
1337 for (i
= 0; i
< mTextRunsToDelete
.Length(); ++i
) {
1338 gfxTextRun
* deleteTextRun
= mTextRunsToDelete
[i
];
1339 gTextRuns
->RemoveFromCache(deleteTextRun
);
1340 delete deleteTextRun
;
1342 mTextRunsToDelete
.Clear();
1345 void BuildTextRunsScanner::AccumulateRunInfo(nsTextFrame
* aFrame
)
1347 if (mMaxTextLength
!= PR_UINT32_MAX
) {
1348 NS_ASSERTION(mMaxTextLength
< PR_UINT32_MAX
- aFrame
->GetContentLength(), "integer overflow");
1349 if (mMaxTextLength
>= PR_UINT32_MAX
- aFrame
->GetContentLength()) {
1350 mMaxTextLength
= PR_UINT32_MAX
;
1352 mMaxTextLength
+= aFrame
->GetContentLength();
1355 mDoubleByteText
|= aFrame
->GetContent()->GetText()->Is2b();
1356 mLastFrame
= aFrame
;
1357 mCommonAncestorWithLastFrame
= aFrame
->GetParent();
1359 MappedFlow
* mappedFlow
= &mMappedFlows
[mMappedFlows
.Length() - 1];
1360 NS_ASSERTION(mappedFlow
->mStartFrame
== aFrame
||
1361 mappedFlow
->GetContentEnd() == aFrame
->GetContentOffset(),
1362 "Overlapping or discontiguous frames => BAD");
1363 mappedFlow
->mEndFrame
= static_cast<nsTextFrame
*>(aFrame
->GetNextContinuation());
1364 if (mCurrentFramesAllSameTextRun
!= aFrame
->GetTextRun()) {
1365 mCurrentFramesAllSameTextRun
= nsnull
;
1369 mLineBreakBeforeFrames
.AppendElement(aFrame
);
1370 mStartOfLine
= PR_FALSE
;
1374 static nscoord
StyleToCoord(const nsStyleCoord
& aCoord
)
1376 if (eStyleUnit_Coord
== aCoord
.GetUnit()) {
1377 return aCoord
.GetCoordValue();
1384 HasTerminalNewline(const nsTextFrame
* aFrame
)
1386 if (aFrame
->GetContentLength() == 0)
1388 const nsTextFragment
* frag
= aFrame
->GetContent()->GetText();
1389 return frag
->CharAt(aFrame
->GetContentEnd() - 1) == '\n';
1393 BuildTextRunsScanner::ContinueTextRunAcrossFrames(nsTextFrame
* aFrame1
, nsTextFrame
* aFrame2
)
1396 NS_GET_EMBEDDING_LEVEL(aFrame1
) != NS_GET_EMBEDDING_LEVEL(aFrame2
))
1399 nsStyleContext
* sc1
= aFrame1
->GetStyleContext();
1400 const nsStyleText
* textStyle1
= sc1
->GetStyleText();
1401 // If the first frame ends in a preformatted newline, then we end the textrun
1402 // here. This avoids creating giant textruns for an entire plain text file.
1403 // Note that we create a single text frame for a preformatted text node,
1404 // even if it has newlines in it, so typically we won't see trailing newlines
1405 // until after reflow has broken up the frame into one (or more) frames per
1406 // line. That's OK though.
1407 if (textStyle1
->NewlineIsSignificant() && HasTerminalNewline(aFrame1
))
1410 if (aFrame1
->GetContent() == aFrame2
->GetContent() &&
1411 aFrame1
->GetNextInFlow() != aFrame2
) {
1412 // aFrame2 must be a non-fluid continuation of aFrame1. This can happen
1413 // sometimes when the unicode-bidi property is used; the bidi resolver
1414 // breaks text into different frames even though the text has the same
1415 // direction. We can't allow these two frames to share the same textrun
1416 // because that would violate our invariant that two flows in the same
1417 // textrun have different content elements.
1421 nsStyleContext
* sc2
= aFrame2
->GetStyleContext();
1424 const nsStyleFont
* fontStyle1
= sc1
->GetStyleFont();
1425 const nsStyleFont
* fontStyle2
= sc2
->GetStyleFont();
1426 const nsStyleText
* textStyle2
= sc2
->GetStyleText();
1427 return fontStyle1
->mFont
.BaseEquals(fontStyle2
->mFont
) &&
1428 sc1
->GetStyleVisibility()->mLanguage
== sc2
->GetStyleVisibility()->mLanguage
&&
1429 nsLayoutUtils::GetTextRunFlagsForStyle(sc1
, textStyle1
, fontStyle1
) ==
1430 nsLayoutUtils::GetTextRunFlagsForStyle(sc2
, textStyle2
, fontStyle2
);
1433 void BuildTextRunsScanner::ScanFrame(nsIFrame
* aFrame
)
1435 // First check if we can extend the current mapped frame block. This is common.
1436 if (mMappedFlows
.Length() > 0) {
1437 MappedFlow
* mappedFlow
= &mMappedFlows
[mMappedFlows
.Length() - 1];
1438 if (mappedFlow
->mEndFrame
== aFrame
&&
1439 (aFrame
->GetStateBits() & NS_FRAME_IS_FLUID_CONTINUATION
)) {
1440 NS_ASSERTION(aFrame
->GetType() == nsGkAtoms::textFrame
,
1441 "Flow-sibling of a text frame is not a text frame?");
1443 // Don't do this optimization if mLastFrame has a terminal newline...
1444 // it's quite likely preformatted and we might want to end the textrun here.
1445 // This is almost always true:
1446 if (mLastFrame
->GetStyleContext() == aFrame
->GetStyleContext() &&
1447 !HasTerminalNewline(mLastFrame
)) {
1448 AccumulateRunInfo(static_cast<nsTextFrame
*>(aFrame
));
1454 nsIAtom
* frameType
= aFrame
->GetType();
1455 // Now see if we can add a new set of frames to the current textrun
1456 if (frameType
== nsGkAtoms::textFrame
) {
1457 nsTextFrame
* frame
= static_cast<nsTextFrame
*>(aFrame
);
1460 if (!ContinueTextRunAcrossFrames(mLastFrame
, frame
)) {
1461 FlushFrames(PR_FALSE
, PR_FALSE
);
1463 if (mLastFrame
->GetContent() == frame
->GetContent()) {
1464 AccumulateRunInfo(frame
);
1470 MappedFlow
* mappedFlow
= mMappedFlows
.AppendElement();
1474 mappedFlow
->mStartFrame
= frame
;
1475 mappedFlow
->mAncestorControllingInitialBreak
= mCommonAncestorWithLastFrame
;
1477 AccumulateRunInfo(frame
);
1478 if (mMappedFlows
.Length() == 1) {
1479 mCurrentFramesAllSameTextRun
= frame
->GetTextRun();
1480 mCurrentRunContextInfo
= mNextRunContextInfo
;
1485 FrameTextTraversal traversal
=
1486 CanTextCrossFrameBoundary(aFrame
, frameType
);
1487 PRBool isBR
= frameType
== nsGkAtoms::brFrame
;
1488 if (!traversal
.mLineBreakerCanCrossFrameBoundary
) {
1489 // BR frames are special. We do not need or want to record a break opportunity
1490 // before a BR frame.
1491 FlushFrames(PR_TRUE
, isBR
);
1492 mCommonAncestorWithLastFrame
= aFrame
;
1493 mNextRunContextInfo
&= ~nsTextFrameUtils::INCOMING_WHITESPACE
;
1494 mStartOfLine
= PR_FALSE
;
1495 } else if (!traversal
.mTextRunCanCrossFrameBoundary
) {
1496 FlushFrames(PR_FALSE
, PR_FALSE
);
1499 for (nsIFrame
* f
= traversal
.NextFrameToScan(); f
;
1500 f
= traversal
.NextFrameToScan()) {
1504 if (!traversal
.mLineBreakerCanCrossFrameBoundary
) {
1505 // Really if we're a BR frame this is unnecessary since descendInto will be
1506 // false. In fact this whole "if" statement should move into the descendInto.
1507 FlushFrames(PR_TRUE
, isBR
);
1508 mCommonAncestorWithLastFrame
= aFrame
;
1509 mNextRunContextInfo
&= ~nsTextFrameUtils::INCOMING_WHITESPACE
;
1510 } else if (!traversal
.mTextRunCanCrossFrameBoundary
) {
1511 FlushFrames(PR_FALSE
, PR_FALSE
);
1514 LiftCommonAncestorWithLastFrameToParent(aFrame
->GetParent());
1518 BuildTextRunsScanner::GetNextBreakBeforeFrame(PRUint32
* aIndex
)
1520 PRUint32 index
= *aIndex
;
1521 if (index
>= mLineBreakBeforeFrames
.Length())
1523 *aIndex
= index
+ 1;
1524 return static_cast<nsTextFrame
*>(mLineBreakBeforeFrames
.ElementAt(index
));
1528 GetSpacingFlags(nscoord spacing
)
1530 return spacing
? gfxTextRunFactory::TEXT_ENABLE_SPACING
: 0;
1533 static gfxFontGroup
*
1534 GetFontGroupForFrame(nsIFrame
* aFrame
,
1535 nsIFontMetrics
** aOutFontMetrics
= nsnull
)
1537 if (aOutFontMetrics
)
1538 *aOutFontMetrics
= nsnull
;
1540 nsCOMPtr
<nsIFontMetrics
> metrics
;
1541 nsLayoutUtils::GetFontMetricsForFrame(aFrame
, getter_AddRefs(metrics
));
1546 nsIFontMetrics
* metricsRaw
= metrics
;
1547 if (aOutFontMetrics
) {
1548 *aOutFontMetrics
= metricsRaw
;
1549 NS_ADDREF(*aOutFontMetrics
);
1551 nsIThebesFontMetrics
* fm
= static_cast<nsIThebesFontMetrics
*>(metricsRaw
);
1552 // XXX this is a bit bogus, we're releasing 'metrics' so the returned font-group
1553 // might actually be torn down, although because of the way the device context
1554 // caches font metrics, this seems to not actually happen. But we should fix
1556 return fm
->GetThebesFontGroup();
1559 static already_AddRefed
<gfxContext
>
1560 GetReferenceRenderingContext(nsTextFrame
* aTextFrame
, nsIRenderingContext
* aRC
)
1562 nsCOMPtr
<nsIRenderingContext
> tmp
= aRC
;
1564 tmp
= aTextFrame
->PresContext()->PresShell()->GetReferenceRenderingContext();
1569 gfxContext
* ctx
= tmp
->ThebesContext();
1575 * The returned textrun must be released via gfxTextRunCache::ReleaseTextRun
1576 * or gfxTextRunCache::AutoTextRun.
1579 GetHyphenTextRun(gfxTextRun
* aTextRun
, gfxContext
* aContext
, nsTextFrame
* aTextFrame
)
1581 nsRefPtr
<gfxContext
> ctx
= aContext
;
1583 ctx
= GetReferenceRenderingContext(aTextFrame
, nsnull
);
1588 gfxFontGroup
* fontGroup
= aTextRun
->GetFontGroup();
1589 PRUint32 flags
= gfxFontGroup::TEXT_IS_PERSISTENT
;
1591 // only use U+2010 if it is supported by the first font in the group;
1592 // it's better to use ASCII '-' from the primary font than to fall back to U+2010
1593 // from some other, possibly poorly-matching face
1594 static const PRUnichar unicodeHyphen
= 0x2010;
1595 gfxFont
*font
= fontGroup
->GetFontAt(0);
1596 if (font
&& font
->HasCharacter(unicodeHyphen
)) {
1597 return gfxTextRunCache::MakeTextRun(&unicodeHyphen
, 1, fontGroup
, ctx
,
1598 aTextRun
->GetAppUnitsPerDevUnit(), flags
);
1601 static const PRUint8 dash
= '-';
1602 return gfxTextRunCache::MakeTextRun(&dash
, 1, fontGroup
, ctx
,
1603 aTextRun
->GetAppUnitsPerDevUnit(),
1607 static gfxFont::Metrics
1608 GetFirstFontMetrics(gfxFontGroup
* aFontGroup
)
1611 return gfxFont::Metrics();
1612 gfxFont
* font
= aFontGroup
->GetFontAt(0);
1614 return gfxFont::Metrics();
1615 return font
->GetMetrics();
1618 PR_STATIC_ASSERT(NS_STYLE_WHITESPACE_NORMAL
== 0);
1619 PR_STATIC_ASSERT(NS_STYLE_WHITESPACE_PRE
== 1);
1620 PR_STATIC_ASSERT(NS_STYLE_WHITESPACE_NOWRAP
== 2);
1621 PR_STATIC_ASSERT(NS_STYLE_WHITESPACE_PRE_WRAP
== 3);
1622 PR_STATIC_ASSERT(NS_STYLE_WHITESPACE_PRE_LINE
== 4);
1624 static const nsTextFrameUtils::CompressionMode CSSWhitespaceToCompressionMode
[] =
1626 nsTextFrameUtils::COMPRESS_WHITESPACE_NEWLINE
, // normal
1627 nsTextFrameUtils::COMPRESS_NONE
, // pre
1628 nsTextFrameUtils::COMPRESS_WHITESPACE_NEWLINE
, // nowrap
1629 nsTextFrameUtils::COMPRESS_NONE
, // pre-wrap
1630 nsTextFrameUtils::COMPRESS_WHITESPACE
// pre-line
1634 BuildTextRunsScanner::BuildTextRunForFrames(void* aTextBuffer
)
1636 gfxSkipCharsBuilder builder
;
1638 const void* textPtr
= aTextBuffer
;
1639 PRBool anySmallcapsStyle
= PR_FALSE
;
1640 PRBool anyTextTransformStyle
= PR_FALSE
;
1641 PRInt32 endOfLastContent
= 0;
1642 PRUint32 textFlags
= nsTextFrameUtils::TEXT_NO_BREAKS
;
1644 if (mCurrentRunContextInfo
& nsTextFrameUtils::INCOMING_WHITESPACE
) {
1645 textFlags
|= nsTextFrameUtils::TEXT_INCOMING_WHITESPACE
;
1647 if (mCurrentRunContextInfo
& nsTextFrameUtils::INCOMING_ARABICCHAR
) {
1648 textFlags
|= gfxTextRunWordCache::TEXT_INCOMING_ARABICCHAR
;
1651 nsAutoTArray
<PRInt32
,50> textBreakPoints
;
1652 TextRunUserData dummyData
;
1653 TextRunMappedFlow dummyMappedFlow
;
1655 TextRunUserData
* userData
;
1656 TextRunUserData
* userDataToDestroy
;
1657 // If the situation is particularly simple (and common) we don't need to
1658 // allocate userData.
1659 if (mMappedFlows
.Length() == 1 && !mMappedFlows
[0].mEndFrame
&&
1660 mMappedFlows
[0].mStartFrame
->GetContentOffset() == 0) {
1661 userData
= &dummyData
;
1662 userDataToDestroy
= nsnull
;
1663 dummyData
.mMappedFlows
= &dummyMappedFlow
;
1665 userData
= static_cast<TextRunUserData
*>
1666 (nsMemory::Alloc(sizeof(TextRunUserData
) + mMappedFlows
.Length()*sizeof(TextRunMappedFlow
)));
1667 userDataToDestroy
= userData
;
1668 userData
->mMappedFlows
= reinterpret_cast<TextRunMappedFlow
*>(userData
+ 1);
1670 userData
->mMappedFlowCount
= mMappedFlows
.Length();
1671 userData
->mLastFlowIndex
= 0;
1673 PRUint32 currentTransformedTextOffset
= 0;
1675 PRUint32 nextBreakIndex
= 0;
1676 nsTextFrame
* nextBreakBeforeFrame
= GetNextBreakBeforeFrame(&nextBreakIndex
);
1677 PRBool enabledJustification
= mLineContainer
&&
1678 mLineContainer
->GetStyleText()->mTextAlign
== NS_STYLE_TEXT_ALIGN_JUSTIFY
;
1681 const nsStyleText
* textStyle
= nsnull
;
1682 const nsStyleFont
* fontStyle
= nsnull
;
1683 nsStyleContext
* lastStyleContext
= nsnull
;
1684 for (i
= 0; i
< mMappedFlows
.Length(); ++i
) {
1685 MappedFlow
* mappedFlow
= &mMappedFlows
[i
];
1686 nsTextFrame
* f
= mappedFlow
->mStartFrame
;
1688 lastStyleContext
= f
->GetStyleContext();
1689 // Detect use of text-transform or font-variant anywhere in the run
1690 textStyle
= f
->GetStyleText();
1691 if (NS_STYLE_TEXT_TRANSFORM_NONE
!= textStyle
->mTextTransform
) {
1692 anyTextTransformStyle
= PR_TRUE
;
1694 textFlags
|= GetSpacingFlags(StyleToCoord(textStyle
->mLetterSpacing
));
1695 textFlags
|= GetSpacingFlags(textStyle
->mWordSpacing
);
1696 nsTextFrameUtils::CompressionMode compression
=
1697 CSSWhitespaceToCompressionMode
[textStyle
->mWhiteSpace
];
1698 if (enabledJustification
&& !textStyle
->WhiteSpaceIsSignificant()) {
1699 textFlags
|= gfxTextRunFactory::TEXT_ENABLE_SPACING
;
1701 fontStyle
= f
->GetStyleFont();
1702 if (NS_STYLE_FONT_VARIANT_SMALL_CAPS
== fontStyle
->mFont
.variant
) {
1703 anySmallcapsStyle
= PR_TRUE
;
1706 // Figure out what content is included in this flow.
1707 nsIContent
* content
= f
->GetContent();
1708 const nsTextFragment
* frag
= content
->GetText();
1709 PRInt32 contentStart
= mappedFlow
->mStartFrame
->GetContentOffset();
1710 PRInt32 contentEnd
= mappedFlow
->GetContentEnd();
1711 PRInt32 contentLength
= contentEnd
- contentStart
;
1713 TextRunMappedFlow
* newFlow
= &userData
->mMappedFlows
[i
];
1714 newFlow
->mStartFrame
= mappedFlow
->mStartFrame
;
1715 newFlow
->mDOMOffsetToBeforeTransformOffset
= builder
.GetCharCount() -
1716 mappedFlow
->mStartFrame
->GetContentOffset();
1717 newFlow
->mContentLength
= contentLength
;
1719 while (nextBreakBeforeFrame
&& nextBreakBeforeFrame
->GetContent() == content
) {
1720 textBreakPoints
.AppendElement(
1721 nextBreakBeforeFrame
->GetContentOffset() + newFlow
->mDOMOffsetToBeforeTransformOffset
);
1722 nextBreakBeforeFrame
= GetNextBreakBeforeFrame(&nextBreakIndex
);
1725 PRUint32 analysisFlags
;
1727 NS_ASSERTION(mDoubleByteText
, "Wrong buffer char size!");
1728 PRUnichar
* bufStart
= static_cast<PRUnichar
*>(aTextBuffer
);
1729 PRUnichar
* bufEnd
= nsTextFrameUtils::TransformText(
1730 frag
->Get2b() + contentStart
, contentLength
, bufStart
,
1731 compression
, &mNextRunContextInfo
, &builder
, &analysisFlags
);
1732 aTextBuffer
= bufEnd
;
1734 if (mDoubleByteText
) {
1735 // Need to expand the text. First transform it into a temporary buffer,
1737 nsAutoTArray
<PRUint8
,BIG_TEXT_NODE_SIZE
> tempBuf
;
1738 if (!tempBuf
.AppendElements(contentLength
)) {
1739 DestroyUserData(userDataToDestroy
);
1742 PRUint8
* bufStart
= tempBuf
.Elements();
1743 PRUint8
* end
= nsTextFrameUtils::TransformText(
1744 reinterpret_cast<const PRUint8
*>(frag
->Get1b()) + contentStart
, contentLength
,
1745 bufStart
, compression
, &mNextRunContextInfo
, &builder
, &analysisFlags
);
1746 aTextBuffer
= ExpandBuffer(static_cast<PRUnichar
*>(aTextBuffer
),
1747 tempBuf
.Elements(), end
- tempBuf
.Elements());
1749 PRUint8
* bufStart
= static_cast<PRUint8
*>(aTextBuffer
);
1750 PRUint8
* end
= nsTextFrameUtils::TransformText(
1751 reinterpret_cast<const PRUint8
*>(frag
->Get1b()) + contentStart
, contentLength
,
1752 bufStart
, compression
, &mNextRunContextInfo
, &builder
, &analysisFlags
);
1756 textFlags
|= analysisFlags
;
1758 currentTransformedTextOffset
=
1759 (static_cast<const PRUint8
*>(aTextBuffer
) - static_cast<const PRUint8
*>(textPtr
)) >> mDoubleByteText
;
1761 endOfLastContent
= contentEnd
;
1764 // Check for out-of-memory in gfxSkipCharsBuilder
1765 if (!builder
.IsOK()) {
1766 DestroyUserData(userDataToDestroy
);
1770 void* finalUserData
;
1771 if (userData
== &dummyData
) {
1772 textFlags
|= nsTextFrameUtils::TEXT_IS_SIMPLE_FLOW
;
1774 finalUserData
= mMappedFlows
[0].mStartFrame
;
1776 finalUserData
= userData
;
1779 PRUint32 transformedLength
= currentTransformedTextOffset
;
1781 // Now build the textrun
1782 nsTextFrame
* firstFrame
= mMappedFlows
[0].mStartFrame
;
1783 gfxFontGroup
* fontGroup
= GetFontGroupForFrame(firstFrame
);
1785 DestroyUserData(userDataToDestroy
);
1789 if (textFlags
& nsTextFrameUtils::TEXT_HAS_TAB
) {
1790 textFlags
|= gfxTextRunFactory::TEXT_ENABLE_SPACING
;
1792 if (textFlags
& nsTextFrameUtils::TEXT_HAS_SHY
) {
1793 textFlags
|= gfxTextRunFactory::TEXT_ENABLE_HYPHEN_BREAKS
;
1795 if (mBidiEnabled
&& (NS_GET_EMBEDDING_LEVEL(firstFrame
) & 1)) {
1796 textFlags
|= gfxTextRunFactory::TEXT_IS_RTL
;
1798 if (mNextRunContextInfo
& nsTextFrameUtils::INCOMING_WHITESPACE
) {
1799 textFlags
|= nsTextFrameUtils::TEXT_TRAILING_WHITESPACE
;
1801 if (mNextRunContextInfo
& nsTextFrameUtils::INCOMING_ARABICCHAR
) {
1802 textFlags
|= gfxTextRunWordCache::TEXT_TRAILING_ARABICCHAR
;
1804 // ContinueTextRunAcrossFrames guarantees that it doesn't matter which
1805 // frame's style is used, so use the last frame's
1806 textFlags
|= nsLayoutUtils::GetTextRunFlagsForStyle(lastStyleContext
,
1807 textStyle
, fontStyle
);
1808 // XXX this is a bit of a hack. For performance reasons, if we're favouring
1809 // performance over quality, don't try to get accurate glyph extents.
1810 if (!(textFlags
& gfxTextRunFactory::TEXT_OPTIMIZE_SPEED
)) {
1811 textFlags
|= gfxTextRunFactory::TEXT_NEED_BOUNDING_BOX
;
1814 gfxSkipChars skipChars
;
1815 skipChars
.TakeFrom(&builder
);
1816 // Convert linebreak coordinates to transformed string offsets
1817 NS_ASSERTION(nextBreakIndex
== mLineBreakBeforeFrames
.Length(),
1818 "Didn't find all the frames to break-before...");
1819 gfxSkipCharsIterator
iter(skipChars
);
1820 nsAutoTArray
<PRUint32
,50> textBreakPointsAfterTransform
;
1821 for (i
= 0; i
< textBreakPoints
.Length(); ++i
) {
1822 nsTextFrameUtils::AppendLineBreakOffset(&textBreakPointsAfterTransform
,
1823 iter
.ConvertOriginalToSkipped(textBreakPoints
[i
]));
1826 nsTextFrameUtils::AppendLineBreakOffset(&textBreakPointsAfterTransform
,
1830 // Setup factory chain
1831 nsAutoPtr
<nsTransformingTextRunFactory
> transformingFactory
;
1832 if (anySmallcapsStyle
) {
1833 transformingFactory
= new nsFontVariantTextRunFactory();
1835 if (anyTextTransformStyle
) {
1836 transformingFactory
=
1837 new nsCaseTransformTextRunFactory(transformingFactory
.forget());
1839 nsTArray
<nsStyleContext
*> styles
;
1840 if (transformingFactory
) {
1841 iter
.SetOriginalOffset(0);
1842 for (i
= 0; i
< mMappedFlows
.Length(); ++i
) {
1843 MappedFlow
* mappedFlow
= &mMappedFlows
[i
];
1845 for (f
= mappedFlow
->mStartFrame
; f
!= mappedFlow
->mEndFrame
;
1846 f
= static_cast<nsTextFrame
*>(f
->GetNextContinuation())) {
1847 PRUint32 offset
= iter
.GetSkippedOffset();
1848 iter
.AdvanceOriginal(f
->GetContentLength());
1849 PRUint32 end
= iter
.GetSkippedOffset();
1850 nsStyleContext
* sc
= f
->GetStyleContext();
1852 for (j
= offset
; j
< end
; ++j
) {
1853 styles
.AppendElement(sc
);
1857 textFlags
|= nsTextFrameUtils::TEXT_IS_TRANSFORMED
;
1858 NS_ASSERTION(iter
.GetSkippedOffset() == transformedLength
,
1859 "We didn't cover all the characters in the text run!");
1862 gfxTextRun
* textRun
;
1863 gfxTextRunFactory::Parameters params
=
1864 { mContext
, finalUserData
, &skipChars
,
1865 textBreakPointsAfterTransform
.Elements(), textBreakPointsAfterTransform
.Length(),
1866 firstFrame
->PresContext()->AppUnitsPerDevPixel() };
1868 if (mDoubleByteText
) {
1869 const PRUnichar
* text
= static_cast<const PRUnichar
*>(textPtr
);
1870 if (transformingFactory
) {
1871 textRun
= transformingFactory
->MakeTextRun(text
, transformedLength
, ¶ms
,
1872 fontGroup
, textFlags
, styles
.Elements());
1874 // ownership of the factory has passed to the textrun
1875 transformingFactory
.forget();
1878 textRun
= MakeTextRun(text
, transformedLength
, fontGroup
, ¶ms
, textFlags
);
1881 const PRUint8
* text
= static_cast<const PRUint8
*>(textPtr
);
1882 textFlags
|= gfxFontGroup::TEXT_IS_8BIT
;
1883 if (transformingFactory
) {
1884 textRun
= transformingFactory
->MakeTextRun(text
, transformedLength
, ¶ms
,
1885 fontGroup
, textFlags
, styles
.Elements());
1887 // ownership of the factory has passed to the textrun
1888 transformingFactory
.forget();
1891 textRun
= MakeTextRun(text
, transformedLength
, fontGroup
, ¶ms
, textFlags
);
1895 DestroyUserData(userDataToDestroy
);
1899 // We have to set these up after we've created the textrun, because
1900 // the breaks may be stored in the textrun during this very call.
1901 // This is a bit annoying because it requires another loop over the frames
1902 // making up the textrun, but I don't see a way to avoid this.
1903 SetupBreakSinksForTextRun(textRun
, PR_FALSE
, mSkipIncompleteTextRuns
);
1905 if (mSkipIncompleteTextRuns
) {
1906 mSkipIncompleteTextRuns
= !TextContainsLineBreakerWhiteSpace(textPtr
,
1907 transformedLength
, mDoubleByteText
);
1908 // Arrange for this textrun to be deleted the next time the linebreaker
1910 mTextRunsToDelete
.AppendElement(textRun
);
1911 // Since we're doing to destroy the user data now, avoid a dangling
1912 // pointer. Strictly speaking we don't need to do this since it should
1913 // not be used (since this textrun will not be used and will be
1914 // itself deleted soon), but it's always better to not have dangling
1916 textRun
->SetUserData(nsnull
);
1917 DestroyUserData(userDataToDestroy
);
1921 // Actually wipe out the textruns associated with the mapped frames and associate
1922 // those frames with this text run.
1923 AssignTextRun(textRun
);
1928 HasCompressedLeadingWhitespace(nsTextFrame
* aFrame
, const nsStyleText
* aStyleText
,
1929 PRInt32 aContentEndOffset
,
1930 const gfxSkipCharsIterator
& aIterator
)
1932 if (!aIterator
.IsOriginalCharSkipped())
1935 gfxSkipCharsIterator iter
= aIterator
;
1936 PRInt32 frameContentOffset
= aFrame
->GetContentOffset();
1937 const nsTextFragment
* frag
= aFrame
->GetContent()->GetText();
1938 while (frameContentOffset
< aContentEndOffset
&& iter
.IsOriginalCharSkipped()) {
1939 if (IsTrimmableSpace(frag
, frameContentOffset
, aStyleText
))
1941 ++frameContentOffset
;
1942 iter
.AdvanceOriginal(1);
1948 BuildTextRunsScanner::SetupBreakSinksForTextRun(gfxTextRun
* aTextRun
,
1949 PRBool aIsExistingTextRun
,
1950 PRBool aSuppressSink
)
1952 // textruns have uniform language
1953 nsIAtom
* language
= mMappedFlows
[0].mStartFrame
->GetStyleVisibility()->mLanguage
;
1954 // We keep this pointed at the skip-chars data for the current mappedFlow.
1955 // This lets us cheaply check whether the flow has compressed initial
1957 gfxSkipCharsIterator
iter(aTextRun
->GetSkipChars());
1960 for (i
= 0; i
< mMappedFlows
.Length(); ++i
) {
1961 MappedFlow
* mappedFlow
= &mMappedFlows
[i
];
1962 PRUint32 offset
= iter
.GetSkippedOffset();
1963 gfxSkipCharsIterator iterNext
= iter
;
1964 iterNext
.AdvanceOriginal(mappedFlow
->GetContentEnd() -
1965 mappedFlow
->mStartFrame
->GetContentOffset());
1967 nsAutoPtr
<BreakSink
>* breakSink
= mBreakSinks
.AppendElement(
1968 new BreakSink(aTextRun
, mContext
, offset
, aIsExistingTextRun
));
1969 if (!breakSink
|| !*breakSink
)
1972 PRUint32 length
= iterNext
.GetSkippedOffset() - offset
;
1974 nsIFrame
* initialBreakController
= mappedFlow
->mAncestorControllingInitialBreak
;
1975 if (!initialBreakController
) {
1976 initialBreakController
= mLineContainer
;
1978 if (!initialBreakController
->GetStyleText()->WhiteSpaceCanWrap()) {
1979 flags
|= nsLineBreaker::BREAK_SUPPRESS_INITIAL
;
1981 nsTextFrame
* startFrame
= mappedFlow
->mStartFrame
;
1982 const nsStyleText
* textStyle
= startFrame
->GetStyleText();
1983 if (!textStyle
->WhiteSpaceCanWrap()) {
1984 flags
|= nsLineBreaker::BREAK_SUPPRESS_INSIDE
;
1986 if (aTextRun
->GetFlags() & nsTextFrameUtils::TEXT_NO_BREAKS
) {
1987 flags
|= nsLineBreaker::BREAK_SKIP_SETTING_NO_BREAKS
;
1989 if (textStyle
->mTextTransform
== NS_STYLE_TEXT_TRANSFORM_CAPITALIZE
) {
1990 flags
|= nsLineBreaker::BREAK_NEED_CAPITALIZATION
;
1993 if (HasCompressedLeadingWhitespace(startFrame
, textStyle
,
1994 mappedFlow
->GetContentEnd(), iter
)) {
1995 mLineBreaker
.AppendInvisibleWhitespace(flags
);
1999 BreakSink
* sink
= aSuppressSink
? nsnull
: (*breakSink
).get();
2000 if (aTextRun
->GetFlags() & gfxFontGroup::TEXT_IS_8BIT
) {
2001 mLineBreaker
.AppendText(language
, aTextRun
->GetText8Bit() + offset
,
2002 length
, flags
, sink
);
2004 mLineBreaker
.AppendText(language
, aTextRun
->GetTextUnicode() + offset
,
2005 length
, flags
, sink
);
2013 // Find the flow corresponding to aContent in aUserData
2014 static inline TextRunMappedFlow
*
2015 FindFlowForContent(TextRunUserData
* aUserData
, nsIContent
* aContent
)
2017 // Find the flow that contains us
2018 PRInt32 i
= aUserData
->mLastFlowIndex
;
2021 // Search starting at the current position and examine close-by
2022 // positions first, moving further and further away as we go.
2023 while (i
>= 0 && i
< aUserData
->mMappedFlowCount
) {
2024 TextRunMappedFlow
* flow
= &aUserData
->mMappedFlows
[i
];
2025 if (flow
->mStartFrame
->GetContent() == aContent
) {
2030 delta
= -delta
- sign
;
2034 // We ran into an array edge. Add |delta| to |i| once more to get
2035 // back to the side where we still need to search, then step in
2036 // the |sign| direction.
2039 for (; i
< aUserData
->mMappedFlowCount
; ++i
) {
2040 TextRunMappedFlow
* flow
= &aUserData
->mMappedFlows
[i
];
2041 if (flow
->mStartFrame
->GetContent() == aContent
) {
2046 for (; i
>= 0; --i
) {
2047 TextRunMappedFlow
* flow
= &aUserData
->mMappedFlows
[i
];
2048 if (flow
->mStartFrame
->GetContent() == aContent
) {
2058 BuildTextRunsScanner::AssignTextRun(gfxTextRun
* aTextRun
)
2061 for (i
= 0; i
< mMappedFlows
.Length(); ++i
) {
2062 MappedFlow
* mappedFlow
= &mMappedFlows
[i
];
2063 nsTextFrame
* startFrame
= mappedFlow
->mStartFrame
;
2064 nsTextFrame
* endFrame
= mappedFlow
->mEndFrame
;
2066 for (f
= startFrame
; f
!= endFrame
;
2067 f
= static_cast<nsTextFrame
*>(f
->GetNextContinuation())) {
2069 if (f
->GetTextRun()) {
2070 gfxTextRun
* textRun
= f
->GetTextRun();
2071 if (textRun
->GetFlags() & nsTextFrameUtils::TEXT_IS_SIMPLE_FLOW
) {
2072 if (mMappedFlows
[0].mStartFrame
!= static_cast<nsTextFrame
*>(textRun
->GetUserData())) {
2073 NS_WARNING("REASSIGNING SIMPLE FLOW TEXT RUN!");
2076 TextRunUserData
* userData
=
2077 static_cast<TextRunUserData
*>(textRun
->GetUserData());
2079 if (PRUint32(userData
->mMappedFlowCount
) >= mMappedFlows
.Length() ||
2080 userData
->mMappedFlows
[userData
->mMappedFlowCount
- 1].mStartFrame
!=
2081 mMappedFlows
[userData
->mMappedFlowCount
- 1].mStartFrame
) {
2082 NS_WARNING("REASSIGNING MULTIFLOW TEXT RUN (not append)!");
2088 gfxTextRun
* oldTextRun
= f
->GetTextRun();
2090 nsTextFrame
* firstFrame
= nsnull
;
2091 PRUint32 startOffset
= 0;
2092 if (oldTextRun
->GetFlags() & nsTextFrameUtils::TEXT_IS_SIMPLE_FLOW
) {
2093 firstFrame
= static_cast<nsTextFrame
*>(oldTextRun
->GetUserData());
2096 TextRunUserData
* userData
= static_cast<TextRunUserData
*>(oldTextRun
->GetUserData());
2097 firstFrame
= userData
->mMappedFlows
[0].mStartFrame
;
2098 if (NS_UNLIKELY(f
!= firstFrame
)) {
2099 TextRunMappedFlow
* flow
= FindFlowForContent(userData
, f
->GetContent());
2101 startOffset
= flow
->mDOMOffsetToBeforeTransformOffset
;
2104 NS_ERROR("Can't find flow containing frame 'f'");
2109 // Optimization: if |f| is the first frame in the flow then there are no
2110 // prev-continuations that use |oldTextRun|.
2111 nsTextFrame
* clearFrom
= nsnull
;
2112 if (NS_UNLIKELY(f
!= firstFrame
)) {
2113 // If all the frames in the mapped flow starting at |f| (inclusive)
2114 // are empty then we let the prev-continuations keep the old text run.
2115 gfxSkipCharsIterator
iter(oldTextRun
->GetSkipChars(), startOffset
, f
->GetContentOffset());
2116 PRUint32 textRunOffset
= iter
.ConvertOriginalToSkipped(f
->GetContentOffset());
2117 clearFrom
= textRunOffset
== oldTextRun
->GetLength() ? f
: nsnull
;
2119 f
->ClearTextRun(clearFrom
);
2122 if (firstFrame
&& !firstFrame
->GetTextRun()) {
2123 // oldTextRun was destroyed - assert that we don't reference it.
2124 for (PRUint32 i
= 0; i
< mBreakSinks
.Length(); ++i
) {
2125 NS_ASSERTION(oldTextRun
!= mBreakSinks
[i
]->mTextRun
,
2126 "destroyed text run is still in use");
2131 f
->SetTextRun(aTextRun
);
2133 // Set this bit now; we can't set it any earlier because
2134 // f->ClearTextRun() might clear it out.
2135 startFrame
->AddStateBits(TEXT_IN_TEXTRUN_USER_DATA
);
2139 gfxSkipCharsIterator
2140 nsTextFrame::EnsureTextRun(gfxContext
* aReferenceContext
, nsIFrame
* aLineContainer
,
2141 const nsLineList::iterator
* aLine
,
2142 PRUint32
* aFlowEndInTextRun
)
2144 if (mTextRun
&& (!aLine
|| !(*aLine
)->GetInvalidateTextRuns())) {
2145 if (mTextRun
->GetExpirationState()->IsTracked()) {
2146 gTextRuns
->MarkUsed(mTextRun
);
2149 nsRefPtr
<gfxContext
> ctx
= aReferenceContext
;
2151 ctx
= GetReferenceRenderingContext(this, nsnull
);
2154 BuildTextRuns(ctx
, this, aLineContainer
, aLine
);
2157 // A text run was not constructed for this frame. This is bad. The caller
2158 // will check mTextRun.
2159 static const gfxSkipChars emptySkipChars
;
2160 return gfxSkipCharsIterator(emptySkipChars
, 0);
2164 if (mTextRun
->GetFlags() & nsTextFrameUtils::TEXT_IS_SIMPLE_FLOW
) {
2165 if (aFlowEndInTextRun
) {
2166 *aFlowEndInTextRun
= mTextRun
->GetLength();
2168 return gfxSkipCharsIterator(mTextRun
->GetSkipChars(), 0, mContentOffset
);
2171 TextRunUserData
* userData
= static_cast<TextRunUserData
*>(mTextRun
->GetUserData());
2172 TextRunMappedFlow
* flow
= FindFlowForContent(userData
, mContent
);
2174 // Since textruns can only contain one flow for a given content element,
2175 // this must be our flow.
2176 PRInt32 flowIndex
= flow
- userData
->mMappedFlows
;
2177 userData
->mLastFlowIndex
= flowIndex
;
2178 gfxSkipCharsIterator
iter(mTextRun
->GetSkipChars(),
2179 flow
->mDOMOffsetToBeforeTransformOffset
, mContentOffset
);
2180 if (aFlowEndInTextRun
) {
2181 if (flowIndex
+ 1 < userData
->mMappedFlowCount
) {
2182 gfxSkipCharsIterator
end(mTextRun
->GetSkipChars());
2183 *aFlowEndInTextRun
= end
.ConvertOriginalToSkipped(
2184 flow
[1].mStartFrame
->GetContentOffset() + flow
[1].mDOMOffsetToBeforeTransformOffset
);
2186 *aFlowEndInTextRun
= mTextRun
->GetLength();
2192 NS_ERROR("Can't find flow containing this frame???");
2193 static const gfxSkipChars emptySkipChars
;
2194 return gfxSkipCharsIterator(emptySkipChars
, 0);
2198 GetEndOfTrimmedText(const nsTextFragment
* aFrag
, const nsStyleText
* aStyleText
,
2199 PRUint32 aStart
, PRUint32 aEnd
,
2200 gfxSkipCharsIterator
* aIterator
)
2202 aIterator
->SetSkippedOffset(aEnd
);
2203 while (aIterator
->GetSkippedOffset() > aStart
) {
2204 aIterator
->AdvanceSkipped(-1);
2205 if (!IsTrimmableSpace(aFrag
, aIterator
->GetOriginalOffset(), aStyleText
))
2206 return aIterator
->GetSkippedOffset() + 1;
2211 nsTextFrame::TrimmedOffsets
2212 nsTextFrame::GetTrimmedOffsets(const nsTextFragment
* aFrag
,
2215 NS_ASSERTION(mTextRun
, "Need textrun here");
2216 // This should not be used during reflow. We need our TEXT_REFLOW_FLAGS
2217 // to be set correctly.
2218 NS_ASSERTION(!(GetStateBits() & NS_FRAME_FIRST_REFLOW
),
2219 "Can only call this on frames that have been reflowed");
2220 NS_ASSERTION(!(GetStateBits() & NS_FRAME_IN_REFLOW
),
2221 "Can only call this on frames that are not being reflowed");
2223 TrimmedOffsets offsets
= { GetContentOffset(), GetContentLength() };
2224 const nsStyleText
* textStyle
= GetStyleText();
2225 // Note that pre-line newlines should still allow us to trim spaces
2227 if (textStyle
->WhiteSpaceIsSignificant())
2230 if (GetStateBits() & TEXT_START_OF_LINE
) {
2231 PRInt32 whitespaceCount
=
2232 GetTrimmableWhitespaceCount(aFrag
,
2233 offsets
.mStart
, offsets
.mLength
, 1);
2234 offsets
.mStart
+= whitespaceCount
;
2235 offsets
.mLength
-= whitespaceCount
;
2238 if (aTrimAfter
&& (GetStateBits() & TEXT_END_OF_LINE
)) {
2239 // This treats a trailing 'pre-line' newline as trimmable. That's fine,
2240 // it's actually what we want since we want whitespace before it to
2242 PRInt32 whitespaceCount
=
2243 GetTrimmableWhitespaceCount(aFrag
,
2244 offsets
.GetEnd() - 1, offsets
.mLength
, -1);
2245 offsets
.mLength
-= whitespaceCount
;
2251 * Currently only Unicode characters below 0x10000 have their spacing modified
2252 * by justification. If characters above 0x10000 turn out to need
2253 * justification spacing, that will require extra work. Currently,
2254 * this function must not include 0xd800 to 0xdbff because these characters
2257 static PRBool
IsJustifiableCharacter(const nsTextFragment
* aFrag
, PRInt32 aPos
,
2260 PRUnichar ch
= aFrag
->CharAt(aPos
);
2261 if (ch
== '\n' || ch
== '\t' || ch
== '\r')
2263 if (ch
== ' ' || ch
== CH_NBSP
) {
2264 // Don't justify spaces that are combined with diacriticals
2267 return !nsTextFrameUtils::IsSpaceCombiningSequenceTail(
2268 aFrag
->Get2b() + aPos
+ 1, aFrag
->GetLength() - (aPos
+ 1));
2273 (0x2150u
<= ch
&& ch
<= 0x22ffu
) || // Number Forms, Arrows, Mathematical Operators
2274 (0x2460u
<= ch
&& ch
<= 0x24ffu
) || // Enclosed Alphanumerics
2275 (0x2580u
<= ch
&& ch
<= 0x27bfu
) || // Block Elements, Geometric Shapes, Miscellaneous Symbols, Dingbats
2276 (0x27f0u
<= ch
&& ch
<= 0x2bffu
) || // Supplemental Arrows-A, Braille Patterns, Supplemental Arrows-B,
2277 // Miscellaneous Mathematical Symbols-B, Supplemental Mathematical Operators,
2278 // Miscellaneous Symbols and Arrows
2279 (0x2e80u
<= ch
&& ch
<= 0x312fu
) || // CJK Radicals Supplement, CJK Radicals Supplement,
2280 // Ideographic Description Characters, CJK Symbols and Punctuation,
2281 // Hiragana, Katakana, Bopomofo
2282 (0x3190u
<= ch
&& ch
<= 0xabffu
) || // Kanbun, Bopomofo Extended, Katakana Phonetic Extensions,
2283 // Enclosed CJK Letters and Months, CJK Compatibility,
2284 // CJK Unified Ideographs Extension A, Yijing Hexagram Symbols,
2285 // CJK Unified Ideographs, Yi Syllables, Yi Radicals
2286 (0xf900u
<= ch
&& ch
<= 0xfaffu
) || // CJK Compatibility Ideographs
2287 (0xff5eu
<= ch
&& ch
<= 0xff9fu
) // Halfwidth and Fullwidth Forms(a part)
2294 nsTextFrame::ClearMetrics(nsHTMLReflowMetrics
& aMetrics
)
2297 aMetrics
.height
= 0;
2298 aMetrics
.ascent
= 0;
2302 static PRInt32
FindChar(const nsTextFragment
* frag
,
2303 PRInt32 aOffset
, PRInt32 aLength
, PRUnichar ch
)
2307 const PRUnichar
* str
= frag
->Get2b() + aOffset
;
2308 for (; i
< aLength
; ++i
) {
2314 if (PRUint16(ch
) <= 0xFF) {
2315 const char* str
= frag
->Get1b() + aOffset
;
2316 const void* p
= memchr(str
, ch
, aLength
);
2318 return (static_cast<const char*>(p
) - str
) + aOffset
;
2324 static PRBool
IsChineseOrJapanese(nsIFrame
* aFrame
)
2326 nsIAtom
* language
= aFrame
->GetStyleVisibility()->mLanguage
;
2330 const PRUnichar
*lang
= language
->GetUTF16String();
2331 return (!nsCRT::strncmp(lang
, NS_LITERAL_STRING("ja").get(), 2) ||
2332 !nsCRT::strncmp(lang
, NS_LITERAL_STRING("zh").get(), 2)) &&
2333 (language
->GetLength() == 2 || lang
[2] == '-');
2337 static PRBool
IsInBounds(const gfxSkipCharsIterator
& aStart
, PRInt32 aContentLength
,
2338 PRUint32 aOffset
, PRUint32 aLength
) {
2339 if (aStart
.GetSkippedOffset() > aOffset
)
2341 if (aContentLength
== PR_INT32_MAX
)
2343 gfxSkipCharsIterator
iter(aStart
);
2344 iter
.AdvanceOriginal(aContentLength
);
2345 return iter
.GetSkippedOffset() >= aOffset
+ aLength
;
2349 class NS_STACK_CLASS PropertyProvider
: public gfxTextRun::PropertyProvider
{
2352 * Use this constructor for reflow, when we don't know what text is
2353 * really mapped by the frame and we have a lot of other data around.
2355 * @param aLength can be PR_INT32_MAX to indicate we cover all the text
2356 * associated with aFrame up to where its flow chain ends in the given
2357 * textrun. If PR_INT32_MAX is passed, justification and hyphen-related methods
2358 * cannot be called, nor can GetOriginalLength().
2360 PropertyProvider(gfxTextRun
* aTextRun
, const nsStyleText
* aTextStyle
,
2361 const nsTextFragment
* aFrag
, nsTextFrame
* aFrame
,
2362 const gfxSkipCharsIterator
& aStart
, PRInt32 aLength
,
2363 nsIFrame
* aLineContainer
,
2364 nscoord aOffsetFromBlockOriginForTabs
)
2365 : mTextRun(aTextRun
), mFontGroup(nsnull
),
2366 mTextStyle(aTextStyle
), mFrag(aFrag
),
2367 mLineContainer(aLineContainer
),
2368 mFrame(aFrame
), mStart(aStart
), mTempIterator(aStart
),
2369 mTabWidths(nsnull
), mLength(aLength
),
2370 mWordSpacing(mTextStyle
->mWordSpacing
),
2371 mLetterSpacing(StyleToCoord(mTextStyle
->mLetterSpacing
)),
2372 mJustificationSpacing(0),
2374 mOffsetFromBlockOriginForTabs(aOffsetFromBlockOriginForTabs
),
2377 NS_ASSERTION(mStart
.IsInitialized(), "Start not initialized?");
2381 * Use this constructor after the frame has been reflowed and we don't
2382 * have other data around. Gets everything from the frame. EnsureTextRun
2383 * *must* be called before this!!!
2385 PropertyProvider(nsTextFrame
* aFrame
, const gfxSkipCharsIterator
& aStart
)
2386 : mTextRun(aFrame
->GetTextRun()), mFontGroup(nsnull
),
2387 mTextStyle(aFrame
->GetStyleText()),
2388 mFrag(aFrame
->GetContent()->GetText()),
2389 mLineContainer(nsnull
),
2390 mFrame(aFrame
), mStart(aStart
), mTempIterator(aStart
),
2392 mLength(aFrame
->GetContentLength()),
2393 mWordSpacing(mTextStyle
->mWordSpacing
),
2394 mLetterSpacing(StyleToCoord(mTextStyle
->mLetterSpacing
)),
2395 mJustificationSpacing(0),
2397 mOffsetFromBlockOriginForTabs(0),
2398 mReflowing(PR_FALSE
)
2400 NS_ASSERTION(mTextRun
, "Textrun not initialized!");
2403 // Call this after construction if you're not going to reflow the text
2404 void InitializeForDisplay(PRBool aTrimAfter
);
2406 virtual void GetSpacing(PRUint32 aStart
, PRUint32 aLength
, Spacing
* aSpacing
);
2407 virtual gfxFloat
GetHyphenWidth();
2408 virtual void GetHyphenationBreaks(PRUint32 aStart
, PRUint32 aLength
,
2409 PRPackedBool
* aBreakBefore
);
2411 void GetSpacingInternal(PRUint32 aStart
, PRUint32 aLength
, Spacing
* aSpacing
,
2412 PRBool aIgnoreTabs
);
2415 * Count the number of justifiable characters in the given DOM range
2417 PRUint32
ComputeJustifiableCharacters(PRInt32 aOffset
, PRInt32 aLength
);
2419 * Find the start and end of the justifiable characters. Does not depend on the
2420 * position of aStart or aEnd, although it's most efficient if they are near the
2421 * start and end of the text frame.
2423 void FindJustificationRange(gfxSkipCharsIterator
* aStart
,
2424 gfxSkipCharsIterator
* aEnd
);
2426 const nsStyleText
* GetStyleText() { return mTextStyle
; }
2427 nsTextFrame
* GetFrame() { return mFrame
; }
2428 // This may not be equal to the frame offset/length in because we may have
2429 // adjusted for whitespace trimming according to the state bits set in the frame
2430 // (for the static provider)
2431 const gfxSkipCharsIterator
& GetStart() { return mStart
; }
2432 // May return PR_INT32_MAX if that was given to the constructor
2433 PRUint32
GetOriginalLength() {
2434 NS_ASSERTION(mLength
!= PR_INT32_MAX
, "Length not known");
2437 const nsTextFragment
* GetFragment() { return mFrag
; }
2439 gfxFontGroup
* GetFontGroup() {
2441 InitFontGroupAndFontMetrics();
2445 nsIFontMetrics
* GetFontMetrics() {
2447 InitFontGroupAndFontMetrics();
2448 return mFontMetrics
;
2451 gfxFloat
* GetTabWidths(PRUint32 aTransformedStart
, PRUint32 aTransformedLength
);
2453 const gfxSkipCharsIterator
& GetEndHint() { return mTempIterator
; }
2456 void SetupJustificationSpacing();
2458 void InitFontGroupAndFontMetrics() {
2459 mFontGroup
= GetFontGroupForFrame(mFrame
, getter_AddRefs(mFontMetrics
));
2462 gfxTextRun
* mTextRun
;
2463 gfxFontGroup
* mFontGroup
;
2464 nsCOMPtr
<nsIFontMetrics
> mFontMetrics
;
2465 const nsStyleText
* mTextStyle
;
2466 const nsTextFragment
* mFrag
;
2467 nsIFrame
* mLineContainer
;
2468 nsTextFrame
* mFrame
;
2469 gfxSkipCharsIterator mStart
; // Offset in original and transformed string
2470 gfxSkipCharsIterator mTempIterator
;
2472 // Widths for each transformed string character, 0 for non-tab characters.
2473 // Either null, or pointing to the frame's tabWidthProperty.
2474 nsTArray
<gfxFloat
>* mTabWidths
;
2476 PRInt32 mLength
; // DOM string length, may be PR_INT32_MAX
2477 gfxFloat mWordSpacing
; // space for each whitespace char
2478 gfxFloat mLetterSpacing
; // space for each letter
2479 gfxFloat mJustificationSpacing
;
2480 gfxFloat mHyphenWidth
;
2481 gfxFloat mOffsetFromBlockOriginForTabs
;
2482 PRPackedBool mReflowing
;
2486 PropertyProvider::ComputeJustifiableCharacters(PRInt32 aOffset
, PRInt32 aLength
)
2488 // Scan non-skipped characters and count justifiable chars.
2489 nsSkipCharsRunIterator
2490 run(mStart
, nsSkipCharsRunIterator::LENGTH_INCLUDES_SKIPPED
, aLength
);
2491 run
.SetOriginalOffset(aOffset
);
2492 PRUint32 justifiableChars
= 0;
2493 PRBool isCJK
= IsChineseOrJapanese(mFrame
);
2494 while (run
.NextRun()) {
2496 for (i
= 0; i
< run
.GetRunLength(); ++i
) {
2498 IsJustifiableCharacter(mFrag
, run
.GetOriginalOffset() + i
, isCJK
);
2501 return justifiableChars
;
2505 * Finds the offset of the first character of the cluster containing aPos
2507 static void FindClusterStart(gfxTextRun
* aTextRun
, PRInt32 aOriginalStart
,
2508 gfxSkipCharsIterator
* aPos
)
2510 while (aPos
->GetOriginalOffset() > aOriginalStart
) {
2511 if (aPos
->IsOriginalCharSkipped() ||
2512 aTextRun
->IsClusterStart(aPos
->GetSkippedOffset())) {
2515 aPos
->AdvanceOriginal(-1);
2520 * Finds the offset of the last character of the cluster containing aPos
2522 static void FindClusterEnd(gfxTextRun
* aTextRun
, PRInt32 aOriginalEnd
,
2523 gfxSkipCharsIterator
* aPos
)
2525 NS_PRECONDITION(aPos
->GetOriginalOffset() < aOriginalEnd
,
2526 "character outside string");
2527 aPos
->AdvanceOriginal(1);
2528 while (aPos
->GetOriginalOffset() < aOriginalEnd
) {
2529 if (aPos
->IsOriginalCharSkipped() ||
2530 aTextRun
->IsClusterStart(aPos
->GetSkippedOffset())) {
2533 aPos
->AdvanceOriginal(1);
2535 aPos
->AdvanceOriginal(-1);
2538 // aStart, aLength in transformed string offsets
2540 PropertyProvider::GetSpacing(PRUint32 aStart
, PRUint32 aLength
,
2543 GetSpacingInternal(aStart
, aLength
, aSpacing
,
2544 (mTextRun
->GetFlags() & nsTextFrameUtils::TEXT_HAS_TAB
) == 0);
2548 CanAddSpacingAfter(gfxTextRun
* aTextRun
, PRUint32 aOffset
)
2550 if (aOffset
+ 1 >= aTextRun
->GetLength())
2552 return aTextRun
->IsClusterStart(aOffset
+ 1) &&
2553 aTextRun
->IsLigatureGroupStart(aOffset
+ 1);
2557 PropertyProvider::GetSpacingInternal(PRUint32 aStart
, PRUint32 aLength
,
2558 Spacing
* aSpacing
, PRBool aIgnoreTabs
)
2560 NS_PRECONDITION(IsInBounds(mStart
, mLength
, aStart
, aLength
), "Range out of bounds");
2563 for (index
= 0; index
< aLength
; ++index
) {
2564 aSpacing
[index
].mBefore
= 0.0;
2565 aSpacing
[index
].mAfter
= 0.0;
2568 // Find our offset into the original+transformed string
2569 gfxSkipCharsIterator
start(mStart
);
2570 start
.SetSkippedOffset(aStart
);
2572 // First, compute the word and letter spacing
2573 if (mWordSpacing
|| mLetterSpacing
) {
2574 // Iterate over non-skipped characters
2575 nsSkipCharsRunIterator
2576 run(start
, nsSkipCharsRunIterator::LENGTH_UNSKIPPED_ONLY
, aLength
);
2577 while (run
.NextRun()) {
2578 PRUint32 runOffsetInSubstring
= run
.GetSkippedOffset() - aStart
;
2580 gfxSkipCharsIterator iter
= run
.GetPos();
2581 for (i
= 0; i
< run
.GetRunLength(); ++i
) {
2582 if (CanAddSpacingAfter(mTextRun
, run
.GetSkippedOffset() + i
)) {
2583 // End of a cluster, not in a ligature: put letter-spacing after it
2584 aSpacing
[runOffsetInSubstring
+ i
].mAfter
+= mLetterSpacing
;
2586 if (IsCSSWordSpacingSpace(mFrag
, i
+ run
.GetOriginalOffset(),
2588 // It kinda sucks, but space characters can be part of clusters,
2589 // and even still be whitespace (I think!)
2590 iter
.SetSkippedOffset(run
.GetSkippedOffset() + i
);
2591 FindClusterEnd(mTextRun
, run
.GetOriginalOffset() + run
.GetRunLength(),
2593 aSpacing
[iter
.GetSkippedOffset() - aStart
].mAfter
+= mWordSpacing
;
2599 // Ignore tab spacing rather than computing it, if the tab size is 0
2601 aIgnoreTabs
= mFrame
->GetStyleText()->mTabSize
== 0;
2603 // Now add tab spacing, if there is any
2605 gfxFloat
* tabs
= GetTabWidths(aStart
, aLength
);
2607 for (index
= 0; index
< aLength
; ++index
) {
2608 aSpacing
[index
].mAfter
+= tabs
[index
];
2613 // Now add in justification spacing
2614 if (mJustificationSpacing
) {
2615 gfxFloat halfJustificationSpace
= mJustificationSpacing
/2;
2616 // Scan non-skipped characters and adjust justifiable chars, adding
2617 // justification space on either side of the cluster
2618 PRBool isCJK
= IsChineseOrJapanese(mFrame
);
2619 gfxSkipCharsIterator
justificationStart(mStart
), justificationEnd(mStart
);
2620 FindJustificationRange(&justificationStart
, &justificationEnd
);
2622 nsSkipCharsRunIterator
2623 run(start
, nsSkipCharsRunIterator::LENGTH_UNSKIPPED_ONLY
, aLength
);
2624 while (run
.NextRun()) {
2626 gfxSkipCharsIterator iter
= run
.GetPos();
2627 PRInt32 runOriginalOffset
= run
.GetOriginalOffset();
2628 for (i
= 0; i
< run
.GetRunLength(); ++i
) {
2629 PRInt32 iterOriginalOffset
= runOriginalOffset
+ i
;
2630 if (IsJustifiableCharacter(mFrag
, iterOriginalOffset
, isCJK
)) {
2631 iter
.SetOriginalOffset(iterOriginalOffset
);
2632 FindClusterStart(mTextRun
, runOriginalOffset
, &iter
);
2633 PRUint32 clusterFirstChar
= iter
.GetSkippedOffset();
2634 FindClusterEnd(mTextRun
, runOriginalOffset
+ run
.GetRunLength(), &iter
);
2635 PRUint32 clusterLastChar
= iter
.GetSkippedOffset();
2636 // Only apply justification to characters before justificationEnd
2637 if (clusterFirstChar
>= justificationStart
.GetSkippedOffset() &&
2638 clusterLastChar
< justificationEnd
.GetSkippedOffset()) {
2639 aSpacing
[clusterFirstChar
- aStart
].mBefore
+= halfJustificationSpace
;
2640 aSpacing
[clusterLastChar
- aStart
].mAfter
+= halfJustificationSpace
;
2649 ComputeTabWidthAppUnits(nsIFrame
* aFrame
, gfxTextRun
* aTextRun
)
2651 // Get the number of spaces from CSS -moz-tab-size
2652 const nsStyleText
* textStyle
= aFrame
->GetStyleText();
2654 // Round the space width when converting to appunits the same way
2656 gfxFloat spaceWidthAppUnits
=
2657 NS_roundf(GetFirstFontMetrics(
2658 GetFontGroupForFrame(aFrame
)).spaceWidth
*
2659 aTextRun
->GetAppUnitsPerDevUnit());
2660 return textStyle
->mTabSize
* spaceWidthAppUnits
;
2663 // aX and the result are in whole appunits.
2665 AdvanceToNextTab(gfxFloat aX
, nsIFrame
* aFrame
,
2666 gfxTextRun
* aTextRun
, gfxFloat
* aCachedTabWidth
)
2668 if (*aCachedTabWidth
< 0) {
2669 *aCachedTabWidth
= ComputeTabWidthAppUnits(aFrame
, aTextRun
);
2672 // Advance aX to the next multiple of *aCachedTabWidth. We must advance
2673 // by at least 1 appunit.
2674 // XXX should we make this 1 CSS pixel?
2675 return NS_ceil((aX
+ 1)/(*aCachedTabWidth
))*(*aCachedTabWidth
);
2679 PropertyProvider::GetTabWidths(PRUint32 aStart
, PRUint32 aLength
)
2683 mTabWidths
= static_cast<nsTArray
<gfxFloat
>*>
2684 (mFrame
->Properties().Get(TabWidthProperty()));
2686 NS_WARNING("We need precomputed tab widths, but they're not here...");
2690 if (!mLineContainer
) {
2691 // Intrinsic width computation does its own tab processing. We
2692 // just don't do anything here.
2696 nsAutoPtr
<nsTArray
<gfxFloat
> > tabs(new nsTArray
<gfxFloat
>());
2699 mFrame
->Properties().Set(TabWidthProperty(), tabs
);
2700 mTabWidths
= tabs
.forget();
2704 PRUint32 startOffset
= mStart
.GetSkippedOffset();
2705 PRUint32 tabsEnd
= startOffset
+ mTabWidths
->Length();
2706 if (tabsEnd
< aStart
+ aLength
) {
2708 NS_WARNING("We need precomputed tab widths, but we don't have enough...");
2712 if (!mTabWidths
->AppendElements(aStart
+ aLength
- tabsEnd
))
2715 gfxFloat tabWidth
= -1;
2716 for (PRUint32 i
= tabsEnd
; i
< aStart
+ aLength
; ++i
) {
2718 GetSpacingInternal(i
, 1, &spacing
, PR_TRUE
);
2719 mOffsetFromBlockOriginForTabs
+= spacing
.mBefore
;
2721 if (mTextRun
->GetChar(i
) != '\t') {
2722 (*mTabWidths
)[i
- startOffset
] = 0;
2723 if (mTextRun
->IsClusterStart(i
)) {
2724 PRUint32 clusterEnd
= i
+ 1;
2725 while (clusterEnd
< mTextRun
->GetLength() &&
2726 !mTextRun
->IsClusterStart(clusterEnd
)) {
2729 mOffsetFromBlockOriginForTabs
+=
2730 mTextRun
->GetAdvanceWidth(i
, clusterEnd
- i
, nsnull
);
2733 double nextTab
= AdvanceToNextTab(mOffsetFromBlockOriginForTabs
,
2734 mFrame
, mTextRun
, &tabWidth
);
2735 (*mTabWidths
)[i
- startOffset
] = nextTab
- mOffsetFromBlockOriginForTabs
;
2736 mOffsetFromBlockOriginForTabs
= nextTab
;
2739 mOffsetFromBlockOriginForTabs
+= spacing
.mAfter
;
2743 return mTabWidths
->Elements() + aStart
- startOffset
;
2747 PropertyProvider::GetHyphenWidth()
2749 if (mHyphenWidth
< 0) {
2750 gfxTextRunCache::AutoTextRun
hyphenTextRun(GetHyphenTextRun(mTextRun
, nsnull
, mFrame
));
2751 mHyphenWidth
= mLetterSpacing
;
2752 if (hyphenTextRun
.get()) {
2753 mHyphenWidth
+= hyphenTextRun
->GetAdvanceWidth(0, hyphenTextRun
->GetLength(), nsnull
);
2756 return mHyphenWidth
;
2760 PropertyProvider::GetHyphenationBreaks(PRUint32 aStart
, PRUint32 aLength
,
2761 PRPackedBool
* aBreakBefore
)
2763 NS_PRECONDITION(IsInBounds(mStart
, mLength
, aStart
, aLength
), "Range out of bounds");
2764 NS_PRECONDITION(mLength
!= PR_INT32_MAX
, "Can't call this with undefined length");
2766 if (!mTextStyle
->WhiteSpaceCanWrap()) {
2767 memset(aBreakBefore
, PR_FALSE
, aLength
);
2771 // Iterate through the original-string character runs
2772 nsSkipCharsRunIterator
2773 run(mStart
, nsSkipCharsRunIterator::LENGTH_UNSKIPPED_ONLY
, aLength
);
2774 run
.SetSkippedOffset(aStart
);
2775 // We need to visit skipped characters so that we can detect SHY
2776 run
.SetVisitSkipped();
2778 PRInt32 prevTrailingCharOffset
= run
.GetPos().GetOriginalOffset() - 1;
2779 PRBool allowHyphenBreakBeforeNextChar
=
2780 prevTrailingCharOffset
>= mStart
.GetOriginalOffset() &&
2781 prevTrailingCharOffset
< mStart
.GetOriginalOffset() + mLength
&&
2782 mFrag
->CharAt(prevTrailingCharOffset
) == CH_SHY
;
2784 while (run
.NextRun()) {
2785 NS_ASSERTION(run
.GetRunLength() > 0, "Shouldn't return zero-length runs");
2786 if (run
.IsSkipped()) {
2787 // Check if there's a soft hyphen which would let us hyphenate before
2788 // the next non-skipped character. Don't look at soft hyphens followed
2789 // by other skipped characters, we won't use them.
2790 allowHyphenBreakBeforeNextChar
=
2791 mFrag
->CharAt(run
.GetOriginalOffset() + run
.GetRunLength() - 1) == CH_SHY
;
2793 PRInt32 runOffsetInSubstring
= run
.GetSkippedOffset() - aStart
;
2794 memset(aBreakBefore
+ runOffsetInSubstring
, 0, run
.GetRunLength());
2795 // Don't allow hyphen breaks at the start of the line
2796 aBreakBefore
[runOffsetInSubstring
] = allowHyphenBreakBeforeNextChar
&&
2797 (!(mFrame
->GetStateBits() & TEXT_START_OF_LINE
) ||
2798 run
.GetSkippedOffset() > mStart
.GetSkippedOffset());
2799 allowHyphenBreakBeforeNextChar
= PR_FALSE
;
2805 PropertyProvider::InitializeForDisplay(PRBool aTrimAfter
)
2807 nsTextFrame::TrimmedOffsets trimmed
=
2808 mFrame
->GetTrimmedOffsets(mFrag
, aTrimAfter
);
2809 mStart
.SetOriginalOffset(trimmed
.mStart
);
2810 mLength
= trimmed
.mLength
;
2811 SetupJustificationSpacing();
2814 static PRUint32
GetSkippedDistance(const gfxSkipCharsIterator
& aStart
,
2815 const gfxSkipCharsIterator
& aEnd
)
2817 return aEnd
.GetSkippedOffset() - aStart
.GetSkippedOffset();
2821 PropertyProvider::FindJustificationRange(gfxSkipCharsIterator
* aStart
,
2822 gfxSkipCharsIterator
* aEnd
)
2824 NS_PRECONDITION(mLength
!= PR_INT32_MAX
, "Can't call this with undefined length");
2825 NS_ASSERTION(aStart
&& aEnd
, "aStart or/and aEnd is null");
2827 aStart
->SetOriginalOffset(mStart
.GetOriginalOffset());
2828 aEnd
->SetOriginalOffset(mStart
.GetOriginalOffset() + mLength
);
2830 // Ignore first cluster at start of line for justification purposes
2831 if (mFrame
->GetStateBits() & TEXT_START_OF_LINE
) {
2832 while (aStart
->GetOriginalOffset() < aEnd
->GetOriginalOffset()) {
2833 aStart
->AdvanceOriginal(1);
2834 if (!aStart
->IsOriginalCharSkipped() &&
2835 mTextRun
->IsClusterStart(aStart
->GetSkippedOffset()))
2840 // Ignore trailing cluster at end of line for justification purposes
2841 if (mFrame
->GetStateBits() & TEXT_END_OF_LINE
) {
2842 while (aEnd
->GetOriginalOffset() > aStart
->GetOriginalOffset()) {
2843 aEnd
->AdvanceOriginal(-1);
2844 if (!aEnd
->IsOriginalCharSkipped() &&
2845 mTextRun
->IsClusterStart(aEnd
->GetSkippedOffset()))
2852 PropertyProvider::SetupJustificationSpacing()
2854 NS_PRECONDITION(mLength
!= PR_INT32_MAX
, "Can't call this with undefined length");
2856 if (!(mFrame
->GetStateBits() & TEXT_JUSTIFICATION_ENABLED
))
2859 gfxSkipCharsIterator
start(mStart
), end(mStart
);
2860 // We can't just use our mLength here; when InitializeForDisplay is
2861 // called with PR_FALSE for aTrimAfter, we still shouldn't be assigning
2862 // justification space to any trailing whitespace.
2863 nsTextFrame::TrimmedOffsets trimmed
=
2864 mFrame
->GetTrimmedOffsets(mFrag
, PR_TRUE
);
2865 end
.AdvanceOriginal(trimmed
.mLength
);
2866 gfxSkipCharsIterator
realEnd(end
);
2867 FindJustificationRange(&start
, &end
);
2869 PRInt32 justifiableCharacters
=
2870 ComputeJustifiableCharacters(start
.GetOriginalOffset(),
2871 end
.GetOriginalOffset() - start
.GetOriginalOffset());
2872 if (justifiableCharacters
== 0) {
2873 // Nothing to do, nothing is justifiable and we shouldn't have any
2874 // justification space assigned
2878 gfxFloat naturalWidth
=
2879 mTextRun
->GetAdvanceWidth(mStart
.GetSkippedOffset(),
2880 GetSkippedDistance(mStart
, realEnd
), this);
2881 if (mFrame
->GetStateBits() & TEXT_HYPHEN_BREAK
) {
2882 gfxTextRunCache::AutoTextRun
hyphenTextRun(GetHyphenTextRun(mTextRun
, nsnull
, mFrame
));
2883 if (hyphenTextRun
.get()) {
2885 hyphenTextRun
->GetAdvanceWidth(0, hyphenTextRun
->GetLength(), nsnull
);
2888 gfxFloat totalJustificationSpace
= mFrame
->GetSize().width
- naturalWidth
;
2889 if (totalJustificationSpace
<= 0) {
2890 // No space available
2894 mJustificationSpacing
= totalJustificationSpace
/justifiableCharacters
;
2897 //----------------------------------------------------------------------
2899 // Helper class for managing blinking text
2901 class nsBlinkTimer
: public nsITimerCallback
2905 virtual ~nsBlinkTimer();
2909 void AddFrame(nsPresContext
* aPresContext
, nsIFrame
* aFrame
);
2911 PRBool
RemoveFrame(nsIFrame
* aFrame
);
2913 PRInt32
FrameCount();
2919 NS_DECL_NSITIMERCALLBACK
2921 static nsresult
AddBlinkFrame(nsPresContext
* aPresContext
, nsIFrame
* aFrame
);
2922 static nsresult
RemoveBlinkFrame(nsIFrame
* aFrame
);
2924 static PRBool
GetBlinkIsOff() { return sState
== 3; }
2929 nsPresContext
* mPresContext
; // pres context associated with the frame
2933 FrameData(nsPresContext
* aPresContext
,
2935 : mPresContext(aPresContext
), mFrame(aFrame
) {}
2938 class FrameDataComparator
{
2940 PRBool
Equals(const FrameData
& aTimer
, nsIFrame
* const& aFrame
) const {
2941 return aTimer
.mFrame
== aFrame
;
2945 nsCOMPtr
<nsITimer
> mTimer
;
2946 nsTArray
<FrameData
> mFrames
;
2947 nsPresContext
* mPresContext
;
2951 static nsBlinkTimer
* sTextBlinker
;
2952 static PRUint32 sState
; // 0-2 == on; 3 == off
2956 nsBlinkTimer
* nsBlinkTimer::sTextBlinker
= nsnull
;
2957 PRUint32
nsBlinkTimer::sState
= 0;
2960 static PRTime gLastTick
;
2963 nsBlinkTimer::nsBlinkTimer()
2967 nsBlinkTimer::~nsBlinkTimer()
2970 sTextBlinker
= nsnull
;
2973 void nsBlinkTimer::Start()
2976 mTimer
= do_CreateInstance("@mozilla.org/timer;1", &rv
);
2978 mTimer
->InitWithCallback(this, 250, nsITimer::TYPE_REPEATING_PRECISE
);
2982 void nsBlinkTimer::Stop()
2984 if (nsnull
!= mTimer
) {
2990 NS_IMPL_ISUPPORTS1(nsBlinkTimer
, nsITimerCallback
)
2992 void nsBlinkTimer::AddFrame(nsPresContext
* aPresContext
, nsIFrame
* aFrame
) {
2993 mFrames
.AppendElement(FrameData(aPresContext
, aFrame
));
2994 if (1 == mFrames
.Length()) {
2999 PRBool
nsBlinkTimer::RemoveFrame(nsIFrame
* aFrame
) {
3000 mFrames
.RemoveElement(aFrame
, FrameDataComparator());
3002 if (mFrames
.IsEmpty()) {
3008 PRInt32
nsBlinkTimer::FrameCount() {
3009 return PRInt32(mFrames
.Length());
3012 NS_IMETHODIMP
nsBlinkTimer::Notify(nsITimer
*timer
)
3014 // Toggle blink state bit so that text code knows whether or not to
3015 // render. All text code shares the same flag so that they all blink
3017 sState
= (sState
+ 1) % 4;
3018 if (sState
== 1 || sState
== 2)
3019 // States 0, 1, and 2 are all the same.
3023 PRTime now
= PR_Now();
3026 LL_SUB(delta
, now
, gLastTick
);
3028 PR_snprintf(buf
, sizeof(buf
), "%lldusec", delta
);
3029 printf("%s\n", buf
);
3032 PRUint32 i
, n
= mFrames
.Length();
3033 for (i
= 0; i
< n
; i
++) {
3034 FrameData
& frameData
= mFrames
.ElementAt(i
);
3036 // Determine damaged area and tell view manager to redraw it
3037 // blink doesn't blink outline ... I hope
3038 nsRect
bounds(nsPoint(0, 0), frameData
.mFrame
->GetSize());
3039 frameData
.mFrame
->Invalidate(bounds
);
3046 nsresult
nsBlinkTimer::AddBlinkFrame(nsPresContext
* aPresContext
, nsIFrame
* aFrame
)
3050 sTextBlinker
= new nsBlinkTimer
;
3051 if (!sTextBlinker
) return NS_ERROR_OUT_OF_MEMORY
;
3054 NS_ADDREF(sTextBlinker
);
3056 sTextBlinker
->AddFrame(aPresContext
, aFrame
);
3062 nsresult
nsBlinkTimer::RemoveBlinkFrame(nsIFrame
* aFrame
)
3064 NS_ASSERTION(sTextBlinker
, "Should have blink timer here");
3066 nsBlinkTimer
* blinkTimer
= sTextBlinker
; // copy so we can call NS_RELEASE on it
3067 if (!blinkTimer
) return NS_OK
;
3069 blinkTimer
->RemoveFrame(aFrame
);
3070 NS_RELEASE(blinkTimer
);
3075 //----------------------------------------------------------------------
3078 EnsureDifferentColors(nscolor colorA
, nscolor colorB
)
3080 if (colorA
== colorB
) {
3082 res
= NS_RGB(NS_GET_R(colorA
) ^ 0xff,
3083 NS_GET_G(colorA
) ^ 0xff,
3084 NS_GET_B(colorA
) ^ 0xff);
3090 //-----------------------------------------------------------------------------
3093 DarkenColor(nscolor aColor
)
3095 PRUint16 hue
, sat
, value
;
3098 // convert the RBG to HSV so we can get the lightness (which is the v)
3099 NS_RGB2HSV(aColor
, hue
, sat
, value
, alpha
);
3101 // The goal here is to send white to black while letting colored
3102 // stuff stay colored... So we adopt the following approach.
3103 // Something with sat = 0 should end up with value = 0. Something
3104 // with a high sat can end up with a high value and it's ok.... At
3105 // the same time, we don't want to make things lighter. Do
3106 // something simple, since it seems to work.
3109 // convert this color back into the RGB color space.
3110 NS_HSV2RGB(aColor
, hue
, sat
, value
, alpha
);
3115 // Check whether we should darken text colors. We need to do this if
3116 // background images and colors are being suppressed, because that means
3117 // light text will not be visible against the (presumed light-colored) background.
3119 ShouldDarkenColors(nsPresContext
* aPresContext
)
3121 return !aPresContext
->GetBackgroundColorDraw() &&
3122 !aPresContext
->GetBackgroundImageDraw();
3125 nsTextPaintStyle::nsTextPaintStyle(nsTextFrame
* aFrame
)
3127 mPresContext(aFrame
->PresContext()),
3128 mInitCommonColors(PR_FALSE
),
3129 mInitSelectionColors(PR_FALSE
)
3131 for (PRUint32 i
= 0; i
< NS_ARRAY_LENGTH(mSelectionStyle
); i
++)
3132 mSelectionStyle
[i
].mInit
= PR_FALSE
;
3136 nsTextPaintStyle::EnsureSufficientContrast(nscolor
*aForeColor
, nscolor
*aBackColor
)
3140 // If the combination of selection background color and frame background color
3141 // is sufficient contrast, don't exchange the selection colors.
3142 PRInt32 backLuminosityDifference
=
3143 NS_LUMINOSITY_DIFFERENCE(*aBackColor
, mFrameBackgroundColor
);
3144 if (backLuminosityDifference
>= mSufficientContrast
)
3147 // Otherwise, we should use the higher-contrast color for the selection
3148 // background color.
3149 PRInt32 foreLuminosityDifference
=
3150 NS_LUMINOSITY_DIFFERENCE(*aForeColor
, mFrameBackgroundColor
);
3151 if (backLuminosityDifference
< foreLuminosityDifference
) {
3152 nscolor tmpColor
= *aForeColor
;
3153 *aForeColor
= *aBackColor
;
3154 *aBackColor
= tmpColor
;
3161 nsTextPaintStyle::GetTextColor()
3163 nscolor color
= mFrame
->GetVisitedDependentColor(eCSSProperty_color
);
3164 if (ShouldDarkenColors(mPresContext
)) {
3165 color
= DarkenColor(color
);
3171 nsTextPaintStyle::GetSelectionColors(nscolor
* aForeColor
,
3172 nscolor
* aBackColor
)
3174 NS_ASSERTION(aForeColor
, "aForeColor is null");
3175 NS_ASSERTION(aBackColor
, "aBackColor is null");
3177 if (!InitSelectionColors())
3180 *aForeColor
= mSelectionTextColor
;
3181 *aBackColor
= mSelectionBGColor
;
3186 nsTextPaintStyle::GetHighlightColors(nscolor
* aForeColor
,
3187 nscolor
* aBackColor
)
3189 NS_ASSERTION(aForeColor
, "aForeColor is null");
3190 NS_ASSERTION(aBackColor
, "aBackColor is null");
3192 nsILookAndFeel
* look
= mPresContext
->LookAndFeel();
3193 nscolor foreColor
, backColor
;
3194 look
->GetColor(nsILookAndFeel::eColor_TextHighlightBackground
,
3196 look
->GetColor(nsILookAndFeel::eColor_TextHighlightForeground
,
3198 EnsureSufficientContrast(&foreColor
, &backColor
);
3199 *aForeColor
= foreColor
;
3200 *aBackColor
= backColor
;
3204 nsTextPaintStyle::GetIMESelectionColors(PRInt32 aIndex
,
3205 nscolor
* aForeColor
,
3206 nscolor
* aBackColor
)
3208 NS_ASSERTION(aForeColor
, "aForeColor is null");
3209 NS_ASSERTION(aBackColor
, "aBackColor is null");
3210 NS_ASSERTION(aIndex
>= 0 && aIndex
< 5, "Index out of range");
3212 nsSelectionStyle
* selectionStyle
= GetSelectionStyle(aIndex
);
3213 *aForeColor
= selectionStyle
->mTextColor
;
3214 *aBackColor
= selectionStyle
->mBGColor
;
3218 nsTextPaintStyle::GetSelectionUnderlineForPaint(PRInt32 aIndex
,
3219 nscolor
* aLineColor
,
3220 float* aRelativeSize
,
3223 NS_ASSERTION(aLineColor
, "aLineColor is null");
3224 NS_ASSERTION(aRelativeSize
, "aRelativeSize is null");
3225 NS_ASSERTION(aIndex
>= 0 && aIndex
< 5, "Index out of range");
3227 nsSelectionStyle
* selectionStyle
= GetSelectionStyle(aIndex
);
3228 if (selectionStyle
->mUnderlineStyle
== NS_STYLE_BORDER_STYLE_NONE
||
3229 selectionStyle
->mUnderlineColor
== NS_TRANSPARENT
||
3230 selectionStyle
->mUnderlineRelativeSize
<= 0.0f
)
3233 *aLineColor
= selectionStyle
->mUnderlineColor
;
3234 *aRelativeSize
= selectionStyle
->mUnderlineRelativeSize
;
3235 *aStyle
= selectionStyle
->mUnderlineStyle
;
3240 nsTextPaintStyle::InitCommonColors()
3242 if (mInitCommonColors
)
3246 nsCSSRendering::FindNonTransparentBackgroundFrame(mFrame
);
3247 NS_ASSERTION(bgFrame
, "Cannot find NonTransparentBackgroundFrame.");
3249 bgFrame
->GetVisitedDependentColor(eCSSProperty_background_color
);
3251 nscolor defaultBgColor
= mPresContext
->DefaultBackgroundColor();
3252 mFrameBackgroundColor
= NS_ComposeColors(defaultBgColor
, bgColor
);
3254 if (bgFrame
->IsThemed()) {
3255 // Assume a native widget has sufficient contrast always
3256 mSufficientContrast
= 0;
3257 mInitCommonColors
= PR_TRUE
;
3261 NS_ASSERTION(NS_GET_A(defaultBgColor
) == 255,
3262 "default background color is not opaque");
3264 nsILookAndFeel
* look
= mPresContext
->LookAndFeel();
3265 nscolor defaultWindowBackgroundColor
, selectionTextColor
, selectionBGColor
;
3266 look
->GetColor(nsILookAndFeel::eColor_TextSelectBackground
,
3268 look
->GetColor(nsILookAndFeel::eColor_TextSelectForeground
,
3269 selectionTextColor
);
3270 look
->GetColor(nsILookAndFeel::eColor_WindowBackground
,
3271 defaultWindowBackgroundColor
);
3273 mSufficientContrast
=
3274 NS_MIN(NS_MIN(NS_SUFFICIENT_LUMINOSITY_DIFFERENCE
,
3275 NS_LUMINOSITY_DIFFERENCE(selectionTextColor
,
3277 NS_LUMINOSITY_DIFFERENCE(defaultWindowBackgroundColor
,
3280 mInitCommonColors
= PR_TRUE
;
3284 FindElementAncestorForMozSelection(nsIContent
* aContent
)
3286 NS_ENSURE_TRUE(aContent
, nsnull
);
3287 while (aContent
&& aContent
->IsInNativeAnonymousSubtree()) {
3288 aContent
= aContent
->GetBindingParent();
3290 NS_ASSERTION(aContent
, "aContent isn't in non-anonymous tree?");
3291 while (aContent
&& !aContent
->IsElement()) {
3292 aContent
= aContent
->GetParent();
3294 return aContent
? aContent
->AsElement() : nsnull
;
3298 nsTextPaintStyle::InitSelectionColors()
3300 if (mInitSelectionColors
)
3303 PRInt16 selectionFlags
;
3304 PRInt16 selectionStatus
= mFrame
->GetSelectionStatus(&selectionFlags
);
3305 if (!(selectionFlags
& nsISelectionDisplay::DISPLAY_TEXT
) ||
3306 selectionStatus
< nsISelectionController::SELECTION_ON
) {
3307 // Not displaying the normal selection.
3308 // We're not caching this fact, so every call to GetSelectionColors
3309 // will come through here. We could avoid this, but it's not really worth it.
3313 mInitSelectionColors
= PR_TRUE
;
3315 nsIFrame
* nonGeneratedAncestor
= nsLayoutUtils::GetNonGeneratedAncestor(mFrame
);
3316 Element
* selectionElement
=
3317 FindElementAncestorForMozSelection(nonGeneratedAncestor
->GetContent());
3319 if (selectionElement
&&
3320 selectionStatus
== nsISelectionController::SELECTION_ON
) {
3321 nsRefPtr
<nsStyleContext
> sc
= nsnull
;
3322 sc
= mPresContext
->StyleSet()->
3323 ProbePseudoElementStyle(selectionElement
,
3324 nsCSSPseudoElements::ePseudo_mozSelection
,
3325 mFrame
->GetStyleContext());
3326 // Use -moz-selection pseudo class.
3329 sc
->GetVisitedDependentColor(eCSSProperty_background_color
);
3330 mSelectionTextColor
= sc
->GetVisitedDependentColor(eCSSProperty_color
);
3335 nsILookAndFeel
* look
= mPresContext
->LookAndFeel();
3337 nscolor selectionBGColor
;
3338 look
->GetColor(nsILookAndFeel::eColor_TextSelectBackground
,
3341 if (selectionStatus
== nsISelectionController::SELECTION_ATTENTION
) {
3342 look
->GetColor(nsILookAndFeel::eColor_TextSelectBackgroundAttention
,
3344 mSelectionBGColor
= EnsureDifferentColors(mSelectionBGColor
,
3346 } else if (selectionStatus
!= nsISelectionController::SELECTION_ON
) {
3347 look
->GetColor(nsILookAndFeel::eColor_TextSelectBackgroundDisabled
,
3349 mSelectionBGColor
= EnsureDifferentColors(mSelectionBGColor
,
3352 mSelectionBGColor
= selectionBGColor
;
3355 look
->GetColor(nsILookAndFeel::eColor_TextSelectForeground
,
3356 mSelectionTextColor
);
3358 // On MacOS X, we don't exchange text color and BG color.
3359 if (mSelectionTextColor
== NS_DONT_CHANGE_COLOR
) {
3360 nscoord frameColor
= mFrame
->GetVisitedDependentColor(eCSSProperty_color
);
3361 mSelectionTextColor
= EnsureDifferentColors(frameColor
, mSelectionBGColor
);
3363 EnsureSufficientContrast(&mSelectionTextColor
, &mSelectionBGColor
);
3368 nsTextPaintStyle::nsSelectionStyle
*
3369 nsTextPaintStyle::GetSelectionStyle(PRInt32 aIndex
)
3371 InitSelectionStyle(aIndex
);
3372 return &mSelectionStyle
[aIndex
];
3376 nsILookAndFeel::nsColorID mForeground
, mBackground
, mLine
;
3377 nsILookAndFeel::nsMetricID mLineStyle
;
3378 nsILookAndFeel::nsMetricFloatID mLineRelativeSize
;
3380 static StyleIDs SelectionStyleIDs
[] = {
3381 { nsILookAndFeel::eColor_IMERawInputForeground
,
3382 nsILookAndFeel::eColor_IMERawInputBackground
,
3383 nsILookAndFeel::eColor_IMERawInputUnderline
,
3384 nsILookAndFeel::eMetric_IMERawInputUnderlineStyle
,
3385 nsILookAndFeel::eMetricFloat_IMEUnderlineRelativeSize
},
3386 { nsILookAndFeel::eColor_IMESelectedRawTextForeground
,
3387 nsILookAndFeel::eColor_IMESelectedRawTextBackground
,
3388 nsILookAndFeel::eColor_IMESelectedRawTextUnderline
,
3389 nsILookAndFeel::eMetric_IMESelectedRawTextUnderlineStyle
,
3390 nsILookAndFeel::eMetricFloat_IMEUnderlineRelativeSize
},
3391 { nsILookAndFeel::eColor_IMEConvertedTextForeground
,
3392 nsILookAndFeel::eColor_IMEConvertedTextBackground
,
3393 nsILookAndFeel::eColor_IMEConvertedTextUnderline
,
3394 nsILookAndFeel::eMetric_IMEConvertedTextUnderlineStyle
,
3395 nsILookAndFeel::eMetricFloat_IMEUnderlineRelativeSize
},
3396 { nsILookAndFeel::eColor_IMESelectedConvertedTextForeground
,
3397 nsILookAndFeel::eColor_IMESelectedConvertedTextBackground
,
3398 nsILookAndFeel::eColor_IMESelectedConvertedTextUnderline
,
3399 nsILookAndFeel::eMetric_IMESelectedConvertedTextUnderline
,
3400 nsILookAndFeel::eMetricFloat_IMEUnderlineRelativeSize
},
3401 { nsILookAndFeel::eColor_LAST_COLOR
,
3402 nsILookAndFeel::eColor_LAST_COLOR
,
3403 nsILookAndFeel::eColor_SpellCheckerUnderline
,
3404 nsILookAndFeel::eMetric_SpellCheckerUnderlineStyle
,
3405 nsILookAndFeel::eMetricFloat_SpellCheckerUnderlineRelativeSize
}
3408 static PRUint8 sUnderlineStyles
[] = {
3409 nsCSSRendering::DECORATION_STYLE_NONE
, // NS_UNDERLINE_STYLE_NONE 0
3410 nsCSSRendering::DECORATION_STYLE_DOTTED
, // NS_UNDERLINE_STYLE_DOTTED 1
3411 nsCSSRendering::DECORATION_STYLE_DASHED
, // NS_UNDERLINE_STYLE_DASHED 2
3412 nsCSSRendering::DECORATION_STYLE_SOLID
, // NS_UNDERLINE_STYLE_SOLID 3
3413 nsCSSRendering::DECORATION_STYLE_DOUBLE
, // NS_UNDERLINE_STYLE_DOUBLE 4
3414 nsCSSRendering::DECORATION_STYLE_WAVY
// NS_UNDERLINE_STYLE_WAVY 5
3418 nsTextPaintStyle::InitSelectionStyle(PRInt32 aIndex
)
3420 NS_ASSERTION(aIndex
>= 0 && aIndex
< 5, "aIndex is invalid");
3421 nsSelectionStyle
* selectionStyle
= &mSelectionStyle
[aIndex
];
3422 if (selectionStyle
->mInit
)
3425 StyleIDs
* styleIDs
= &SelectionStyleIDs
[aIndex
];
3427 nsILookAndFeel
* look
= mPresContext
->LookAndFeel();
3428 nscolor foreColor
, backColor
;
3429 if (styleIDs
->mForeground
== nsILookAndFeel::eColor_LAST_COLOR
) {
3430 foreColor
= NS_SAME_AS_FOREGROUND_COLOR
;
3432 look
->GetColor(styleIDs
->mForeground
, foreColor
);
3434 if (styleIDs
->mBackground
== nsILookAndFeel::eColor_LAST_COLOR
) {
3435 backColor
= NS_TRANSPARENT
;
3437 look
->GetColor(styleIDs
->mBackground
, backColor
);
3440 // Convert special color to actual color
3441 NS_ASSERTION(foreColor
!= NS_TRANSPARENT
,
3442 "foreColor cannot be NS_TRANSPARENT");
3443 NS_ASSERTION(backColor
!= NS_SAME_AS_FOREGROUND_COLOR
,
3444 "backColor cannot be NS_SAME_AS_FOREGROUND_COLOR");
3445 NS_ASSERTION(backColor
!= NS_40PERCENT_FOREGROUND_COLOR
,
3446 "backColor cannot be NS_40PERCENT_FOREGROUND_COLOR");
3448 foreColor
= GetResolvedForeColor(foreColor
, GetTextColor(), backColor
);
3450 if (NS_GET_A(backColor
) > 0)
3451 EnsureSufficientContrast(&foreColor
, &backColor
);
3456 GetSelectionUnderline(mPresContext
, aIndex
,
3457 &lineColor
, &relativeSize
, &lineStyle
);
3458 lineColor
= GetResolvedForeColor(lineColor
, foreColor
, backColor
);
3460 selectionStyle
->mTextColor
= foreColor
;
3461 selectionStyle
->mBGColor
= backColor
;
3462 selectionStyle
->mUnderlineColor
= lineColor
;
3463 selectionStyle
->mUnderlineStyle
= lineStyle
;
3464 selectionStyle
->mUnderlineRelativeSize
= relativeSize
;
3465 selectionStyle
->mInit
= PR_TRUE
;
3469 nsTextPaintStyle::GetSelectionUnderline(nsPresContext
* aPresContext
,
3471 nscolor
* aLineColor
,
3472 float* aRelativeSize
,
3475 NS_ASSERTION(aPresContext
, "aPresContext is null");
3476 NS_ASSERTION(aRelativeSize
, "aRelativeSize is null");
3477 NS_ASSERTION(aStyle
, "aStyle is null");
3478 NS_ASSERTION(aIndex
>= 0 && aIndex
< 5, "Index out of range");
3480 nsILookAndFeel
* look
= aPresContext
->LookAndFeel();
3482 StyleIDs
& styleID
= SelectionStyleIDs
[aIndex
];
3487 look
->GetColor(styleID
.mLine
, color
);
3488 look
->GetMetric(styleID
.mLineStyle
, style
);
3489 if (!NS_IS_VALID_UNDERLINE_STYLE(style
)) {
3490 NS_ERROR("Invalid underline style value is specified");
3491 style
= NS_UNDERLINE_STYLE_SOLID
;
3493 look
->GetMetric(styleID
.mLineRelativeSize
, size
);
3495 NS_ASSERTION(size
, "selection underline relative size must be larger than 0");
3498 *aLineColor
= color
;
3500 *aRelativeSize
= size
;
3501 *aStyle
= sUnderlineStyles
[style
];
3503 return sUnderlineStyles
[style
] != nsCSSRendering::DECORATION_STYLE_NONE
&&
3504 color
!= NS_TRANSPARENT
&&
3508 inline nscolor
Get40PercentColor(nscolor aForeColor
, nscolor aBackColor
)
3510 nscolor foreColor
= NS_RGBA(NS_GET_R(aForeColor
),
3511 NS_GET_G(aForeColor
),
3512 NS_GET_B(aForeColor
),
3513 (PRUint8
)(255 * 0.4f
));
3514 // Don't use true alpha color for readability.
3515 return NS_ComposeColors(aBackColor
, foreColor
);
3519 nsTextPaintStyle::GetResolvedForeColor(nscolor aColor
,
3520 nscolor aDefaultForeColor
,
3523 if (aColor
== NS_SAME_AS_FOREGROUND_COLOR
)
3524 return aDefaultForeColor
;
3526 if (aColor
!= NS_40PERCENT_FOREGROUND_COLOR
)
3529 // Get actual background color
3530 nscolor actualBGColor
= aBackColor
;
3531 if (actualBGColor
== NS_TRANSPARENT
) {
3533 actualBGColor
= mFrameBackgroundColor
;
3535 return Get40PercentColor(aDefaultForeColor
, actualBGColor
);
3538 //-----------------------------------------------------------------------------
3540 #ifdef ACCESSIBILITY
3541 already_AddRefed
<nsAccessible
>
3542 nsTextFrame::CreateAccessible()
3545 nsAutoString renderedWhitespace
;
3546 GetRenderedText(&renderedWhitespace
, nsnull
, nsnull
, 0, 1);
3547 if (renderedWhitespace
.IsEmpty()) {
3552 nsAccessibilityService
* accService
= nsIPresShell::AccService();
3554 return accService
->CreateHTMLTextAccessible(mContent
,
3555 PresContext()->PresShell());
3562 //-----------------------------------------------------------------------------
3564 nsTextFrame::Init(nsIContent
* aContent
,
3566 nsIFrame
* aPrevInFlow
)
3568 NS_ASSERTION(!aPrevInFlow
, "Can't be a continuation!");
3569 NS_PRECONDITION(aContent
->IsNodeOfType(nsINode::eTEXT
),
3572 // Remove any NewlineOffsetProperty since it might be invalid
3573 // if the content was modified while there was no frame
3574 aContent
->DeleteProperty(nsGkAtoms::newline
);
3576 // Since our content has a frame now, this flag is no longer needed.
3577 aContent
->UnsetFlags(NS_CREATE_FRAME_IF_NON_WHITESPACE
);
3578 // We're not a continuing frame.
3579 // mContentOffset = 0; not necessary since we get zeroed out at init
3580 return nsFrame::Init(aContent
, aParent
, aPrevInFlow
);
3584 nsTextFrame::ClearFrameOffsetCache()
3586 // See if we need to remove ourselves from the offset cache
3587 if (GetStateBits() & TEXT_IN_OFFSET_CACHE
) {
3588 nsIFrame
* primaryFrame
= mContent
->GetPrimaryFrame();
3590 // The primary frame might be null here. For example, nsLineBox::DeleteLineList
3591 // just destroys the frames in order, which means that the primary frame is already
3592 // dead if we're a continuing text frame, in which case, all of its properties are
3593 // gone, and we don't need to worry about deleting this property here.
3594 primaryFrame
->Properties().Delete(OffsetToFrameProperty());
3596 RemoveStateBits(TEXT_IN_OFFSET_CACHE
);
3601 nsTextFrame::DestroyFrom(nsIFrame
* aDestructRoot
)
3603 ClearFrameOffsetCache();
3605 // We might want to clear NS_CREATE_FRAME_IF_NON_WHITESPACE or
3606 // NS_REFRAME_IF_WHITESPACE on mContent here, since our parent frame
3607 // type might be changing. Not clear whether it's worth it.
3608 ClearTextRun(nsnull
);
3609 if (mNextContinuation
) {
3610 mNextContinuation
->SetPrevInFlow(nsnull
);
3612 // Let the base class destroy the frame
3613 nsFrame::DestroyFrom(aDestructRoot
);
3616 class nsContinuingTextFrame
: public nsTextFrame
{
3618 NS_DECL_FRAMEARENA_HELPERS
3620 friend nsIFrame
* NS_NewContinuingTextFrame(nsIPresShell
* aPresShell
, nsStyleContext
* aContext
);
3622 NS_IMETHOD
Init(nsIContent
* aContent
,
3624 nsIFrame
* aPrevInFlow
);
3626 virtual void DestroyFrom(nsIFrame
* aDestructRoot
);
3628 virtual nsIFrame
* GetPrevContinuation() const {
3629 return mPrevContinuation
;
3631 NS_IMETHOD
SetPrevContinuation(nsIFrame
* aPrevContinuation
) {
3632 NS_ASSERTION (!aPrevContinuation
|| GetType() == aPrevContinuation
->GetType(),
3633 "setting a prev continuation with incorrect type!");
3634 NS_ASSERTION (!nsSplittableFrame::IsInPrevContinuationChain(aPrevContinuation
, this),
3635 "creating a loop in continuation chain!");
3636 mPrevContinuation
= aPrevContinuation
;
3637 RemoveStateBits(NS_FRAME_IS_FLUID_CONTINUATION
);
3640 virtual nsIFrame
* GetPrevInFlowVirtual() const { return GetPrevInFlow(); }
3641 nsIFrame
* GetPrevInFlow() const {
3642 return (GetStateBits() & NS_FRAME_IS_FLUID_CONTINUATION
) ? mPrevContinuation
: nsnull
;
3644 NS_IMETHOD
SetPrevInFlow(nsIFrame
* aPrevInFlow
) {
3645 NS_ASSERTION (!aPrevInFlow
|| GetType() == aPrevInFlow
->GetType(),
3646 "setting a prev in flow with incorrect type!");
3647 NS_ASSERTION (!nsSplittableFrame::IsInPrevContinuationChain(aPrevInFlow
, this),
3648 "creating a loop in continuation chain!");
3649 mPrevContinuation
= aPrevInFlow
;
3650 AddStateBits(NS_FRAME_IS_FLUID_CONTINUATION
);
3653 virtual nsIFrame
* GetFirstInFlow() const;
3654 virtual nsIFrame
* GetFirstContinuation() const;
3656 virtual void AddInlineMinWidth(nsIRenderingContext
*aRenderingContext
,
3657 InlineMinWidthData
*aData
);
3658 virtual void AddInlinePrefWidth(nsIRenderingContext
*aRenderingContext
,
3659 InlinePrefWidthData
*aData
);
3661 virtual nsresult
GetRenderedText(nsAString
* aString
= nsnull
,
3662 gfxSkipChars
* aSkipChars
= nsnull
,
3663 gfxSkipCharsIterator
* aSkipIter
= nsnull
,
3664 PRUint32 aSkippedStartOffset
= 0,
3665 PRUint32 aSkippedMaxLength
= PR_UINT32_MAX
)
3666 { return NS_ERROR_NOT_IMPLEMENTED
; } // Call on a primary text frame only
3669 nsContinuingTextFrame(nsStyleContext
* aContext
) : nsTextFrame(aContext
) {}
3670 nsIFrame
* mPrevContinuation
;
3674 nsContinuingTextFrame::Init(nsIContent
* aContent
,
3676 nsIFrame
* aPrevInFlow
)
3678 NS_ASSERTION(aPrevInFlow
, "Must be a continuation!");
3679 // NOTE: bypassing nsTextFrame::Init!!!
3680 nsresult rv
= nsFrame::Init(aContent
, aParent
, aPrevInFlow
);
3683 nsTextFrame
* nextContinuation
=
3684 static_cast<nsTextFrame
*>(aPrevInFlow
->GetNextContinuation());
3686 // Hook the frame into the flow
3687 SetPrevInFlow(aPrevInFlow
);
3688 aPrevInFlow
->SetNextInFlow(this);
3689 nsTextFrame
* prev
= static_cast<nsTextFrame
*>(aPrevInFlow
);
3690 mContentOffset
= prev
->GetContentOffset() + prev
->GetContentLengthHint();
3691 NS_ASSERTION(mContentOffset
< PRInt32(aContent
->GetText()->GetLength()),
3692 "Creating ContinuingTextFrame, but there is no more content");
3693 if (prev
->GetStyleContext() != GetStyleContext()) {
3694 // We're taking part of prev's text, and its style may be different
3695 // so clear its textrun which may no longer be valid (and don't set ours)
3696 prev
->ClearTextRun(nsnull
);
3698 mTextRun
= prev
->GetTextRun();
3701 if (aPrevInFlow
->GetStateBits() & NS_FRAME_IS_BIDI
) {
3702 FramePropertyTable
*propTable
= PresContext()->PropertyTable();
3703 // Get all the properties from the prev-in-flow first to take
3704 // advantage of the propTable's cache and simplify the assertion below
3705 void* embeddingLevel
= propTable
->Get(aPrevInFlow
, EmbeddingLevelProperty());
3706 void* baseLevel
= propTable
->Get(aPrevInFlow
, BaseLevelProperty());
3707 propTable
->Set(this, EmbeddingLevelProperty(), embeddingLevel
);
3708 propTable
->Set(this, BaseLevelProperty(), baseLevel
);
3710 if (nextContinuation
) {
3711 SetNextContinuation(nextContinuation
);
3712 nextContinuation
->SetPrevContinuation(this);
3713 // Adjust next-continuations' content offset as needed.
3714 while (nextContinuation
&&
3715 nextContinuation
->GetContentOffset() < mContentOffset
) {
3717 embeddingLevel
== propTable
->Get(nextContinuation
, EmbeddingLevelProperty()) &&
3718 baseLevel
== propTable
->Get(nextContinuation
, BaseLevelProperty()),
3719 "stealing text from different type of BIDI continuation");
3720 nextContinuation
->mContentOffset
= mContentOffset
;
3721 nextContinuation
= static_cast<nsTextFrame
*>(nextContinuation
->GetNextContinuation());
3724 mState
|= NS_FRAME_IS_BIDI
;
3725 } // prev frame is bidi
3732 nsContinuingTextFrame::DestroyFrom(nsIFrame
* aDestructRoot
)
3734 ClearFrameOffsetCache();
3736 // The text associated with this frame will become associated with our
3737 // prev-continuation. If that means the text has changed style, then
3738 // we need to wipe out the text run for the text.
3739 // Note that mPrevContinuation can be null if we're destroying the whole
3740 // frame chain from the start to the end.
3741 // If this frame is mentioned in the userData for a textrun (say
3742 // because there's a direction change at the start of this frame), then
3743 // we have to clear the textrun because we're going away and the
3744 // textrun had better not keep a dangling reference to us.
3745 if ((GetStateBits() & TEXT_IN_TEXTRUN_USER_DATA
) ||
3746 !mPrevContinuation
||
3747 mPrevContinuation
->GetStyleContext() != GetStyleContext()) {
3748 ClearTextRun(nsnull
);
3749 // Clear the previous continuation's text run also, so that it can rebuild
3750 // the text run to include our text.
3751 if (mPrevContinuation
) {
3752 (static_cast<nsTextFrame
*>(mPrevContinuation
))->ClearTextRun(nsnull
);
3755 nsSplittableFrame::RemoveFromFlow(this);
3756 // Let the base class destroy the frame
3757 nsFrame::DestroyFrom(aDestructRoot
);
3761 nsContinuingTextFrame::GetFirstInFlow() const
3763 // Can't cast to |nsContinuingTextFrame*| because the first one isn't.
3764 nsIFrame
*firstInFlow
,
3765 *previous
= const_cast<nsIFrame
*>
3766 (static_cast<const nsIFrame
*>(this));
3768 firstInFlow
= previous
;
3769 previous
= firstInFlow
->GetPrevInFlow();
3775 nsContinuingTextFrame::GetFirstContinuation() const
3777 // Can't cast to |nsContinuingTextFrame*| because the first one isn't.
3778 nsIFrame
*firstContinuation
,
3779 *previous
= const_cast<nsIFrame
*>
3780 (static_cast<const nsIFrame
*>(mPrevContinuation
));
3782 NS_ASSERTION(previous
, "How can an nsContinuingTextFrame be the first continuation?");
3785 firstContinuation
= previous
;
3786 previous
= firstContinuation
->GetPrevContinuation();
3788 return firstContinuation
;
3791 // XXX Do we want to do all the work for the first-in-flow or do the
3792 // work for each part? (Be careful of first-letter / first-line, though,
3793 // especially first-line!) Doing all the work on the first-in-flow has
3794 // the advantage of avoiding the potential for incremental reflow bugs,
3795 // but depends on our maintining the frame tree in reasonable ways even
3796 // for edge cases (block-within-inline splits, nextBidi, etc.)
3798 // XXX We really need to make :first-letter happen during frame
3801 // Needed for text frames in XUL.
3802 /* virtual */ nscoord
3803 nsTextFrame::GetMinWidth(nsIRenderingContext
*aRenderingContext
)
3805 return nsLayoutUtils::MinWidthFromInline(this, aRenderingContext
);
3808 // Needed for text frames in XUL.
3809 /* virtual */ nscoord
3810 nsTextFrame::GetPrefWidth(nsIRenderingContext
*aRenderingContext
)
3812 return nsLayoutUtils::PrefWidthFromInline(this, aRenderingContext
);
3816 nsContinuingTextFrame::AddInlineMinWidth(nsIRenderingContext
*aRenderingContext
,
3817 InlineMinWidthData
*aData
)
3819 // Do nothing, since the first-in-flow accounts for everything.
3824 nsContinuingTextFrame::AddInlinePrefWidth(nsIRenderingContext
*aRenderingContext
,
3825 InlinePrefWidthData
*aData
)
3827 // Do nothing, since the first-in-flow accounts for everything.
3832 DestroySelectionDetails(SelectionDetails
* aDetails
)
3835 SelectionDetails
* next
= aDetails
->mNext
;
3841 //----------------------------------------------------------------------
3843 #if defined(DEBUG_rbs) || defined(DEBUG_bzbarsky)
3845 VerifyNotDirty(nsFrameState state
)
3847 PRBool isZero
= state
& NS_FRAME_FIRST_REFLOW
;
3848 PRBool isDirty
= state
& NS_FRAME_IS_DIRTY
;
3849 if (!isZero
&& isDirty
)
3850 NS_WARNING("internal offsets may be out-of-sync");
3852 #define DEBUG_VERIFY_NOT_DIRTY(state) \
3853 VerifyNotDirty(state)
3855 #define DEBUG_VERIFY_NOT_DIRTY(state)
3859 NS_NewTextFrame(nsIPresShell
* aPresShell
, nsStyleContext
* aContext
)
3861 return new (aPresShell
) nsTextFrame(aContext
);
3864 NS_IMPL_FRAMEARENA_HELPERS(nsTextFrame
)
3867 NS_NewContinuingTextFrame(nsIPresShell
* aPresShell
, nsStyleContext
* aContext
)
3869 return new (aPresShell
) nsContinuingTextFrame(aContext
);
3872 NS_IMPL_FRAMEARENA_HELPERS(nsContinuingTextFrame
)
3874 nsTextFrame::~nsTextFrame()
3876 if (0 != (mState
& TEXT_BLINK_ON
))
3878 nsBlinkTimer::RemoveBlinkFrame(this);
3883 nsTextFrame::GetCursor(const nsPoint
& aPoint
,
3884 nsIFrame::Cursor
& aCursor
)
3886 FillCursorInformationFromStyle(GetStyleUserInterface(), aCursor
);
3887 if (NS_STYLE_CURSOR_AUTO
== aCursor
.mCursor
) {
3888 aCursor
.mCursor
= NS_STYLE_CURSOR_TEXT
;
3890 // If tabindex >= 0, use default cursor to indicate it's not selectable
3891 nsIFrame
*ancestorFrame
= this;
3892 while ((ancestorFrame
= ancestorFrame
->GetParent()) != nsnull
) {
3893 nsIContent
*ancestorContent
= ancestorFrame
->GetContent();
3894 if (ancestorContent
&& ancestorContent
->HasAttr(kNameSpaceID_None
, nsGkAtoms::tabindex
)) {
3895 nsAutoString tabIndexStr
;
3896 ancestorContent
->GetAttr(kNameSpaceID_None
, nsGkAtoms::tabindex
, tabIndexStr
);
3897 if (!tabIndexStr
.IsEmpty()) {
3898 PRInt32 rv
, tabIndexVal
= tabIndexStr
.ToInteger(&rv
);
3899 if (NS_SUCCEEDED(rv
) && tabIndexVal
>= 0) {
3900 aCursor
.mCursor
= NS_STYLE_CURSOR_DEFAULT
;
3912 nsTextFrame::GetLastInFlow() const
3914 nsTextFrame
* lastInFlow
= const_cast<nsTextFrame
*>(this);
3915 while (lastInFlow
->GetNextInFlow()) {
3916 lastInFlow
= static_cast<nsTextFrame
*>(lastInFlow
->GetNextInFlow());
3918 NS_POSTCONDITION(lastInFlow
, "illegal state in flow chain.");
3922 nsTextFrame::GetLastContinuation() const
3924 nsTextFrame
* lastInFlow
= const_cast<nsTextFrame
*>(this);
3925 while (lastInFlow
->mNextContinuation
) {
3926 lastInFlow
= static_cast<nsTextFrame
*>(lastInFlow
->mNextContinuation
);
3928 NS_POSTCONDITION(lastInFlow
, "illegal state in continuation chain.");
3933 nsTextFrame::ClearTextRun(nsTextFrame
* aStartContinuation
)
3935 // save textrun because ClearAllTextRunReferences may clear ours
3936 gfxTextRun
* textRun
= mTextRun
;
3941 UnhookTextRunFromFrames(textRun
, aStartContinuation
);
3942 // see comments in BuildTextRunForFrames...
3943 // if (textRun->GetFlags() & gfxFontGroup::TEXT_IS_PERSISTENT) {
3944 // NS_ERROR("Shouldn't reach here for now...");
3945 // // the textrun's text may be referencing a DOM node that has changed,
3946 // // so we'd better kill this textrun now.
3947 // if (textRun->GetExpirationState()->IsTracked()) {
3948 // gTextRuns->RemoveFromCache(textRun);
3954 if (!(textRun
->GetFlags() & gfxTextRunWordCache::TEXT_IN_CACHE
) &&
3955 !textRun
->GetUserData()) {
3956 // Remove it now because it's not doing anything useful
3957 gTextRuns
->RemoveFromCache(textRun
);
3963 nsTextFrame::CharacterDataChanged(CharacterDataChangeInfo
* aInfo
)
3965 mContent
->DeleteProperty(nsGkAtoms::newline
);
3967 // Find the first frame whose text has changed. Frames that are entirely
3968 // before the text change are completely unaffected.
3970 nsTextFrame
* textFrame
= this;
3972 next
= static_cast<nsTextFrame
*>(textFrame
->GetNextContinuation());
3973 if (!next
|| next
->GetContentOffset() > PRInt32(aInfo
->mChangeStart
))
3978 PRInt32 endOfChangedText
= aInfo
->mChangeStart
+ aInfo
->mReplaceLength
;
3979 nsTextFrame
* lastDirtiedFrame
= nsnull
;
3981 nsIPresShell
* shell
= PresContext()->GetPresShell();
3983 // textFrame contained deleted text (or the insertion point,
3984 // if this was a pure insertion).
3985 textFrame
->mState
&= ~TEXT_WHITESPACE_FLAGS
;
3986 textFrame
->ClearTextRun(nsnull
);
3987 if (!lastDirtiedFrame
||
3988 lastDirtiedFrame
->GetParent() != textFrame
->GetParent()) {
3989 // Ask the parent frame to reflow me.
3990 shell
->FrameNeedsReflow(textFrame
, nsIPresShell::eStyleChange
,
3992 lastDirtiedFrame
= textFrame
;
3994 // if the parent is a block, we're cheating here because we should
3995 // be marking our line dirty, but we're not. nsTextFrame::SetLength
3996 // will do that when it gets called during reflow.
3997 textFrame
->AddStateBits(NS_FRAME_IS_DIRTY
);
4000 // Below, frames that start after the deleted text will be adjusted so that
4001 // their offsets move with the trailing unchanged text. If this change
4002 // deletes more text than it inserts, those frame offsets will decrease.
4003 // We need to maintain the invariant that mContentOffset is non-decreasing
4004 // along the continuation chain. So we need to ensure that frames that
4005 // started in the deleted text are all still starting before the
4007 if (textFrame
->mContentOffset
> endOfChangedText
) {
4008 textFrame
->mContentOffset
= endOfChangedText
;
4011 textFrame
= static_cast<nsTextFrame
*>(textFrame
->GetNextContinuation());
4012 } while (textFrame
&& textFrame
->GetContentOffset() < PRInt32(aInfo
->mChangeEnd
));
4014 // This is how much the length of the string changed by --- i.e.,
4015 // how much the trailing unchanged text moved.
4016 PRInt32 sizeChange
=
4017 aInfo
->mChangeStart
+ aInfo
->mReplaceLength
- aInfo
->mChangeEnd
;
4020 // Fix the offsets of the text frames that start in the trailing
4023 textFrame
->mContentOffset
+= sizeChange
;
4024 // XXX we could rescue some text runs by adjusting their user data
4025 // to reflect the change in DOM offsets
4026 textFrame
->ClearTextRun(nsnull
);
4027 textFrame
= static_cast<nsTextFrame
*>(textFrame
->GetNextContinuation());
4035 nsTextFrame::DidSetStyleContext(nsStyleContext
* aOldStyleContext
)
4037 nsFrame::DidSetStyleContext(aOldStyleContext
);
4038 ClearTextRun(nsnull
);
4041 class nsDisplayText
: public nsDisplayItem
{
4043 nsDisplayText(nsDisplayListBuilder
* aBuilder
, nsTextFrame
* aFrame
) :
4044 nsDisplayItem(aBuilder
, aFrame
),
4045 mDisableSubpixelAA(PR_FALSE
) {
4046 MOZ_COUNT_CTOR(nsDisplayText
);
4048 #ifdef NS_BUILD_REFCNT_LOGGING
4049 virtual ~nsDisplayText() {
4050 MOZ_COUNT_DTOR(nsDisplayText
);
4054 virtual nsRect
GetBounds(nsDisplayListBuilder
* aBuilder
) {
4055 return mFrame
->GetVisualOverflowRect() + ToReferenceFrame();
4057 virtual void HitTest(nsDisplayListBuilder
* aBuilder
, const nsRect
& aRect
,
4058 HitTestState
* aState
, nsTArray
<nsIFrame
*> *aOutFrames
) {
4059 if (nsRect(ToReferenceFrame(), mFrame
->GetSize()).Intersects(aRect
)) {
4060 aOutFrames
->AppendElement(mFrame
);
4063 virtual void Paint(nsDisplayListBuilder
* aBuilder
,
4064 nsIRenderingContext
* aCtx
);
4065 NS_DISPLAY_DECL_NAME("Text", TYPE_TEXT
)
4067 virtual nsRect
GetComponentAlphaBounds(nsDisplayListBuilder
* aBuilder
)
4069 return GetBounds(aBuilder
);
4072 virtual void DisableComponentAlpha() { mDisableSubpixelAA
= PR_TRUE
; }
4074 PRPackedBool mDisableSubpixelAA
;
4078 nsDisplayText::Paint(nsDisplayListBuilder
* aBuilder
,
4079 nsIRenderingContext
* aCtx
) {
4080 // Add 1 pixel of dirty area around mVisibleRect to allow us to paint
4081 // antialiased pixels beyond the measured text extents.
4082 // This is temporary until we do this in the actual calculation of text extents.
4083 nsRect extraVisible
= mVisibleRect
;
4084 nscoord appUnitsPerDevPixel
= mFrame
->PresContext()->AppUnitsPerDevPixel();
4085 extraVisible
.Inflate(appUnitsPerDevPixel
, appUnitsPerDevPixel
);
4086 nsTextFrame
* f
= static_cast<nsTextFrame
*>(mFrame
);
4088 gfxContextAutoDisableSubpixelAntialiasing
disable(aCtx
->ThebesContext(),
4089 mDisableSubpixelAA
);
4090 f
->PaintText(aCtx
, ToReferenceFrame(), extraVisible
);
4094 nsTextFrame::BuildDisplayList(nsDisplayListBuilder
* aBuilder
,
4095 const nsRect
& aDirtyRect
,
4096 const nsDisplayListSet
& aLists
)
4098 if (!IsVisibleForPainting(aBuilder
))
4101 DO_GLOBAL_REFLOW_COUNT_DSP("nsTextFrame");
4103 if ((0 != (mState
& TEXT_BLINK_ON
)) && nsBlinkTimer::GetBlinkIsOff() &&
4104 PresContext()->IsDynamic() && !aBuilder
->IsForEventDelivery())
4107 return aLists
.Content()->AppendNewToTop(
4108 new (aBuilder
) nsDisplayText(aBuilder
, this));
4112 GetGeneratedContentOwner(nsIFrame
* aFrame
, PRBool
* aIsBefore
)
4114 *aIsBefore
= PR_FALSE
;
4115 while (aFrame
&& (aFrame
->GetStateBits() & NS_FRAME_GENERATED_CONTENT
)) {
4116 if (aFrame
->GetStyleContext()->GetPseudo() == nsCSSPseudoElements::before
) {
4117 *aIsBefore
= PR_TRUE
;
4119 aFrame
= aFrame
->GetParent();
4125 nsTextFrame::GetSelectionDetails()
4127 const nsFrameSelection
* frameSelection
= GetConstFrameSelection();
4128 if (!(GetStateBits() & NS_FRAME_GENERATED_CONTENT
)) {
4129 SelectionDetails
* details
=
4130 frameSelection
->LookUpSelection(mContent
, GetContentOffset(),
4131 GetContentLength(), PR_FALSE
);
4132 SelectionDetails
* sd
;
4133 for (sd
= details
; sd
; sd
= sd
->mNext
) {
4134 sd
->mStart
+= mContentOffset
;
4135 sd
->mEnd
+= mContentOffset
;
4140 // Check if the beginning or end of the element is selected, depending on
4141 // whether we're :before content or :after content.
4143 nsIFrame
* owner
= GetGeneratedContentOwner(this, &isBefore
);
4144 if (!owner
|| !owner
->GetContent())
4147 SelectionDetails
* details
=
4148 frameSelection
->LookUpSelection(owner
->GetContent(),
4149 isBefore
? 0 : owner
->GetContent()->GetChildCount(), 0, PR_FALSE
);
4150 SelectionDetails
* sd
;
4151 for (sd
= details
; sd
; sd
= sd
->mNext
) {
4152 // The entire text is selected!
4153 sd
->mStart
= GetContentOffset();
4154 sd
->mEnd
= GetContentEnd();
4160 FillClippedRect(gfxContext
* aCtx
, nsPresContext
* aPresContext
,
4161 nscolor aColor
, const gfxRect
& aDirtyRect
, const gfxRect
& aRect
)
4163 gfxRect r
= aRect
.Intersect(aDirtyRect
);
4164 // For now, we need to put this in pixel coordinates
4165 PRInt32 app
= aPresContext
->AppUnitsPerDevPixel();
4168 aCtx
->Rectangle(gfxRect(r
.X() / app
, r
.Y() / app
,
4169 r
.Width() / app
, r
.Height() / app
), PR_TRUE
);
4170 aCtx
->SetColor(gfxRGBA(aColor
));
4174 nsTextFrame::TextDecorations
4175 nsTextFrame::GetTextDecorations(nsPresContext
* aPresContext
)
4177 TextDecorations decorations
;
4179 // Quirks mode text decoration are rendered by children; see bug 1777
4180 // In non-quirks mode, nsHTMLContainer::Paint and nsBlockFrame::Paint
4181 // does the painting of text decorations.
4182 // FIXME Bug 403524: We'd like to unify standards-mode and quirks-mode
4183 // text-decoration drawing, using what's currently the quirks mode
4184 // codepath. But for now this code is only used for quirks mode.
4185 const nsCompatibility compatMode
= aPresContext
->CompatibilityMode();
4186 if (compatMode
!= eCompatibility_NavQuirks
)
4189 PRBool useOverride
= PR_FALSE
;
4190 nscolor overrideColor
;
4192 // A mask of all possible decorations.
4193 // FIXME: Per spec, we still need to draw all relevant decorations
4194 // from ancestors, not just the nearest one from each.
4195 PRUint8 decorMask
= NS_STYLE_TEXT_DECORATION_UNDERLINE
|
4196 NS_STYLE_TEXT_DECORATION_OVERLINE
|
4197 NS_STYLE_TEXT_DECORATION_LINE_THROUGH
;
4199 PRBool isChild
; // ignored
4200 for (nsIFrame
* f
= this; decorMask
&& f
;
4201 NS_SUCCEEDED(f
->GetParentStyleContextFrame(aPresContext
, &f
, &isChild
))
4203 nsStyleContext
* context
= f
->GetStyleContext();
4204 if (!context
->HasTextDecorations()) {
4207 const nsStyleTextReset
* styleText
= context
->GetStyleTextReset();
4209 (NS_STYLE_TEXT_DECORATION_OVERRIDE_ALL
& styleText
->mTextDecoration
)) {
4210 // This handles the <a href="blah.html"><font color="green">La
4211 // la la</font></a> case. The link underline should be green.
4212 useOverride
= PR_TRUE
;
4213 overrideColor
= context
->GetVisitedDependentColor(eCSSProperty_color
);
4216 // FIXME: see above (remove this check)
4217 PRUint8 useDecorations
= decorMask
& styleText
->mTextDecoration
;
4218 if (useDecorations
) {// a decoration defined here
4219 nscolor color
= context
->GetVisitedDependentColor(eCSSProperty_color
);
4221 // FIXME: We also need to record the thickness and position
4222 // metrics appropriate to this element (at least in standards
4223 // mode). This will require adjusting the visual overflow region
4224 // of this frame and maybe its ancestors. The positions should
4225 // probably be relative to the line's baseline (when text
4226 // decorations are specified on inlines we should look for their
4227 // containing line; otherwise use the element's font); when
4228 // drawing it should always be relative to the line baseline.
4229 // This way we move the decorations for relative positioning.
4230 if (NS_STYLE_TEXT_DECORATION_UNDERLINE
& useDecorations
) {
4231 decorations
.mUnderColor
= useOverride
? overrideColor
: color
;
4232 decorMask
&= ~NS_STYLE_TEXT_DECORATION_UNDERLINE
;
4233 decorations
.mDecorations
|= NS_STYLE_TEXT_DECORATION_UNDERLINE
;
4235 if (NS_STYLE_TEXT_DECORATION_OVERLINE
& useDecorations
) {
4236 decorations
.mOverColor
= useOverride
? overrideColor
: color
;
4237 decorMask
&= ~NS_STYLE_TEXT_DECORATION_OVERLINE
;
4238 decorations
.mDecorations
|= NS_STYLE_TEXT_DECORATION_OVERLINE
;
4240 if (NS_STYLE_TEXT_DECORATION_LINE_THROUGH
& useDecorations
) {
4241 decorations
.mStrikeColor
= useOverride
? overrideColor
: color
;
4242 decorMask
&= ~NS_STYLE_TEXT_DECORATION_LINE_THROUGH
;
4243 decorations
.mDecorations
|= NS_STYLE_TEXT_DECORATION_LINE_THROUGH
;
4247 // In all modes, if we're on an inline-block or inline-table (or
4248 // inline-stack, inline-box, inline-grid), we're done.
4249 const nsStyleDisplay
*disp
= context
->GetStyleDisplay();
4250 if (disp
->mDisplay
!= NS_STYLE_DISPLAY_INLINE
&&
4251 disp
->IsInlineOutside()) {
4255 if (compatMode
== eCompatibility_NavQuirks
) {
4256 // In quirks mode, if we're on an HTML table element, we're done.
4257 if (f
->GetContent()->IsHTML(nsGkAtoms::table
)) {
4261 // In standards/almost-standards mode, if we're on an
4262 // absolutely-positioned element or a floating element, we're done.
4263 if (disp
->IsFloating() || disp
->IsAbsolutelyPositioned()) {
4273 nsTextFrame::UnionTextDecorationOverflow(nsPresContext
* aPresContext
,
4274 PropertyProvider
& aProvider
,
4275 nsRect
* aVisualOverflowRect
)
4277 // Text-shadow overflows
4279 nsLayoutUtils::GetTextShadowRectsUnion(*aVisualOverflowRect
, this);
4280 aVisualOverflowRect
->UnionRect(*aVisualOverflowRect
, shadowRect
);
4282 if (IsFloatingFirstLetterChild()) {
4283 // The underline/overline drawable area must be contained in the overflow
4284 // rect when this is in floating first letter frame at *both* modes.
4285 nscoord fontAscent
, fontHeight
;
4286 nsIFontMetrics
* fm
= aProvider
.GetFontMetrics();
4287 fm
->GetMaxAscent(fontAscent
);
4288 fm
->GetMaxHeight(fontHeight
);
4289 nsRect
fontRect(0, mAscent
- fontAscent
, GetSize().width
, fontHeight
);
4290 aVisualOverflowRect
->UnionRect(*aVisualOverflowRect
, fontRect
);
4293 // When this frame is not selected, the text-decoration area must be in
4295 nsRect decorationRect
;
4296 if (!(GetStateBits() & NS_FRAME_SELECTED_CONTENT
) ||
4297 !CombineSelectionUnderlineRect(aPresContext
, *aVisualOverflowRect
))
4299 AddStateBits(TEXT_SELECTION_UNDERLINE_OVERFLOWED
);
4303 nsTextFrame::PaintTextDecorations(gfxContext
* aCtx
, const gfxRect
& aDirtyRect
,
4304 const gfxPoint
& aFramePt
,
4305 const gfxPoint
& aTextBaselinePt
,
4306 nsTextPaintStyle
& aTextPaintStyle
,
4307 PropertyProvider
& aProvider
,
4308 const nscolor
* aOverrideColor
)
4310 TextDecorations decorations
=
4311 GetTextDecorations(aTextPaintStyle
.PresContext());
4312 if (!decorations
.HasDecorationlines())
4315 // Hide text decorations if we're currently hiding @font-face fallback text
4316 if (aProvider
.GetFontGroup()->ShouldSkipDrawing())
4319 gfxFont
* firstFont
= aProvider
.GetFontGroup()->GetFontAt(0);
4322 const gfxFont::Metrics
& fontMetrics
= firstFont
->GetMetrics();
4323 gfxFloat app
= aTextPaintStyle
.PresContext()->AppUnitsPerDevPixel();
4325 // XXX aFramePt is in AppUnits, shouldn't it be nsFloatPoint?
4326 gfxPoint
pt(aFramePt
.x
/ app
, (aTextBaselinePt
.y
- mAscent
) / app
);
4327 gfxSize
size(GetRect().width
/ app
, 0);
4328 gfxFloat ascent
= gfxFloat(mAscent
) / app
;
4331 if (decorations
.HasOverline()) {
4332 lineColor
= aOverrideColor
? *aOverrideColor
: decorations
.mOverColor
;
4333 size
.height
= fontMetrics
.underlineSize
;
4334 nsCSSRendering::PaintDecorationLine(
4335 aCtx
, lineColor
, pt
, size
, ascent
, fontMetrics
.maxAscent
,
4336 NS_STYLE_TEXT_DECORATION_OVERLINE
,
4337 nsCSSRendering::DECORATION_STYLE_SOLID
);
4339 if (decorations
.HasUnderline()) {
4340 lineColor
= aOverrideColor
? *aOverrideColor
: decorations
.mUnderColor
;
4341 size
.height
= fontMetrics
.underlineSize
;
4342 gfxFloat offset
= aProvider
.GetFontGroup()->GetUnderlineOffset();
4343 nsCSSRendering::PaintDecorationLine(
4344 aCtx
, lineColor
, pt
, size
, ascent
, offset
,
4345 NS_STYLE_TEXT_DECORATION_UNDERLINE
,
4346 nsCSSRendering::DECORATION_STYLE_SOLID
);
4348 if (decorations
.HasStrikeout()) {
4349 lineColor
= aOverrideColor
? *aOverrideColor
: decorations
.mStrikeColor
;
4350 size
.height
= fontMetrics
.strikeoutSize
;
4351 gfxFloat offset
= fontMetrics
.strikeoutOffset
;
4352 nsCSSRendering::PaintDecorationLine(
4353 aCtx
, lineColor
, pt
, size
, ascent
, offset
,
4354 NS_STYLE_TEXT_DECORATION_LINE_THROUGH
,
4355 nsCSSRendering::DECORATION_STYLE_SOLID
);
4360 ComputeDescentLimitForSelectionUnderline(nsPresContext
* aPresContext
,
4361 nsTextFrame
* aFrame
,
4362 const gfxFont::Metrics
& aFontMetrics
)
4364 gfxFloat app
= aPresContext
->AppUnitsPerDevPixel();
4365 nscoord lineHeightApp
=
4366 nsHTMLReflowState::CalcLineHeight(aFrame
->GetStyleContext(), NS_AUTOHEIGHT
);
4367 gfxFloat lineHeight
= gfxFloat(lineHeightApp
) / app
;
4368 if (lineHeight
<= aFontMetrics
.maxHeight
) {
4369 return aFontMetrics
.maxDescent
;
4371 return aFontMetrics
.maxDescent
+ (lineHeight
- aFontMetrics
.maxHeight
) / 2;
4375 // Make sure this stays in sync with DrawSelectionDecorations below
4376 static const SelectionType SelectionTypesWithDecorations
=
4377 nsISelectionController::SELECTION_SPELLCHECK
|
4378 nsISelectionController::SELECTION_IME_RAWINPUT
|
4379 nsISelectionController::SELECTION_IME_SELECTEDRAWTEXT
|
4380 nsISelectionController::SELECTION_IME_CONVERTEDTEXT
|
4381 nsISelectionController::SELECTION_IME_SELECTEDCONVERTEDTEXT
;
4384 GetTextDecorationStyle(const nsTextRangeStyle
&aRangeStyle
)
4386 NS_PRECONDITION(aRangeStyle
.IsLineStyleDefined(),
4387 "aRangeStyle.mLineStyle have to be defined");
4388 switch (aRangeStyle
.mLineStyle
) {
4389 case nsTextRangeStyle::LINESTYLE_NONE
:
4390 return nsCSSRendering::DECORATION_STYLE_NONE
;
4391 case nsTextRangeStyle::LINESTYLE_SOLID
:
4392 return nsCSSRendering::DECORATION_STYLE_SOLID
;
4393 case nsTextRangeStyle::LINESTYLE_DOTTED
:
4394 return nsCSSRendering::DECORATION_STYLE_DOTTED
;
4395 case nsTextRangeStyle::LINESTYLE_DASHED
:
4396 return nsCSSRendering::DECORATION_STYLE_DASHED
;
4397 case nsTextRangeStyle::LINESTYLE_DOUBLE
:
4398 return nsCSSRendering::DECORATION_STYLE_DOUBLE
;
4399 case nsTextRangeStyle::LINESTYLE_WAVY
:
4400 return nsCSSRendering::DECORATION_STYLE_WAVY
;
4402 NS_WARNING("Requested underline style is not valid");
4403 return nsCSSRendering::DECORATION_STYLE_SOLID
;
4408 ComputeSelectionUnderlineHeight(nsPresContext
* aPresContext
,
4409 const gfxFont::Metrics
& aFontMetrics
,
4410 SelectionType aSelectionType
)
4412 switch (aSelectionType
) {
4413 case nsISelectionController::SELECTION_IME_RAWINPUT
:
4414 case nsISelectionController::SELECTION_IME_SELECTEDRAWTEXT
:
4415 case nsISelectionController::SELECTION_IME_CONVERTEDTEXT
:
4416 case nsISelectionController::SELECTION_IME_SELECTEDCONVERTEDTEXT
:
4417 return aFontMetrics
.underlineSize
;
4418 case nsISelectionController::SELECTION_SPELLCHECK
: {
4419 // The thickness of the spellchecker underline shouldn't honor the font
4420 // metrics. It should be constant pixels value which is decided from the
4421 // default font size. Note that if the actual font size is smaller than
4422 // the default font size, we should use the actual font size because the
4423 // computed value from the default font size can be too thick for the
4424 // current font size.
4425 PRInt32 defaultFontSize
=
4426 aPresContext
->AppUnitsToDevPixels(nsStyleFont(aPresContext
).mFont
.size
);
4427 gfxFloat fontSize
= NS_MIN(gfxFloat(defaultFontSize
),
4428 aFontMetrics
.emHeight
);
4429 fontSize
= NS_MAX(fontSize
, 1.0);
4430 return NS_ceil(fontSize
/ 20);
4433 NS_WARNING("Requested underline style is not valid");
4434 return aFontMetrics
.underlineSize
;
4439 * This, plus SelectionTypesWithDecorations, encapsulates all knowledge about
4440 * drawing text decoration for selections.
4442 static void DrawSelectionDecorations(gfxContext
* aContext
, SelectionType aType
,
4443 nsTextFrame
* aFrame
,
4444 nsTextPaintStyle
& aTextPaintStyle
,
4445 const nsTextRangeStyle
&aRangeStyle
,
4446 const gfxPoint
& aPt
, gfxFloat aWidth
,
4447 gfxFloat aAscent
, const gfxFont::Metrics
& aFontMetrics
)
4450 gfxSize
size(aWidth
,
4451 ComputeSelectionUnderlineHeight(aTextPaintStyle
.PresContext(),
4452 aFontMetrics
, aType
));
4453 gfxFloat descentLimit
=
4454 ComputeDescentLimitForSelectionUnderline(aTextPaintStyle
.PresContext(),
4455 aFrame
, aFontMetrics
);
4461 nsTextPaintStyle::GetUnderlineStyleIndexForSelectionType(aType
);
4462 PRBool weDefineSelectionUnderline
=
4463 aTextPaintStyle
.GetSelectionUnderlineForPaint(index
, &color
,
4464 &relativeSize
, &style
);
4467 case nsISelectionController::SELECTION_IME_RAWINPUT
:
4468 case nsISelectionController::SELECTION_IME_SELECTEDRAWTEXT
:
4469 case nsISelectionController::SELECTION_IME_CONVERTEDTEXT
:
4470 case nsISelectionController::SELECTION_IME_SELECTEDCONVERTEDTEXT
: {
4471 // IME decoration lines should not be drawn on the both ends, i.e., we
4472 // need to cut both edges of the decoration lines. Because same style
4473 // IME selections can adjoin, but the users need to be able to know
4474 // where are the boundaries of the selections.
4478 // IME selection #1 IME selection #2 IME selection #3
4480 // | XXXXXXXXXXXXXXXXXXX | XXXXXXXXXXXXXXXXXXXX | XXXXXXXXXXXXXXXXXXX
4481 // +---------------------+----------------------+--------------------
4486 if (aRangeStyle
.IsDefined()) {
4487 // If IME defines the style, that should override our definition.
4488 if (aRangeStyle
.IsLineStyleDefined()) {
4489 if (aRangeStyle
.mLineStyle
== nsTextRangeStyle::LINESTYLE_NONE
) {
4492 style
= GetTextDecorationStyle(aRangeStyle
);
4493 relativeSize
= aRangeStyle
.mIsBoldLine
? 2.0f
: 1.0f
;
4494 } else if (!weDefineSelectionUnderline
) {
4495 // There is no underline style definition.
4498 if (aRangeStyle
.IsUnderlineColorDefined()) {
4499 color
= aRangeStyle
.mUnderlineColor
;
4500 } else if (aRangeStyle
.IsForegroundColorDefined()) {
4501 color
= aRangeStyle
.mForegroundColor
;
4503 NS_ASSERTION(!aRangeStyle
.IsBackgroundColorDefined(),
4504 "Only the background color is defined");
4505 color
= aTextPaintStyle
.GetTextColor();
4507 } else if (!weDefineSelectionUnderline
) {
4508 // IME doesn't specify the selection style and we don't define selection
4514 case nsISelectionController::SELECTION_SPELLCHECK
:
4515 if (!weDefineSelectionUnderline
)
4519 NS_WARNING("Requested selection decorations when there aren't any");
4522 size
.height
*= relativeSize
;
4523 nsCSSRendering::PaintDecorationLine(
4524 aContext
, color
, pt
, size
, aAscent
, aFontMetrics
.underlineOffset
,
4525 NS_STYLE_TEXT_DECORATION_UNDERLINE
, style
, descentLimit
);
4529 * This function encapsulates all knowledge of how selections affect foreground
4530 * and background colors.
4531 * @return true if the selection affects colors, false otherwise
4532 * @param aForeground the foreground color to use
4533 * @param aBackground the background color to use, or RGBA(0,0,0,0) if no
4534 * background should be painted
4536 static PRBool
GetSelectionTextColors(SelectionType aType
,
4537 nsTextPaintStyle
& aTextPaintStyle
,
4538 const nsTextRangeStyle
&aRangeStyle
,
4539 nscolor
* aForeground
, nscolor
* aBackground
)
4542 case nsISelectionController::SELECTION_NORMAL
:
4543 return aTextPaintStyle
.GetSelectionColors(aForeground
, aBackground
);
4544 case nsISelectionController::SELECTION_FIND
:
4545 aTextPaintStyle
.GetHighlightColors(aForeground
, aBackground
);
4547 case nsISelectionController::SELECTION_IME_RAWINPUT
:
4548 case nsISelectionController::SELECTION_IME_SELECTEDRAWTEXT
:
4549 case nsISelectionController::SELECTION_IME_CONVERTEDTEXT
:
4550 case nsISelectionController::SELECTION_IME_SELECTEDCONVERTEDTEXT
:
4551 if (aRangeStyle
.IsDefined()) {
4552 *aForeground
= aTextPaintStyle
.GetTextColor();
4553 *aBackground
= NS_RGBA(0,0,0,0);
4554 if (!aRangeStyle
.IsForegroundColorDefined() &&
4555 !aRangeStyle
.IsBackgroundColorDefined()) {
4558 if (aRangeStyle
.IsForegroundColorDefined()) {
4559 *aForeground
= aRangeStyle
.mForegroundColor
;
4561 if (aRangeStyle
.IsBackgroundColorDefined()) {
4562 *aBackground
= aRangeStyle
.mBackgroundColor
;
4566 aTextPaintStyle
.GetIMESelectionColors(
4567 nsTextPaintStyle::GetUnderlineStyleIndexForSelectionType(aType
),
4568 aForeground
, aBackground
);
4571 *aForeground
= aTextPaintStyle
.GetTextColor();
4572 *aBackground
= NS_RGBA(0,0,0,0);
4578 * This class lets us iterate over chunks of text in a uniform selection state,
4579 * observing cluster boundaries, in content order, maintaining the current
4580 * x-offset as we go, and telling whether the text chunk has a hyphen after
4581 * it or not. The caller is responsible for actually computing the advance
4582 * width of each chunk.
4584 class SelectionIterator
{
4587 * aStart and aLength are in the original string. aSelectionDetails is
4588 * according to the original string.
4590 SelectionIterator(SelectionDetails
** aSelectionDetails
,
4591 PRInt32 aStart
, PRInt32 aLength
,
4592 PropertyProvider
& aProvider
, gfxTextRun
* aTextRun
);
4595 * Returns the next segment of uniformly selected (or not) text.
4596 * @param aXOffset the offset from the origin of the frame to the start
4597 * of the text (the left baseline origin for LTR, the right baseline origin
4599 * @param aOffset the transformed string offset of the text for this segment
4600 * @param aLength the transformed string length of the text for this segment
4601 * @param aHyphenWidth if a hyphen is to be rendered after the text, the
4602 * width of the hyphen, otherwise zero
4603 * @param aType the selection type for this segment
4604 * @param aStyle the selection style for this segment
4605 * @return false if there are no more segments
4607 PRBool
GetNextSegment(gfxFloat
* aXOffset
, PRUint32
* aOffset
, PRUint32
* aLength
,
4608 gfxFloat
* aHyphenWidth
, SelectionType
* aType
,
4609 nsTextRangeStyle
* aStyle
);
4610 void UpdateWithAdvance(gfxFloat aAdvance
) {
4611 mXOffset
+= aAdvance
*mTextRun
->GetDirection();
4615 SelectionDetails
** mSelectionDetails
;
4616 PropertyProvider
& mProvider
;
4617 gfxTextRun
* mTextRun
;
4618 gfxSkipCharsIterator mIterator
;
4619 PRInt32 mOriginalStart
;
4620 PRInt32 mOriginalEnd
;
4624 SelectionIterator::SelectionIterator(SelectionDetails
** aSelectionDetails
,
4625 PRInt32 aStart
, PRInt32 aLength
, PropertyProvider
& aProvider
,
4626 gfxTextRun
* aTextRun
)
4627 : mSelectionDetails(aSelectionDetails
), mProvider(aProvider
),
4628 mTextRun(aTextRun
), mIterator(aProvider
.GetStart()),
4629 mOriginalStart(aStart
), mOriginalEnd(aStart
+ aLength
),
4630 mXOffset(mTextRun
->IsRightToLeft() ? aProvider
.GetFrame()->GetSize().width
: 0)
4632 mIterator
.SetOriginalOffset(aStart
);
4635 PRBool
SelectionIterator::GetNextSegment(gfxFloat
* aXOffset
,
4636 PRUint32
* aOffset
, PRUint32
* aLength
, gfxFloat
* aHyphenWidth
,
4637 SelectionType
* aType
, nsTextRangeStyle
* aStyle
)
4639 if (mIterator
.GetOriginalOffset() >= mOriginalEnd
)
4642 // save offset into transformed string now
4643 PRUint32 runOffset
= mIterator
.GetSkippedOffset();
4645 PRInt32 index
= mIterator
.GetOriginalOffset() - mOriginalStart
;
4646 SelectionDetails
* sdptr
= mSelectionDetails
[index
];
4647 SelectionType type
=
4648 sdptr
? sdptr
->mType
: nsISelectionController::SELECTION_NONE
;
4649 nsTextRangeStyle style
;
4651 style
= sdptr
->mTextRangeStyle
;
4653 for (++index
; mOriginalStart
+ index
< mOriginalEnd
; ++index
) {
4654 if (sdptr
!= mSelectionDetails
[index
])
4657 mIterator
.SetOriginalOffset(index
+ mOriginalStart
);
4659 // Advance to the next cluster boundary
4660 while (mIterator
.GetOriginalOffset() < mOriginalEnd
&&
4661 !mIterator
.IsOriginalCharSkipped() &&
4662 !mTextRun
->IsClusterStart(mIterator
.GetSkippedOffset())) {
4663 mIterator
.AdvanceOriginal(1);
4666 PRBool haveHyphenBreak
=
4667 (mProvider
.GetFrame()->GetStateBits() & TEXT_HYPHEN_BREAK
) != 0;
4668 *aOffset
= runOffset
;
4669 *aLength
= mIterator
.GetSkippedOffset() - runOffset
;
4670 *aXOffset
= mXOffset
;
4672 if (mIterator
.GetOriginalOffset() == mOriginalEnd
&& haveHyphenBreak
) {
4673 *aHyphenWidth
= mProvider
.GetHyphenWidth();
4681 AddHyphenToMetrics(nsTextFrame
* aTextFrame
, gfxTextRun
* aBaseTextRun
,
4682 gfxTextRun::Metrics
* aMetrics
,
4683 gfxFont::BoundingBoxType aBoundingBoxType
,
4684 gfxContext
* aContext
)
4686 // Fix up metrics to include hyphen
4687 gfxTextRunCache::AutoTextRun
hyphenTextRun(
4688 GetHyphenTextRun(aBaseTextRun
, aContext
, aTextFrame
));
4689 if (!hyphenTextRun
.get())
4692 gfxTextRun::Metrics hyphenMetrics
=
4693 hyphenTextRun
->MeasureText(0, hyphenTextRun
->GetLength(),
4694 aBoundingBoxType
, aContext
, nsnull
);
4695 aMetrics
->CombineWith(hyphenMetrics
, aBaseTextRun
->IsRightToLeft());
4699 nsTextFrame::PaintOneShadow(PRUint32 aOffset
, PRUint32 aLength
,
4700 nsCSSShadowItem
* aShadowDetails
,
4701 PropertyProvider
* aProvider
, const nsRect
& aDirtyRect
,
4702 const gfxPoint
& aFramePt
, const gfxPoint
& aTextBaselinePt
,
4703 gfxContext
* aCtx
, const nscolor
& aForegroundColor
)
4705 gfxPoint
shadowOffset(aShadowDetails
->mXOffset
, aShadowDetails
->mYOffset
);
4706 nscoord blurRadius
= NS_MAX(aShadowDetails
->mRadius
, 0);
4708 gfxTextRun::Metrics shadowMetrics
=
4709 mTextRun
->MeasureText(aOffset
, aLength
, gfxFont::LOOSE_INK_EXTENTS
,
4711 if (GetStateBits() & TEXT_HYPHEN_BREAK
) {
4712 AddHyphenToMetrics(this, mTextRun
, &shadowMetrics
, gfxFont::LOOSE_INK_EXTENTS
, aCtx
);
4715 // This rect is the box which is equivalent to where the shadow will be painted.
4716 // The origin of mBoundingBox is the text baseline left, so we must translate it by
4717 // that much in order to make the origin the top-left corner of the text bounding box.
4718 gfxRect shadowGfxRect
= shadowMetrics
.mBoundingBox
+
4719 gfxPoint(aFramePt
.x
, aTextBaselinePt
.y
) + shadowOffset
;
4720 nsRect
shadowRect(shadowGfxRect
.X(), shadowGfxRect
.Y(),
4721 shadowGfxRect
.Width(), shadowGfxRect
.Height());
4723 nsContextBoxBlur contextBoxBlur
;
4724 gfxContext
* shadowContext
= contextBoxBlur
.Init(shadowRect
, 0, blurRadius
,
4725 PresContext()->AppUnitsPerDevPixel(),
4726 aCtx
, aDirtyRect
, nsnull
);
4730 nscolor shadowColor
;
4731 if (aShadowDetails
->mHasColor
)
4732 shadowColor
= aShadowDetails
->mColor
;
4734 shadowColor
= aForegroundColor
;
4738 aCtx
->SetColor(gfxRGBA(shadowColor
));
4740 // Draw the text onto our alpha-only surface to capture the alpha values.
4741 // Remember that the box blur context has a device offset on it, so we don't need to
4742 // translate any coordinates to fit on the surface.
4743 gfxRect
dirtyGfxRect(aDirtyRect
.x
, aDirtyRect
.y
, aDirtyRect
.width
, aDirtyRect
.height
);
4744 gfxFloat advanceWidth
;
4745 DrawText(shadowContext
,
4746 aTextBaselinePt
+ shadowOffset
,
4747 aOffset
, aLength
, &dirtyGfxRect
, aProvider
, advanceWidth
,
4748 (GetStateBits() & TEXT_HYPHEN_BREAK
) != 0);
4750 // This will only have an effect in quirks mode. Standards mode text-decoration shadow painting
4751 // is handled in nsHTMLContainerFrame.cpp, so you must remember to consider that if you change
4752 // any code behaviour here.
4753 nsTextPaintStyle
textPaintStyle(this);
4754 PaintTextDecorations(shadowContext
, dirtyGfxRect
, aFramePt
+ shadowOffset
,
4755 aTextBaselinePt
+ shadowOffset
,
4756 textPaintStyle
, *aProvider
, &shadowColor
);
4758 contextBoxBlur
.DoPaint();
4762 // Paints selection backgrounds and text in the correct colors. Also computes
4763 // aAllTypes, the union of all selection types that are applying to this text.
4765 nsTextFrame::PaintTextWithSelectionColors(gfxContext
* aCtx
,
4766 const gfxPoint
& aFramePt
,
4767 const gfxPoint
& aTextBaselinePt
, const gfxRect
& aDirtyRect
,
4768 PropertyProvider
& aProvider
, nsTextPaintStyle
& aTextPaintStyle
,
4769 SelectionDetails
* aDetails
, SelectionType
* aAllTypes
)
4771 PRInt32 contentOffset
= aProvider
.GetStart().GetOriginalOffset();
4772 PRInt32 contentLength
= aProvider
.GetOriginalLength();
4774 // Figure out which selections control the colors to use for each character.
4775 nsAutoTArray
<SelectionDetails
*,BIG_TEXT_NODE_SIZE
> prevailingSelectionsBuffer
;
4776 if (!prevailingSelectionsBuffer
.AppendElements(contentLength
))
4778 SelectionDetails
** prevailingSelections
= prevailingSelectionsBuffer
.Elements();
4781 SelectionType allTypes
= 0;
4782 for (i
= 0; i
< contentLength
; ++i
) {
4783 prevailingSelections
[i
] = nsnull
;
4786 SelectionDetails
*sdptr
= aDetails
;
4787 PRBool anyBackgrounds
= PR_FALSE
;
4789 PRInt32 start
= NS_MAX(0, sdptr
->mStart
- contentOffset
);
4790 PRInt32 end
= NS_MIN(contentLength
, sdptr
->mEnd
- contentOffset
);
4791 SelectionType type
= sdptr
->mType
;
4794 // Ignore selections that don't set colors
4795 nscolor foreground
, background
;
4796 if (GetSelectionTextColors(type
, aTextPaintStyle
, sdptr
->mTextRangeStyle
,
4797 &foreground
, &background
)) {
4798 if (NS_GET_A(background
) > 0) {
4799 anyBackgrounds
= PR_TRUE
;
4801 for (i
= start
; i
< end
; ++i
) {
4802 // Favour normal selection over IME selections
4803 if (!prevailingSelections
[i
] ||
4804 type
< prevailingSelections
[i
]->mType
) {
4805 prevailingSelections
[i
] = sdptr
;
4810 sdptr
= sdptr
->mNext
;
4812 *aAllTypes
= allTypes
;
4814 gfxFloat xOffset
, hyphenWidth
;
4815 PRUint32 offset
, length
; // in transformed string
4817 nsTextRangeStyle rangeStyle
;
4818 // Draw background colors
4819 if (anyBackgrounds
) {
4820 SelectionIterator
iterator(prevailingSelections
, contentOffset
, contentLength
,
4821 aProvider
, mTextRun
);
4822 while (iterator
.GetNextSegment(&xOffset
, &offset
, &length
, &hyphenWidth
,
4823 &type
, &rangeStyle
)) {
4824 nscolor foreground
, background
;
4825 GetSelectionTextColors(type
, aTextPaintStyle
, rangeStyle
,
4826 &foreground
, &background
);
4827 // Draw background color
4828 gfxFloat advance
= hyphenWidth
+
4829 mTextRun
->GetAdvanceWidth(offset
, length
, &aProvider
);
4830 if (NS_GET_A(background
) > 0) {
4831 gfxFloat x
= xOffset
- (mTextRun
->IsRightToLeft() ? advance
: 0);
4832 FillClippedRect(aCtx
, aTextPaintStyle
.PresContext(),
4833 background
, aDirtyRect
,
4834 gfxRect(aFramePt
.x
+ x
, aFramePt
.y
, advance
, GetSize().height
));
4836 iterator
.UpdateWithAdvance(advance
);
4841 SelectionIterator
iterator(prevailingSelections
, contentOffset
, contentLength
,
4842 aProvider
, mTextRun
);
4843 while (iterator
.GetNextSegment(&xOffset
, &offset
, &length
, &hyphenWidth
,
4844 &type
, &rangeStyle
)) {
4845 nscolor foreground
, background
;
4846 GetSelectionTextColors(type
, aTextPaintStyle
, rangeStyle
,
4847 &foreground
, &background
);
4848 // Draw text segment
4849 aCtx
->SetColor(gfxRGBA(foreground
));
4852 DrawText(aCtx
, gfxPoint(aFramePt
.x
+ xOffset
, aTextBaselinePt
.y
),
4853 offset
, length
, &aDirtyRect
, &aProvider
,
4854 advance
, hyphenWidth
> 0);
4856 advance
+= hyphenWidth
;
4858 iterator
.UpdateWithAdvance(advance
);
4863 nsTextFrame::PaintTextSelectionDecorations(gfxContext
* aCtx
,
4864 const gfxPoint
& aFramePt
,
4865 const gfxPoint
& aTextBaselinePt
, const gfxRect
& aDirtyRect
,
4866 PropertyProvider
& aProvider
, nsTextPaintStyle
& aTextPaintStyle
,
4867 SelectionDetails
* aDetails
, SelectionType aSelectionType
)
4869 // Hide text decorations if we're currently hiding @font-face fallback text
4870 if (aProvider
.GetFontGroup()->ShouldSkipDrawing())
4873 PRInt32 contentOffset
= aProvider
.GetStart().GetOriginalOffset();
4874 PRInt32 contentLength
= aProvider
.GetOriginalLength();
4876 // Figure out which characters will be decorated for this selection.
4877 nsAutoTArray
<SelectionDetails
*, BIG_TEXT_NODE_SIZE
> selectedCharsBuffer
;
4878 if (!selectedCharsBuffer
.AppendElements(contentLength
))
4880 SelectionDetails
** selectedChars
= selectedCharsBuffer
.Elements();
4882 for (i
= 0; i
< contentLength
; ++i
) {
4883 selectedChars
[i
] = nsnull
;
4886 SelectionDetails
*sdptr
= aDetails
;
4888 if (sdptr
->mType
== aSelectionType
) {
4889 PRInt32 start
= NS_MAX(0, sdptr
->mStart
- contentOffset
);
4890 PRInt32 end
= NS_MIN(contentLength
, sdptr
->mEnd
- contentOffset
);
4891 for (i
= start
; i
< end
; ++i
) {
4892 selectedChars
[i
] = sdptr
;
4895 sdptr
= sdptr
->mNext
;
4898 gfxFont
* firstFont
= aProvider
.GetFontGroup()->GetFontAt(0);
4901 gfxFont::Metrics
decorationMetrics(firstFont
->GetMetrics());
4902 decorationMetrics
.underlineOffset
=
4903 aProvider
.GetFontGroup()->GetUnderlineOffset();
4905 SelectionIterator
iterator(selectedChars
, contentOffset
, contentLength
,
4906 aProvider
, mTextRun
);
4907 gfxFloat xOffset
, hyphenWidth
;
4908 PRUint32 offset
, length
;
4909 PRInt32 app
= aTextPaintStyle
.PresContext()->AppUnitsPerDevPixel();
4910 // XXX aTextBaselinePt is in AppUnits, shouldn't it be nsFloatPoint?
4911 gfxPoint
pt(0.0, (aTextBaselinePt
.y
- mAscent
) / app
);
4913 nsTextRangeStyle selectedStyle
;
4914 while (iterator
.GetNextSegment(&xOffset
, &offset
, &length
, &hyphenWidth
,
4915 &type
, &selectedStyle
)) {
4916 gfxFloat advance
= hyphenWidth
+
4917 mTextRun
->GetAdvanceWidth(offset
, length
, &aProvider
);
4918 if (type
== aSelectionType
) {
4919 pt
.x
= (aFramePt
.x
+ xOffset
-
4920 (mTextRun
->IsRightToLeft() ? advance
: 0)) / app
;
4921 gfxFloat width
= PR_ABS(advance
) / app
;
4922 DrawSelectionDecorations(aCtx
, aSelectionType
, this, aTextPaintStyle
,
4924 pt
, width
, mAscent
/ app
, decorationMetrics
);
4926 iterator
.UpdateWithAdvance(advance
);
4931 nsTextFrame::PaintTextWithSelection(gfxContext
* aCtx
,
4932 const gfxPoint
& aFramePt
,
4933 const gfxPoint
& aTextBaselinePt
, const gfxRect
& aDirtyRect
,
4934 PropertyProvider
& aProvider
, nsTextPaintStyle
& aTextPaintStyle
)
4936 SelectionDetails
* details
= GetSelectionDetails();
4940 SelectionType allTypes
;
4941 PaintTextWithSelectionColors(aCtx
, aFramePt
, aTextBaselinePt
, aDirtyRect
,
4942 aProvider
, aTextPaintStyle
, details
, &allTypes
);
4943 PaintTextDecorations(aCtx
, aDirtyRect
, aFramePt
, aTextBaselinePt
,
4944 aTextPaintStyle
, aProvider
);
4946 // Iterate through just the selection types that paint decorations and
4947 // paint decorations for any that actually occur in this frame. Paint
4948 // higher-numbered selection types below lower-numered ones on the
4949 // general principal that lower-numbered selections are higher priority.
4950 allTypes
&= SelectionTypesWithDecorations
;
4951 for (i
= nsISelectionController::NUM_SELECTIONTYPES
- 1; i
>= 1; --i
) {
4952 SelectionType type
= 1 << (i
- 1);
4953 if (allTypes
& type
) {
4954 // There is some selection of this type. Try to paint its decorations
4955 // (there might not be any for this type but that's OK,
4956 // PaintTextSelectionDecorations will exit early).
4957 PaintTextSelectionDecorations(aCtx
, aFramePt
, aTextBaselinePt
, aDirtyRect
,
4958 aProvider
, aTextPaintStyle
, details
, type
);
4962 DestroySelectionDetails(details
);
4967 nsTextFrame::GetCaretColorAt(PRInt32 aOffset
)
4969 NS_PRECONDITION(aOffset
>= 0, "aOffset must be positive");
4971 gfxSkipCharsIterator iter
= EnsureTextRun();
4972 PropertyProvider
provider(this, iter
);
4973 PRInt32 contentOffset
= provider
.GetStart().GetOriginalOffset();
4974 PRInt32 contentLength
= provider
.GetOriginalLength();
4975 NS_PRECONDITION(aOffset
>= contentOffset
&&
4976 aOffset
<= contentOffset
+ contentLength
,
4977 "aOffset must be in the frame's range");
4978 PRInt32 offsetInFrame
= aOffset
- contentOffset
;
4979 if (offsetInFrame
< 0 || offsetInFrame
>= contentLength
) {
4980 return nsFrame::GetCaretColorAt(aOffset
);
4983 nsTextPaintStyle
textPaintStyle(this);
4984 SelectionDetails
* details
= GetSelectionDetails();
4985 SelectionDetails
* sdptr
= details
;
4986 nscolor result
= nsFrame::GetCaretColorAt(aOffset
);
4987 SelectionType type
= 0;
4989 PRInt32 start
= NS_MAX(0, sdptr
->mStart
- contentOffset
);
4990 PRInt32 end
= NS_MIN(contentLength
, sdptr
->mEnd
- contentOffset
);
4991 if (start
<= offsetInFrame
&& offsetInFrame
< end
&&
4992 (type
== 0 || sdptr
->mType
< type
)) {
4993 nscolor foreground
, background
;
4994 if (GetSelectionTextColors(sdptr
->mType
, textPaintStyle
,
4995 sdptr
->mTextRangeStyle
,
4996 &foreground
, &background
)) {
4997 result
= foreground
;
4998 type
= sdptr
->mType
;
5001 sdptr
= sdptr
->mNext
;
5004 DestroySelectionDetails(details
);
5009 ComputeTransformedLength(PropertyProvider
& aProvider
)
5011 gfxSkipCharsIterator
iter(aProvider
.GetStart());
5012 PRUint32 start
= iter
.GetSkippedOffset();
5013 iter
.AdvanceOriginal(aProvider
.GetOriginalLength());
5014 return iter
.GetSkippedOffset() - start
;
5018 nsTextFrame::GetSnappedBaselineY(gfxContext
* aContext
, gfxFloat aY
)
5020 gfxFloat appUnitsPerDevUnit
= mTextRun
->GetAppUnitsPerDevUnit();
5021 gfxFloat baseline
= aY
+ mAscent
;
5022 gfxRect
putativeRect(0, baseline
/appUnitsPerDevUnit
, 1, 1);
5023 if (!aContext
->UserToDevicePixelSnapped(putativeRect
))
5025 return aContext
->DeviceToUser(putativeRect
.pos
).y
*appUnitsPerDevUnit
;
5029 nsTextFrame::PaintText(nsIRenderingContext
* aRenderingContext
, nsPoint aPt
,
5030 const nsRect
& aDirtyRect
)
5032 // Don't pass in aRenderingContext here, because we need a *reference*
5033 // context and aRenderingContext might have some transform in it
5034 // XXX get the block and line passed to us somehow! This is slow!
5035 gfxSkipCharsIterator iter
= EnsureTextRun();
5039 nsTextPaintStyle
textPaintStyle(this);
5040 PropertyProvider
provider(this, iter
);
5041 // Trim trailing whitespace
5042 provider
.InitializeForDisplay(PR_TRUE
);
5044 gfxContext
* ctx
= aRenderingContext
->ThebesContext();
5046 gfxPoint
framePt(aPt
.x
, aPt
.y
);
5047 gfxPoint
textBaselinePt(
5048 mTextRun
->IsRightToLeft() ? gfxFloat(aPt
.x
+ GetSize().width
) : framePt
.x
,
5049 GetSnappedBaselineY(ctx
, aPt
.y
));
5051 gfxRect
dirtyRect(aDirtyRect
.x
, aDirtyRect
.y
,
5052 aDirtyRect
.width
, aDirtyRect
.height
);
5054 gfxFloat advanceWidth
;
5055 gfxRGBA foregroundColor
= gfxRGBA(textPaintStyle
.GetTextColor());
5057 // Paint the text shadow before doing any foreground stuff
5058 const nsStyleText
* textStyle
= GetStyleText();
5059 if (textStyle
->mTextShadow
) {
5060 // Text shadow happens with the last value being painted at the back,
5061 // ie. it is painted first.
5062 for (PRUint32 i
= textStyle
->mTextShadow
->Length(); i
> 0; --i
) {
5063 PaintOneShadow(provider
.GetStart().GetSkippedOffset(),
5064 ComputeTransformedLength(provider
),
5065 textStyle
->mTextShadow
->ShadowAt(i
- 1), &provider
,
5066 aDirtyRect
, framePt
, textBaselinePt
, ctx
,
5067 textPaintStyle
.GetTextColor());
5071 // Fork off to the (slower) paint-with-selection path if necessary.
5072 if (nsLayoutUtils::GetNonGeneratedAncestor(this)->GetStateBits() & NS_FRAME_SELECTED_CONTENT
) {
5073 if (PaintTextWithSelection(ctx
, framePt
, textBaselinePt
,
5074 dirtyRect
, provider
, textPaintStyle
))
5078 ctx
->SetColor(foregroundColor
);
5080 DrawText(ctx
, textBaselinePt
, provider
.GetStart().GetSkippedOffset(),
5081 ComputeTransformedLength(provider
), &dirtyRect
,
5082 &provider
, advanceWidth
,
5083 (GetStateBits() & TEXT_HYPHEN_BREAK
) != 0);
5084 PaintTextDecorations(ctx
, dirtyRect
, framePt
, textBaselinePt
,
5085 textPaintStyle
, provider
);
5089 nsTextFrame::DrawText(gfxContext
* aCtx
, const gfxPoint
& aTextBaselinePt
,
5090 PRUint32 aOffset
, PRUint32 aLength
,
5091 const gfxRect
* aDirtyRect
, PropertyProvider
* aProvider
,
5092 gfxFloat
& aAdvanceWidth
, PRBool aDrawSoftHyphen
)
5094 // Paint the text and soft-hyphen (if any) onto the given graphics context
5095 mTextRun
->Draw(aCtx
, aTextBaselinePt
, aOffset
, aLength
,
5096 aProvider
, &aAdvanceWidth
);
5098 if (aDrawSoftHyphen
) {
5099 // Don't use ctx as the context, because we need a reference context here,
5100 // ctx may be transformed.
5101 gfxTextRunCache::AutoTextRun
hyphenTextRun(GetHyphenTextRun(mTextRun
, nsnull
, this));
5102 if (hyphenTextRun
.get()) {
5103 // For right-to-left text runs, the soft-hyphen is positioned at the left
5104 // of the text, minus its own width
5105 gfxFloat hyphenBaselineX
= aTextBaselinePt
.x
+ mTextRun
->GetDirection() * aAdvanceWidth
-
5106 (mTextRun
->IsRightToLeft() ? hyphenTextRun
->GetAdvanceWidth(0, hyphenTextRun
->GetLength(), nsnull
) : 0);
5107 hyphenTextRun
->Draw(aCtx
, gfxPoint(hyphenBaselineX
, aTextBaselinePt
.y
),
5108 0, hyphenTextRun
->GetLength(), nsnull
, nsnull
);
5114 nsTextFrame::GetSelectionStatus(PRInt16
* aSelectionFlags
)
5116 // get the selection controller
5117 nsCOMPtr
<nsISelectionController
> selectionController
;
5118 nsresult rv
= GetSelectionController(PresContext(),
5119 getter_AddRefs(selectionController
));
5120 if (NS_FAILED(rv
) || !selectionController
)
5121 return nsISelectionController::SELECTION_OFF
;
5123 selectionController
->GetSelectionFlags(aSelectionFlags
);
5125 PRInt16 selectionValue
;
5126 selectionController
->GetDisplaySelection(&selectionValue
);
5128 return selectionValue
;
5132 nsTextFrame::IsVisibleInSelection(nsISelection
* aSelection
)
5134 // Check the quick way first
5135 PRBool isSelected
= (mState
& NS_FRAME_SELECTED_CONTENT
) == NS_FRAME_SELECTED_CONTENT
;
5139 SelectionDetails
* details
= GetSelectionDetails();
5140 PRBool found
= PR_FALSE
;
5142 // where are the selection points "really"
5143 SelectionDetails
*sdptr
= details
;
5145 if (sdptr
->mEnd
> GetContentOffset() &&
5146 sdptr
->mStart
< GetContentEnd() &&
5147 sdptr
->mType
== nsISelectionController::SELECTION_NORMAL
) {
5151 sdptr
= sdptr
->mNext
;
5153 DestroySelectionDetails(details
);
5159 * Compute the longest prefix of text whose width is <= aWidth. Return
5160 * the length of the prefix. Also returns the width of the prefix in aFitWidth.
5163 CountCharsFit(gfxTextRun
* aTextRun
, PRUint32 aStart
, PRUint32 aLength
,
5164 gfxFloat aWidth
, PropertyProvider
* aProvider
,
5165 gfxFloat
* aFitWidth
)
5170 for (i
= 1; i
<= aLength
; ++i
) {
5171 if (i
== aLength
|| aTextRun
->IsClusterStart(aStart
+ i
)) {
5172 gfxFloat nextWidth
= width
+
5173 aTextRun
->GetAdvanceWidth(aStart
+ last
, i
- last
, aProvider
);
5174 if (nextWidth
> aWidth
)
5184 nsIFrame::ContentOffsets
5185 nsTextFrame::CalcContentOffsetsFromFramePoint(nsPoint aPoint
)
5187 return GetCharacterOffsetAtFramePointInternal(aPoint
, PR_TRUE
);
5190 nsIFrame::ContentOffsets
5191 nsTextFrame::GetCharacterOffsetAtFramePoint(const nsPoint
&aPoint
)
5193 return GetCharacterOffsetAtFramePointInternal(aPoint
, PR_FALSE
);
5196 nsIFrame::ContentOffsets
5197 nsTextFrame::GetCharacterOffsetAtFramePointInternal(const nsPoint
&aPoint
,
5198 PRBool aForInsertionPoint
)
5200 ContentOffsets offsets
;
5202 gfxSkipCharsIterator iter
= EnsureTextRun();
5206 PropertyProvider
provider(this, iter
);
5207 // Trim leading but not trailing whitespace if possible
5208 provider
.InitializeForDisplay(PR_FALSE
);
5209 gfxFloat width
= mTextRun
->IsRightToLeft() ? mRect
.width
- aPoint
.x
: aPoint
.x
;
5211 PRUint32 skippedLength
= ComputeTransformedLength(provider
);
5213 PRUint32 charsFit
= CountCharsFit(mTextRun
,
5214 provider
.GetStart().GetSkippedOffset(), skippedLength
, width
, &provider
, &fitWidth
);
5216 PRInt32 selectedOffset
;
5217 if (charsFit
< skippedLength
) {
5218 // charsFit characters fitted, but no more could fit. See if we're
5219 // more than halfway through the cluster.. If we are, choose the next
5221 gfxSkipCharsIterator
extraCluster(provider
.GetStart());
5222 extraCluster
.AdvanceSkipped(charsFit
);
5223 gfxSkipCharsIterator
extraClusterLastChar(extraCluster
);
5224 FindClusterEnd(mTextRun
,
5225 provider
.GetStart().GetOriginalOffset() + provider
.GetOriginalLength(),
5226 &extraClusterLastChar
);
5227 gfxFloat charWidth
=
5228 mTextRun
->GetAdvanceWidth(extraCluster
.GetSkippedOffset(),
5229 GetSkippedDistance(extraCluster
, extraClusterLastChar
) + 1,
5231 selectedOffset
= !aForInsertionPoint
|| width
<= fitWidth
+ charWidth
/2
5232 ? extraCluster
.GetOriginalOffset()
5233 : extraClusterLastChar
.GetOriginalOffset() + 1;
5235 // All characters fitted, we're at (or beyond) the end of the text.
5236 // XXX This could be some pathological situation where negative spacing
5237 // caused characters to move backwards. We can't really handle that
5238 // in the current frame system because frames can't have negative
5239 // intrinsic widths.
5241 provider
.GetStart().GetOriginalOffset() + provider
.GetOriginalLength();
5242 // If we're at the end of a preformatted line which has a terminating
5243 // linefeed, we want to reduce the offset by one to make sure that the
5244 // selection is placed before the linefeed character.
5245 if (GetStyleText()->NewlineIsSignificant() &&
5246 HasTerminalNewline()) {
5251 offsets
.content
= GetContent();
5252 offsets
.offset
= offsets
.secondaryOffset
= selectedOffset
;
5253 offsets
.associateWithNext
= mContentOffset
== offsets
.offset
;
5258 nsTextFrame::CombineSelectionUnderlineRect(nsPresContext
* aPresContext
,
5261 if (aRect
.IsEmpty())
5264 nsRect givenRect
= aRect
;
5266 nsCOMPtr
<nsIFontMetrics
> fm
;
5267 nsLayoutUtils::GetFontMetricsForFrame(this, getter_AddRefs(fm
));
5268 nsIThebesFontMetrics
* tfm
= static_cast<nsIThebesFontMetrics
*>(fm
.get());
5269 gfxFontGroup
* fontGroup
= tfm
->GetThebesFontGroup();
5270 gfxFont
* firstFont
= fontGroup
->GetFontAt(0);
5272 return PR_FALSE
; // OOM
5273 const gfxFont::Metrics
& metrics
= firstFont
->GetMetrics();
5274 gfxFloat underlineOffset
= fontGroup
->GetUnderlineOffset();
5275 gfxFloat ascent
= aPresContext
->AppUnitsToGfxUnits(mAscent
);
5276 gfxFloat descentLimit
=
5277 ComputeDescentLimitForSelectionUnderline(aPresContext
, this, metrics
);
5279 SelectionDetails
*details
= GetSelectionDetails();
5280 for (SelectionDetails
*sd
= details
; sd
; sd
= sd
->mNext
) {
5281 if (sd
->mStart
== sd
->mEnd
|| !(sd
->mType
& SelectionTypesWithDecorations
))
5287 nsTextPaintStyle::GetUnderlineStyleIndexForSelectionType(sd
->mType
);
5288 if (sd
->mType
== nsISelectionController::SELECTION_SPELLCHECK
) {
5289 if (!nsTextPaintStyle::GetSelectionUnderline(aPresContext
, index
, nsnull
,
5290 &relativeSize
, &style
)) {
5295 nsTextRangeStyle
& rangeStyle
= sd
->mTextRangeStyle
;
5296 if (rangeStyle
.IsDefined()) {
5297 if (!rangeStyle
.IsLineStyleDefined() ||
5298 rangeStyle
.mLineStyle
== nsTextRangeStyle::LINESTYLE_NONE
) {
5301 style
= GetTextDecorationStyle(rangeStyle
);
5302 relativeSize
= rangeStyle
.mIsBoldLine
? 2.0f
: 1.0f
;
5303 } else if (!nsTextPaintStyle::GetSelectionUnderline(aPresContext
, index
,
5304 nsnull
, &relativeSize
,
5309 nsRect decorationArea
;
5310 gfxSize
size(aPresContext
->AppUnitsToGfxUnits(aRect
.width
),
5311 ComputeSelectionUnderlineHeight(aPresContext
,
5312 metrics
, sd
->mType
));
5313 relativeSize
= NS_MAX(relativeSize
, 1.0f
);
5314 size
.height
*= relativeSize
;
5316 nsCSSRendering::GetTextDecorationRect(aPresContext
, size
,
5317 ascent
, underlineOffset
,
5318 NS_STYLE_TEXT_DECORATION_UNDERLINE
,
5319 style
, descentLimit
);
5320 aRect
.UnionRect(aRect
, decorationArea
);
5322 DestroySelectionDetails(details
);
5324 return !aRect
.IsEmpty() && !givenRect
.Contains(aRect
);
5328 nsTextFrame::SetSelected(PRBool aSelected
,
5329 SelectionType aType
)
5331 SetSelectedRange(0, mContent
->GetText()->GetLength(), aSelected
, aType
);
5335 nsTextFrame::SetSelectedRange(PRUint32 aStart
,
5338 SelectionType aType
)
5340 NS_ASSERTION(!GetPrevContinuation(), "Should only be called for primary frame");
5341 DEBUG_VERIFY_NOT_DIRTY(mState
);
5343 // Selection is collapsed, which can't affect text frame rendering
5347 if (aType
== nsISelectionController::SELECTION_NORMAL
) {
5348 // check whether style allows selection
5350 IsSelectable(&selectable
, nsnull
);
5355 PRBool anySelected
= PR_FALSE
;
5357 nsTextFrame
* f
= this;
5358 while (f
&& f
->GetContentEnd() <= PRInt32(aStart
)) {
5359 if (f
->GetStateBits() & NS_FRAME_SELECTED_CONTENT
) {
5360 anySelected
= PR_TRUE
;
5362 f
= static_cast<nsTextFrame
*>(f
->GetNextContinuation());
5365 nsPresContext
* presContext
= PresContext();
5366 while (f
&& f
->GetContentOffset() < PRInt32(aEnd
)) {
5368 f
->AddStateBits(NS_FRAME_SELECTED_CONTENT
);
5369 anySelected
= PR_TRUE
;
5370 } else { // we need to see if any other selection is available.
5371 SelectionDetails
*details
= f
->GetSelectionDetails();
5373 anySelected
= PR_TRUE
;
5374 DestroySelectionDetails(details
);
5376 f
->RemoveStateBits(NS_FRAME_SELECTED_CONTENT
);
5380 // We may need to reflow to recompute the overflow area for
5381 // spellchecking or IME underline if their underline is thicker than
5382 // the normal decoration line.
5383 if (aType
& SelectionTypesWithDecorations
) {
5384 PRBool didHaveOverflowingSelection
=
5385 (f
->GetStateBits() & TEXT_SELECTION_UNDERLINE_OVERFLOWED
) != 0;
5386 nsRect
r(nsPoint(0, 0), GetSize());
5387 PRBool willHaveOverflowingSelection
=
5388 aSelected
&& f
->CombineSelectionUnderlineRect(presContext
, r
);
5389 if (didHaveOverflowingSelection
|| willHaveOverflowingSelection
) {
5390 presContext
->PresShell()->FrameNeedsReflow(f
,
5391 nsIPresShell::eStyleChange
,
5395 // Selection might change anything. Invalidate the overflow area.
5396 f
->InvalidateOverflowRect();
5398 f
= static_cast<nsTextFrame
*>(f
->GetNextContinuation());
5401 // Scan remaining continuations to see if any are selected
5402 while (f
&& !anySelected
) {
5403 if (f
->GetStateBits() & NS_FRAME_SELECTED_CONTENT
) {
5404 anySelected
= PR_TRUE
;
5406 f
= static_cast<nsTextFrame
*>(f
->GetNextContinuation());
5410 mContent
->SetFlags(NS_TEXT_IN_SELECTION
);
5412 // This is only legal because there is only one presentation for the
5413 // content with a selection
5414 mContent
->UnsetFlags(NS_TEXT_IN_SELECTION
);
5419 nsTextFrame::GetPointFromOffset(PRInt32 inOffset
,
5423 return NS_ERROR_NULL_POINTER
;
5428 DEBUG_VERIFY_NOT_DIRTY(mState
);
5429 if (mState
& NS_FRAME_IS_DIRTY
)
5430 return NS_ERROR_UNEXPECTED
;
5432 if (GetContentLength() <= 0) {
5436 gfxSkipCharsIterator iter
= EnsureTextRun();
5438 return NS_ERROR_FAILURE
;
5440 PropertyProvider
properties(this, iter
);
5441 // Don't trim trailing whitespace, we want the caret to appear in the right
5442 // place if it's positioned there
5443 properties
.InitializeForDisplay(PR_FALSE
);
5445 if (inOffset
< GetContentOffset()){
5446 NS_WARNING("offset before this frame's content");
5447 inOffset
= GetContentOffset();
5448 } else if (inOffset
> GetContentEnd()) {
5449 NS_WARNING("offset after this frame's content");
5450 inOffset
= GetContentEnd();
5452 PRInt32 trimmedOffset
= properties
.GetStart().GetOriginalOffset();
5453 PRInt32 trimmedEnd
= trimmedOffset
+ properties
.GetOriginalLength();
5454 inOffset
= NS_MAX(inOffset
, trimmedOffset
);
5455 inOffset
= NS_MIN(inOffset
, trimmedEnd
);
5457 iter
.SetOriginalOffset(inOffset
);
5459 if (inOffset
< trimmedEnd
&&
5460 !iter
.IsOriginalCharSkipped() &&
5461 !mTextRun
->IsClusterStart(iter
.GetSkippedOffset())) {
5462 NS_WARNING("GetPointFromOffset called for non-cluster boundary");
5463 FindClusterStart(mTextRun
, trimmedOffset
, &iter
);
5466 gfxFloat advanceWidth
=
5467 mTextRun
->GetAdvanceWidth(properties
.GetStart().GetSkippedOffset(),
5468 GetSkippedDistance(properties
.GetStart(), iter
),
5470 nscoord width
= NSToCoordCeilClamped(advanceWidth
);
5472 if (mTextRun
->IsRightToLeft()) {
5473 outPoint
->x
= mRect
.width
- width
;
5475 outPoint
->x
= width
;
5483 nsTextFrame::GetChildFrameContainingOffset(PRInt32 aContentOffset
,
5485 PRInt32
* aOutOffset
,
5486 nsIFrame
**aOutFrame
)
5488 DEBUG_VERIFY_NOT_DIRTY(mState
);
5489 #if 0 //XXXrbs disable due to bug 310227
5490 if (mState
& NS_FRAME_IS_DIRTY
)
5491 return NS_ERROR_UNEXPECTED
;
5494 NS_ASSERTION(aOutOffset
&& aOutFrame
, "Bad out parameters");
5495 NS_ASSERTION(aContentOffset
>= 0, "Negative content offset, existing code was very broken!");
5496 nsIFrame
* primaryFrame
= mContent
->GetPrimaryFrame();
5497 if (this != primaryFrame
) {
5498 // This call needs to happen on the primary frame
5499 return primaryFrame
->GetChildFrameContainingOffset(aContentOffset
, aHint
,
5500 aOutOffset
, aOutFrame
);
5503 nsTextFrame
* f
= this;
5504 PRInt32 offset
= mContentOffset
;
5506 // Try to look up the offset to frame property
5507 nsTextFrame
* cachedFrame
= static_cast<nsTextFrame
*>
5508 (Properties().Get(OffsetToFrameProperty()));
5512 offset
= f
->GetContentOffset();
5514 f
->RemoveStateBits(TEXT_IN_OFFSET_CACHE
);
5517 if ((aContentOffset
>= offset
) &&
5518 (aHint
|| aContentOffset
!= offset
)) {
5520 nsTextFrame
* next
= static_cast<nsTextFrame
*>(f
->GetNextContinuation());
5521 if (!next
|| aContentOffset
< next
->GetContentOffset())
5523 if (aContentOffset
== next
->GetContentOffset()) {
5533 nsTextFrame
* prev
= static_cast<nsTextFrame
*>(f
->GetPrevContinuation());
5534 if (!prev
|| aContentOffset
> f
->GetContentOffset())
5536 if (aContentOffset
== f
->GetContentOffset()) {
5546 *aOutOffset
= aContentOffset
- f
->GetContentOffset();
5549 // cache the frame we found
5550 Properties().Set(OffsetToFrameProperty(), f
);
5551 f
->AddStateBits(TEXT_IN_OFFSET_CACHE
);
5557 nsTextFrame::PeekOffsetNoAmount(PRBool aForward
, PRInt32
* aOffset
)
5559 NS_ASSERTION(aOffset
&& *aOffset
<= GetContentLength(), "aOffset out of range");
5561 gfxSkipCharsIterator iter
= EnsureTextRun();
5565 TrimmedOffsets trimmed
= GetTrimmedOffsets(mContent
->GetText(), PR_TRUE
);
5566 // Check whether there are nonskipped characters in the trimmmed range
5567 return iter
.ConvertOriginalToSkipped(trimmed
.GetEnd()) >
5568 iter
.ConvertOriginalToSkipped(trimmed
.mStart
);
5572 * This class iterates through the clusters before or after the given
5573 * aPosition (which is a content offset). You can test each cluster
5574 * to see if it's whitespace (as far as selection/caret movement is concerned),
5575 * or punctuation, or if there is a word break before the cluster. ("Before"
5576 * is interpreted according to aDirection, so if aDirection is -1, "before"
5577 * means actually *after* the cluster content.)
5579 class NS_STACK_CLASS ClusterIterator
{
5581 ClusterIterator(nsTextFrame
* aTextFrame
, PRInt32 aPosition
, PRInt32 aDirection
,
5582 nsString
& aContext
);
5584 PRBool
NextCluster();
5585 PRBool
IsWhitespace();
5586 PRBool
IsPunctuation();
5587 PRBool
HaveWordBreakBefore() { return mHaveWordBreak
; }
5588 PRInt32
GetAfterOffset();
5589 PRInt32
GetBeforeOffset();
5592 nsCOMPtr
<nsIUGenCategory
> mCategories
;
5593 gfxSkipCharsIterator mIterator
;
5594 const nsTextFragment
* mFrag
;
5595 nsTextFrame
* mTextFrame
;
5598 nsTextFrame::TrimmedOffsets mTrimmed
;
5599 nsTArray
<PRPackedBool
> mWordBreaks
;
5600 PRPackedBool mHaveWordBreak
;
5604 IsAcceptableCaretPosition(const gfxSkipCharsIterator
& aIter
,
5605 PRBool aRespectClusters
,
5606 gfxTextRun
* aTextRun
,
5609 if (aIter
.IsOriginalCharSkipped())
5611 PRUint32 index
= aIter
.GetSkippedOffset();
5612 if (aRespectClusters
&& !aTextRun
->IsClusterStart(index
))
5615 // Check whether the proposed position is in between the two halves of a
5616 // surrogate pair; if so, this is not a valid character boundary.
5617 // (In the case where we are respecting clusters, we won't actually get
5618 // this far because the low surrogate is also marked as non-clusterStart
5619 // so we'll return FALSE above.)
5620 // If the textrun is 8-bit it can't have any surrogates, so we only need
5621 // to check the actual characters if GetTextUnicode() returns non-null.
5622 const PRUnichar
*txt
= aTextRun
->GetTextUnicode();
5623 if (txt
&& NS_IS_LOW_SURROGATE(txt
[index
]) &&
5624 NS_IS_HIGH_SURROGATE(txt
[index
-1]))
5631 nsTextFrame::PeekOffsetCharacter(PRBool aForward
, PRInt32
* aOffset
,
5632 PRBool aRespectClusters
)
5634 PRInt32 contentLength
= GetContentLength();
5635 NS_ASSERTION(aOffset
&& *aOffset
<= contentLength
, "aOffset out of range");
5638 PRUint8 selectStyle
;
5639 IsSelectable(&selectable
, &selectStyle
);
5640 if (selectStyle
== NS_STYLE_USER_SELECT_ALL
)
5643 gfxSkipCharsIterator iter
= EnsureTextRun();
5647 TrimmedOffsets trimmed
= GetTrimmedOffsets(mContent
->GetText(), PR_FALSE
);
5649 // A negative offset means "end of frame".
5650 PRInt32 startOffset
= GetContentOffset() + (*aOffset
< 0 ? contentLength
: *aOffset
);
5653 // If at the beginning of the line, look at the previous continuation
5654 for (PRInt32 i
= NS_MIN(trimmed
.GetEnd(), startOffset
) - 1;
5655 i
>= trimmed
.mStart
; --i
) {
5656 iter
.SetOriginalOffset(i
);
5657 if (IsAcceptableCaretPosition(iter
, aRespectClusters
, mTextRun
, this)) {
5658 *aOffset
= i
- mContentOffset
;
5664 // If we're at the end of a line, look at the next continuation
5665 iter
.SetOriginalOffset(startOffset
);
5666 if (startOffset
<= trimmed
.GetEnd() &&
5667 !(startOffset
< trimmed
.GetEnd() &&
5668 GetStyleText()->NewlineIsSignificant() &&
5669 iter
.GetSkippedOffset() < mTextRun
->GetLength() &&
5670 mTextRun
->GetChar(iter
.GetSkippedOffset()) == '\n')) {
5671 for (PRInt32 i
= startOffset
+ 1; i
<= trimmed
.GetEnd(); ++i
) {
5672 iter
.SetOriginalOffset(i
);
5673 if (i
== trimmed
.GetEnd() ||
5674 IsAcceptableCaretPosition(iter
, aRespectClusters
, mTextRun
, this)) {
5675 *aOffset
= i
- mContentOffset
;
5680 *aOffset
= contentLength
;
5687 ClusterIterator::IsWhitespace()
5689 NS_ASSERTION(mCharIndex
>= 0, "No cluster selected");
5690 return IsSelectionSpace(mFrag
, mCharIndex
);
5694 ClusterIterator::IsPunctuation()
5696 NS_ASSERTION(mCharIndex
>= 0, "No cluster selected");
5699 nsIUGenCategory::nsUGenCategory c
= mCategories
->Get(mFrag
->CharAt(mCharIndex
));
5700 return c
== nsIUGenCategory::kPunctuation
|| c
== nsIUGenCategory::kSymbol
;
5704 ClusterIterator::GetBeforeOffset()
5706 NS_ASSERTION(mCharIndex
>= 0, "No cluster selected");
5707 return mCharIndex
+ (mDirection
> 0 ? 0 : 1);
5711 ClusterIterator::GetAfterOffset()
5713 NS_ASSERTION(mCharIndex
>= 0, "No cluster selected");
5714 return mCharIndex
+ (mDirection
> 0 ? 1 : 0);
5718 ClusterIterator::NextCluster()
5722 gfxTextRun
* textRun
= mTextFrame
->GetTextRun();
5724 mHaveWordBreak
= PR_FALSE
;
5726 PRBool keepGoing
= PR_FALSE
;
5727 if (mDirection
> 0) {
5728 if (mIterator
.GetOriginalOffset() >= mTrimmed
.GetEnd())
5730 keepGoing
= mIterator
.IsOriginalCharSkipped() ||
5731 mIterator
.GetOriginalOffset() < mTrimmed
.mStart
||
5732 !textRun
->IsClusterStart(mIterator
.GetSkippedOffset());
5733 mCharIndex
= mIterator
.GetOriginalOffset();
5734 mIterator
.AdvanceOriginal(1);
5736 if (mIterator
.GetOriginalOffset() <= mTrimmed
.mStart
)
5738 mIterator
.AdvanceOriginal(-1);
5739 keepGoing
= mIterator
.IsOriginalCharSkipped() ||
5740 mIterator
.GetOriginalOffset() >= mTrimmed
.GetEnd() ||
5741 !textRun
->IsClusterStart(mIterator
.GetSkippedOffset());
5742 mCharIndex
= mIterator
.GetOriginalOffset();
5745 if (mWordBreaks
[GetBeforeOffset() - mTextFrame
->GetContentOffset()]) {
5746 mHaveWordBreak
= PR_TRUE
;
5753 ClusterIterator::ClusterIterator(nsTextFrame
* aTextFrame
, PRInt32 aPosition
,
5754 PRInt32 aDirection
, nsString
& aContext
)
5755 : mTextFrame(aTextFrame
), mDirection(aDirection
), mCharIndex(-1)
5757 mIterator
= aTextFrame
->EnsureTextRun();
5758 if (!aTextFrame
->GetTextRun()) {
5759 mDirection
= 0; // signal failure
5762 mIterator
.SetOriginalOffset(aPosition
);
5764 mCategories
= do_GetService(NS_UNICHARCATEGORY_CONTRACTID
);
5766 mFrag
= aTextFrame
->GetContent()->GetText();
5767 mTrimmed
= aTextFrame
->GetTrimmedOffsets(mFrag
, PR_TRUE
);
5769 PRInt32 textOffset
= aTextFrame
->GetContentOffset();
5770 PRInt32 textLen
= aTextFrame
->GetContentLength();
5771 if (!mWordBreaks
.AppendElements(textLen
+ 1)) {
5772 mDirection
= 0; // signal failure
5775 memset(mWordBreaks
.Elements(), PR_FALSE
, textLen
+ 1);
5777 if (aDirection
> 0) {
5778 if (aContext
.IsEmpty()) {
5779 // No previous context, so it must be the start of a line or text run
5780 mWordBreaks
[0] = PR_TRUE
;
5782 textStart
= aContext
.Length();
5783 mFrag
->AppendTo(aContext
, textOffset
, textLen
);
5785 if (aContext
.IsEmpty()) {
5786 // No following context, so it must be the end of a line or text run
5787 mWordBreaks
[textLen
] = PR_TRUE
;
5791 mFrag
->AppendTo(str
, textOffset
, textLen
);
5792 aContext
.Insert(str
, 0);
5794 nsIWordBreaker
* wordBreaker
= nsContentUtils::WordBreaker();
5796 for (i
= 0; i
<= textLen
; ++i
) {
5797 PRInt32 indexInText
= i
+ textStart
;
5799 wordBreaker
->BreakInBetween(aContext
.get(), indexInText
,
5800 aContext
.get() + indexInText
,
5801 aContext
.Length() - indexInText
);
5806 nsTextFrame::PeekOffsetWord(PRBool aForward
, PRBool aWordSelectEatSpace
, PRBool aIsKeyboardSelect
,
5807 PRInt32
* aOffset
, PeekWordState
* aState
)
5809 PRInt32 contentLength
= GetContentLength();
5810 NS_ASSERTION (aOffset
&& *aOffset
<= contentLength
, "aOffset out of range");
5813 PRUint8 selectStyle
;
5814 IsSelectable(&selectable
, &selectStyle
);
5815 if (selectStyle
== NS_STYLE_USER_SELECT_ALL
)
5818 PRInt32 offset
= GetContentOffset() + (*aOffset
< 0 ? contentLength
: *aOffset
);
5819 ClusterIterator
cIter(this, offset
, aForward
? 1 : -1, aState
->mContext
);
5821 if (!cIter
.NextCluster())
5825 PRBool isPunctuation
= cIter
.IsPunctuation();
5826 PRBool isWhitespace
= cIter
.IsWhitespace();
5827 PRBool isWordBreakBefore
= cIter
.HaveWordBreakBefore();
5828 if (aWordSelectEatSpace
== isWhitespace
&& !aState
->mSawBeforeType
) {
5829 aState
->SetSawBeforeType();
5830 aState
->Update(isPunctuation
, isWhitespace
);
5833 // See if we can break before the current cluster
5834 if (!aState
->mAtStart
) {
5836 if (isPunctuation
!= aState
->mLastCharWasPunctuation
) {
5837 canBreak
= BreakWordBetweenPunctuation(aState
, aForward
,
5838 isPunctuation
, isWhitespace
, aIsKeyboardSelect
);
5839 } else if (!aState
->mLastCharWasWhitespace
&&
5840 !isWhitespace
&& !isPunctuation
&& isWordBreakBefore
) {
5841 // if both the previous and the current character are not white
5842 // space but this can be word break before, we don't need to eat
5843 // a white space in this case. This case happens in some languages
5844 // that their words are not separated by white spaces. E.g.,
5845 // Japanese and Chinese.
5848 canBreak
= isWordBreakBefore
&& aState
->mSawBeforeType
;
5851 *aOffset
= cIter
.GetBeforeOffset() - mContentOffset
;
5855 aState
->Update(isPunctuation
, isWhitespace
);
5856 } while (cIter
.NextCluster());
5858 *aOffset
= cIter
.GetAfterOffset() - mContentOffset
;
5862 // TODO this needs to be deCOMtaminated with the interface fixed in
5863 // nsIFrame.h, but we won't do that until the old textframe is gone.
5865 nsTextFrame::CheckVisibility(nsPresContext
* aContext
, PRInt32 aStartIndex
,
5866 PRInt32 aEndIndex
, PRBool aRecurse
, PRBool
*aFinished
, PRBool
*aRetval
)
5869 return NS_ERROR_NULL_POINTER
;
5871 // Text in the range is visible if there is at least one character in the range
5872 // that is not skipped and is mapped by this frame (which is the primary frame)
5873 // or one of its continuations.
5874 for (nsTextFrame
* f
= this; f
;
5875 f
= static_cast<nsTextFrame
*>(GetNextContinuation())) {
5876 PRInt32 dummyOffset
= 0;
5877 if (f
->PeekOffsetNoAmount(PR_TRUE
, &dummyOffset
)) {
5883 *aRetval
= PR_FALSE
;
5888 nsTextFrame::GetOffsets(PRInt32
&start
, PRInt32
&end
) const
5890 start
= GetContentOffset();
5891 end
= GetContentEnd();
5896 FindEndOfPunctuationRun(const nsTextFragment
* aFrag
,
5897 gfxTextRun
* aTextRun
,
5898 gfxSkipCharsIterator
* aIter
,
5905 for (i
= aStart
; i
< aEnd
- aOffset
; ++i
) {
5906 if (nsContentUtils::IsPunctuationMarkAt(aFrag
, aOffset
+ i
)) {
5907 aIter
->SetOriginalOffset(aOffset
+ i
);
5908 FindClusterEnd(aTextRun
, aEnd
, aIter
);
5909 i
= aIter
->GetOriginalOffset() - aOffset
;
5918 * Returns PR_TRUE if this text frame completes the first-letter, PR_FALSE
5919 * if it does not contain a true "letter".
5920 * If returns PR_TRUE, then it also updates aLength to cover just the first-letter
5923 * XXX :first-letter should be handled during frame construction
5924 * (and it has a good bit in common with nextBidi)
5926 * @param aLength an in/out parameter: on entry contains the maximum length to
5927 * return, on exit returns length of the first-letter fragment (which may
5928 * include leading and trailing punctuation, for example)
5931 FindFirstLetterRange(const nsTextFragment
* aFrag
,
5932 gfxTextRun
* aTextRun
,
5933 PRInt32 aOffset
, const gfxSkipCharsIterator
& aIter
,
5937 PRInt32 length
= *aLength
;
5938 PRInt32 endOffset
= aOffset
+ length
;
5939 gfxSkipCharsIterator
iter(aIter
);
5941 // skip leading whitespace, then consume clusters that start with punctuation
5942 i
= FindEndOfPunctuationRun(aFrag
, aTextRun
, &iter
, aOffset
,
5943 GetTrimmableWhitespaceCount(aFrag
, aOffset
, length
, 1),
5948 // If the next character is not a letter or number, there is no first-letter.
5949 // Return PR_TRUE so that we don't go on looking, but set aLength to 0.
5950 if (!nsContentUtils::IsAlphanumericAt(aFrag
, aOffset
+ i
)) {
5955 // consume another cluster (the actual first letter)
5956 iter
.SetOriginalOffset(aOffset
+ i
);
5957 FindClusterEnd(aTextRun
, endOffset
, &iter
);
5958 i
= iter
.GetOriginalOffset() - aOffset
;
5959 if (i
+ 1 == length
)
5962 // consume clusters that start with punctuation
5963 i
= FindEndOfPunctuationRun(aFrag
, aTextRun
, &iter
, aOffset
, i
+ 1, endOffset
);
5970 FindStartAfterSkippingWhitespace(PropertyProvider
* aProvider
,
5971 nsIFrame::InlineIntrinsicWidthData
* aData
,
5972 const nsStyleText
* aTextStyle
,
5973 gfxSkipCharsIterator
* aIterator
,
5974 PRUint32 aFlowEndInTextRun
)
5976 if (aData
->skipWhitespace
) {
5977 while (aIterator
->GetSkippedOffset() < aFlowEndInTextRun
&&
5978 IsTrimmableSpace(aProvider
->GetFragment(), aIterator
->GetOriginalOffset(), aTextStyle
)) {
5979 aIterator
->AdvanceOriginal(1);
5982 return aIterator
->GetSkippedOffset();
5986 void nsTextFrame::MarkIntrinsicWidthsDirty()
5988 ClearTextRun(nsnull
);
5989 nsFrame::MarkIntrinsicWidthsDirty();
5992 // XXX this doesn't handle characters shaped by line endings. We need to
5993 // temporarily override the "current line ending" settings.
5995 nsTextFrame::AddInlineMinWidthForFlow(nsIRenderingContext
*aRenderingContext
,
5996 nsIFrame::InlineMinWidthData
*aData
)
5998 PRUint32 flowEndInTextRun
;
5999 gfxContext
* ctx
= aRenderingContext
->ThebesContext();
6000 gfxSkipCharsIterator iter
=
6001 EnsureTextRun(ctx
, aData
->lineContainer
, aData
->line
, &flowEndInTextRun
);
6005 // Pass null for the line container. This will disable tab spacing, but that's
6006 // OK since we can't really handle tabs for intrinsic sizing anyway.
6007 const nsStyleText
* textStyle
= GetStyleText();
6008 const nsTextFragment
* frag
= mContent
->GetText();
6009 PropertyProvider
provider(mTextRun
, textStyle
, frag
, this,
6010 iter
, PR_INT32_MAX
, nsnull
, 0);
6012 PRBool collapseWhitespace
= !textStyle
->WhiteSpaceIsSignificant();
6013 PRBool preformatNewlines
= textStyle
->NewlineIsSignificant();
6014 PRBool preformatTabs
= textStyle
->WhiteSpaceIsSignificant();
6015 gfxFloat tabWidth
= -1;
6017 FindStartAfterSkippingWhitespace(&provider
, aData
, textStyle
, &iter
, flowEndInTextRun
);
6019 // XXX Should we consider hyphenation here?
6020 for (PRUint32 i
= start
, wordStart
= start
; i
<= flowEndInTextRun
; ++i
) {
6021 PRBool preformattedNewline
= PR_FALSE
;
6022 PRBool preformattedTab
= PR_FALSE
;
6023 if (i
< flowEndInTextRun
) {
6024 // XXXldb Shouldn't we be including the newline as part of the
6025 // segment that it ends rather than part of the segment that it
6027 preformattedNewline
= preformatNewlines
&& mTextRun
->GetChar(i
) == '\n';
6028 preformattedTab
= preformatTabs
&& mTextRun
->GetChar(i
) == '\t';
6029 if (!mTextRun
->CanBreakLineBefore(i
) && !preformattedNewline
&&
6031 // we can't break here (and it's not the end of the flow)
6036 if (i
> wordStart
) {
6038 NSToCoordCeilClamped(mTextRun
->GetAdvanceWidth(wordStart
, i
- wordStart
, &provider
));
6039 aData
->currentLine
= NSCoordSaturatingAdd(aData
->currentLine
, width
);
6040 aData
->atStartOfLine
= PR_FALSE
;
6042 if (collapseWhitespace
) {
6043 PRUint32 trimStart
= GetEndOfTrimmedText(frag
, textStyle
, wordStart
, i
, &iter
);
6044 if (trimStart
== start
) {
6045 // This is *all* trimmable whitespace, so whatever trailingWhitespace
6046 // we saw previously is still trailing...
6047 aData
->trailingWhitespace
+= width
;
6049 // Some non-whitespace so the old trailingWhitespace is no longer trailing
6050 aData
->trailingWhitespace
=
6051 NSToCoordCeilClamped(mTextRun
->GetAdvanceWidth(trimStart
, i
- trimStart
, &provider
));
6054 aData
->trailingWhitespace
= 0;
6058 if (preformattedTab
) {
6059 PropertyProvider::Spacing spacing
;
6060 provider
.GetSpacing(i
, 1, &spacing
);
6061 aData
->currentLine
+= nscoord(spacing
.mBefore
);
6063 AdvanceToNextTab(aData
->currentLine
, this,
6064 mTextRun
, &tabWidth
);
6065 aData
->currentLine
= nscoord(afterTab
+ spacing
.mAfter
);
6067 } else if (i
< flowEndInTextRun
||
6068 (i
== mTextRun
->GetLength() &&
6069 (mTextRun
->GetFlags() & nsTextFrameUtils::TEXT_HAS_TRAILING_BREAK
))) {
6070 if (preformattedNewline
) {
6071 aData
->ForceBreak(aRenderingContext
);
6073 aData
->OptionallyBreak(aRenderingContext
);
6079 if (start
< flowEndInTextRun
) {
6080 // Check if we have collapsible whitespace at the end
6081 aData
->skipWhitespace
=
6082 IsTrimmableSpace(provider
.GetFragment(),
6083 iter
.ConvertSkippedToOriginal(flowEndInTextRun
- 1),
6088 // XXX Need to do something here to avoid incremental reflow bugs due to
6089 // first-line and first-letter changing min-width
6091 nsTextFrame::AddInlineMinWidth(nsIRenderingContext
*aRenderingContext
,
6092 nsIFrame::InlineMinWidthData
*aData
)
6095 gfxTextRun
* lastTextRun
= nsnull
;
6096 // nsContinuingTextFrame does nothing for AddInlineMinWidth; all text frames
6097 // in the flow are handled right here.
6098 for (f
= this; f
; f
= static_cast<nsTextFrame
*>(f
->GetNextContinuation())) {
6099 // f->mTextRun could be null if we haven't set up textruns yet for f.
6100 // Except in OOM situations, lastTextRun will only be null for the first
6102 if (f
== this || f
->mTextRun
!= lastTextRun
) {
6104 if (aData
->lineContainer
&&
6105 aData
->lineContainer
!= (lc
= FindLineContainer(f
))) {
6106 NS_ASSERTION(f
!= this, "wrong InlineMinWidthData container"
6107 " for first continuation");
6108 aData
->line
= nsnull
;
6109 aData
->lineContainer
= lc
;
6112 // This will process all the text frames that share the same textrun as f.
6113 f
->AddInlineMinWidthForFlow(aRenderingContext
, aData
);
6114 lastTextRun
= f
->mTextRun
;
6119 // XXX this doesn't handle characters shaped by line endings. We need to
6120 // temporarily override the "current line ending" settings.
6122 nsTextFrame::AddInlinePrefWidthForFlow(nsIRenderingContext
*aRenderingContext
,
6123 nsIFrame::InlinePrefWidthData
*aData
)
6125 PRUint32 flowEndInTextRun
;
6126 gfxContext
* ctx
= aRenderingContext
->ThebesContext();
6127 gfxSkipCharsIterator iter
=
6128 EnsureTextRun(ctx
, aData
->lineContainer
, aData
->line
, &flowEndInTextRun
);
6132 // Pass null for the line container. This will disable tab spacing, but that's
6133 // OK since we can't really handle tabs for intrinsic sizing anyway.
6135 const nsStyleText
* textStyle
= GetStyleText();
6136 const nsTextFragment
* frag
= mContent
->GetText();
6137 PropertyProvider
provider(mTextRun
, textStyle
, frag
, this,
6138 iter
, PR_INT32_MAX
, nsnull
, 0);
6140 PRBool collapseWhitespace
= !textStyle
->WhiteSpaceIsSignificant();
6141 PRBool preformatNewlines
= textStyle
->NewlineIsSignificant();
6142 PRBool preformatTabs
= textStyle
->WhiteSpaceIsSignificant();
6143 gfxFloat tabWidth
= -1;
6145 FindStartAfterSkippingWhitespace(&provider
, aData
, textStyle
, &iter
, flowEndInTextRun
);
6147 // XXX Should we consider hyphenation here?
6148 // If newlines and tabs aren't preformatted, nothing to do inside
6149 // the loop so make i skip to the end
6150 PRUint32 loopStart
= (preformatNewlines
|| preformatTabs
) ? start
: flowEndInTextRun
;
6151 for (PRUint32 i
= loopStart
, lineStart
= start
; i
<= flowEndInTextRun
; ++i
) {
6152 PRBool preformattedNewline
= PR_FALSE
;
6153 PRBool preformattedTab
= PR_FALSE
;
6154 if (i
< flowEndInTextRun
) {
6155 // XXXldb Shouldn't we be including the newline as part of the
6156 // segment that it ends rather than part of the segment that it
6158 NS_ASSERTION(preformatNewlines
, "We can't be here unless newlines are hard breaks");
6159 preformattedNewline
= preformatNewlines
&& mTextRun
->GetChar(i
) == '\n';
6160 preformattedTab
= preformatTabs
&& mTextRun
->GetChar(i
) == '\t';
6161 if (!preformattedNewline
&& !preformattedTab
) {
6162 // we needn't break here (and it's not the end of the flow)
6167 if (i
> lineStart
) {
6169 NSToCoordCeilClamped(mTextRun
->GetAdvanceWidth(lineStart
, i
- lineStart
, &provider
));
6170 aData
->currentLine
= NSCoordSaturatingAdd(aData
->currentLine
, width
);
6172 if (collapseWhitespace
) {
6173 PRUint32 trimStart
= GetEndOfTrimmedText(frag
, textStyle
, lineStart
, i
, &iter
);
6174 if (trimStart
== start
) {
6175 // This is *all* trimmable whitespace, so whatever trailingWhitespace
6176 // we saw previously is still trailing...
6177 aData
->trailingWhitespace
+= width
;
6179 // Some non-whitespace so the old trailingWhitespace is no longer trailing
6180 aData
->trailingWhitespace
=
6181 NSToCoordCeilClamped(mTextRun
->GetAdvanceWidth(trimStart
, i
- trimStart
, &provider
));
6184 aData
->trailingWhitespace
= 0;
6188 if (preformattedTab
) {
6189 PropertyProvider::Spacing spacing
;
6190 provider
.GetSpacing(i
, 1, &spacing
);
6191 aData
->currentLine
+= nscoord(spacing
.mBefore
);
6193 AdvanceToNextTab(aData
->currentLine
, this,
6194 mTextRun
, &tabWidth
);
6195 aData
->currentLine
= nscoord(afterTab
+ spacing
.mAfter
);
6197 } else if (preformattedNewline
) {
6198 aData
->ForceBreak(aRenderingContext
);
6203 // Check if we have collapsible whitespace at the end
6204 if (start
< flowEndInTextRun
) {
6205 aData
->skipWhitespace
=
6206 IsTrimmableSpace(provider
.GetFragment(),
6207 iter
.ConvertSkippedToOriginal(flowEndInTextRun
- 1),
6212 // XXX Need to do something here to avoid incremental reflow bugs due to
6213 // first-line and first-letter changing pref-width
6215 nsTextFrame::AddInlinePrefWidth(nsIRenderingContext
*aRenderingContext
,
6216 nsIFrame::InlinePrefWidthData
*aData
)
6219 gfxTextRun
* lastTextRun
= nsnull
;
6220 // nsContinuingTextFrame does nothing for AddInlineMinWidth; all text frames
6221 // in the flow are handled right here.
6222 for (f
= this; f
; f
= static_cast<nsTextFrame
*>(f
->GetNextContinuation())) {
6223 // f->mTextRun could be null if we haven't set up textruns yet for f.
6224 // Except in OOM situations, lastTextRun will only be null for the first
6226 if (f
== this || f
->mTextRun
!= lastTextRun
) {
6228 if (aData
->lineContainer
&&
6229 aData
->lineContainer
!= (lc
= FindLineContainer(f
))) {
6230 NS_ASSERTION(f
!= this, "wrong InlinePrefWidthData container"
6231 " for first continuation");
6232 aData
->line
= nsnull
;
6233 aData
->lineContainer
= lc
;
6236 // This will process all the text frames that share the same textrun as f.
6237 f
->AddInlinePrefWidthForFlow(aRenderingContext
, aData
);
6238 lastTextRun
= f
->mTextRun
;
6243 /* virtual */ nsSize
6244 nsTextFrame::ComputeSize(nsIRenderingContext
*aRenderingContext
,
6245 nsSize aCBSize
, nscoord aAvailableWidth
,
6246 nsSize aMargin
, nsSize aBorder
, nsSize aPadding
,
6249 // Inlines and text don't compute size before reflow.
6250 return nsSize(NS_UNCONSTRAINEDSIZE
, NS_UNCONSTRAINEDSIZE
);
6254 RoundOut(const gfxRect
& aRect
)
6257 r
.x
= NSToCoordFloor(aRect
.X());
6258 r
.y
= NSToCoordFloor(aRect
.Y());
6259 r
.width
= NSToCoordCeil(aRect
.XMost()) - r
.x
;
6260 r
.height
= NSToCoordCeil(aRect
.YMost()) - r
.y
;
6265 nsTextFrame::ComputeTightBounds(gfxContext
* aContext
) const
6267 if ((GetStyleContext()->HasTextDecorations() &&
6268 eCompatibility_NavQuirks
== PresContext()->CompatibilityMode()) ||
6269 (GetStateBits() & TEXT_HYPHEN_BREAK
)) {
6270 // This is conservative, but OK.
6271 return GetVisualOverflowRect();
6274 gfxSkipCharsIterator iter
= const_cast<nsTextFrame
*>(this)->EnsureTextRun();
6276 return nsRect(0, 0, 0, 0);
6278 PropertyProvider
provider(const_cast<nsTextFrame
*>(this), iter
);
6279 // Trim trailing whitespace
6280 provider
.InitializeForDisplay(PR_TRUE
);
6282 gfxTextRun::Metrics metrics
=
6283 mTextRun
->MeasureText(provider
.GetStart().GetSkippedOffset(),
6284 ComputeTransformedLength(provider
),
6285 gfxFont::TIGHT_HINTED_OUTLINE_EXTENTS
,
6286 aContext
, &provider
);
6287 // mAscent should be the same as metrics.mAscent, but it's what we use to
6288 // paint so that's the one we'll use.
6289 return RoundOut(metrics
.mBoundingBox
) + nsPoint(0, mAscent
);
6293 HasSoftHyphenBefore(const nsTextFragment
* aFrag
, gfxTextRun
* aTextRun
,
6294 PRInt32 aStartOffset
, const gfxSkipCharsIterator
& aIter
)
6296 if (!(aTextRun
->GetFlags() & nsTextFrameUtils::TEXT_HAS_SHY
))
6298 gfxSkipCharsIterator iter
= aIter
;
6299 while (iter
.GetOriginalOffset() > aStartOffset
) {
6300 iter
.AdvanceOriginal(-1);
6301 if (!iter
.IsOriginalCharSkipped())
6303 if (aFrag
->CharAt(iter
.GetOriginalOffset()) == CH_SHY
)
6310 nsTextFrame::SetLength(PRInt32 aLength
, nsLineLayout
* aLineLayout
)
6312 mContentLengthHint
= aLength
;
6313 PRInt32 end
= GetContentOffset() + aLength
;
6314 nsTextFrame
* f
= static_cast<nsTextFrame
*>(GetNextInFlow());
6318 // If our end offset is moving, then even if frames are not being pushed or
6319 // pulled, content is moving to or from the next line and the next line
6320 // must be reflowed.
6321 // If the next-continuation is dirty, then we should dirty the next line now
6322 // because we may have skipped doing it if we dirtied it in
6323 // CharacterDataChanged. This is ugly but teaching FrameNeedsReflow
6324 // and ChildIsDirty to handle a range of frames would be worse.
6326 (end
!= f
->mContentOffset
|| (f
->GetStateBits() & NS_FRAME_IS_DIRTY
))) {
6327 aLineLayout
->SetDirtyNextLine();
6330 if (end
< f
->mContentOffset
) {
6331 // Our frame is shrinking. Give the text to our next in flow.
6333 GetStyleText()->WhiteSpaceIsSignificant() &&
6334 HasTerminalNewline() &&
6335 GetParent()->GetType() != nsGkAtoms::letterFrame
) {
6336 // Whatever text we hand to our next-in-flow will end up in a frame all of
6337 // its own, since it ends in a forced linebreak. Might as well just put
6338 // it in a separate frame now. This is important to prevent text run
6339 // churn; if we did not do that, then we'd likely end up rebuilding
6340 // textruns for all our following continuations.
6341 // We skip this optimization when the parent is a first-letter frame
6342 // because it doesn't deal well with more than one child frame.
6343 nsPresContext
* presContext
= PresContext();
6345 nsresult rv
= presContext
->PresShell()->FrameConstructor()->
6346 CreateContinuingFrame(presContext
, this, GetParent(), &newFrame
);
6347 if (NS_SUCCEEDED(rv
)) {
6348 nsTextFrame
* next
= static_cast<nsTextFrame
*>(newFrame
);
6349 nsFrameList
temp(next
, next
);
6350 GetParent()->InsertFrames(nsGkAtoms::nextBidi
, this, temp
);
6355 f
->mContentOffset
= end
;
6356 if (f
->GetTextRun() != mTextRun
) {
6357 ClearTextRun(nsnull
);
6358 f
->ClearTextRun(nsnull
);
6362 // Our frame is growing. Take text from our in-flow(s).
6363 // We can take text from frames in lines beyond just the next line.
6364 // We don't dirty those lines. That's OK, because when we reflow
6365 // our empty next-in-flow, it will take text from its next-in-flow and
6367 while (f
&& f
->mContentOffset
< end
) {
6368 f
->mContentOffset
= end
;
6369 if (f
->GetTextRun() != mTextRun
) {
6370 ClearTextRun(nsnull
);
6371 f
->ClearTextRun(nsnull
);
6373 nsTextFrame
* next
= static_cast<nsTextFrame
*>(f
->GetNextInFlow());
6374 // Note: the "f->GetNextSibling() == next" check below is to restrict
6375 // this optimization to the case where they are on the same child list.
6376 // Otherwise we might remove the only child of a nsFirstLetterFrame
6377 // for example and it can't handle that. See bug 597627 for details.
6378 if (next
&& next
->mContentOffset
<= end
&& f
->GetNextSibling() == next
) {
6379 // |f| is now empty. We may as well remove it, instead of copying all
6380 // the text from |next| into it instead; the latter leads to use
6381 // rebuilding textruns for all following continuations. We have to be
6382 // careful here, though, because some RemoveFrame implementations remove
6383 // and destroy not only the passed-in frame but also all its following
6384 // in-flows (and sometimes all its following continuations in general).
6385 // So we remove |f| from the flow first, to make sure that only |f| is
6387 nsSplittableFrame::RemoveFromFlow(f
);
6388 f
->GetParent()->RemoveFrame(nsGkAtoms::nextBidi
, f
);
6395 PRInt32 iterations
= 0;
6396 while (f
&& iterations
< 10) {
6397 f
->GetContentLength(); // Assert if negative length
6398 f
= static_cast<nsTextFrame
*>(f
->GetNextContinuation());
6403 while (f
&& iterations
< 10) {
6404 f
->GetContentLength(); // Assert if negative length
6405 f
= static_cast<nsTextFrame
*>(f
->GetPrevContinuation());
6412 nsTextFrame::IsFloatingFirstLetterChild()
6414 if (!(GetStateBits() & TEXT_FIRST_LETTER
))
6416 nsIFrame
* frame
= GetParent();
6417 if (!frame
|| frame
->GetType() != nsGkAtoms::letterFrame
)
6419 return frame
->GetStyleDisplay()->IsFloating();
6422 struct NewlineProperty
{
6423 PRInt32 mStartOffset
;
6424 // The offset of the first \n after mStartOffset, or -1 if there is none
6425 PRInt32 mNewlineOffset
;
6427 static void Destroy(void* aObject
, nsIAtom
* aPropertyName
,
6428 void* aPropertyValue
, void* aData
)
6430 delete static_cast<NewlineProperty
*>(aPropertyValue
);
6435 nsTextFrame::Reflow(nsPresContext
* aPresContext
,
6436 nsHTMLReflowMetrics
& aMetrics
,
6437 const nsHTMLReflowState
& aReflowState
,
6438 nsReflowStatus
& aStatus
)
6440 DO_GLOBAL_REFLOW_COUNT("nsTextFrame");
6441 DISPLAY_REFLOW(aPresContext
, this, aReflowState
, aMetrics
, aStatus
);
6443 // XXX If there's no line layout, we shouldn't even have created this
6444 // frame. This may happen if, for example, this is text inside a table
6445 // but not inside a cell. For now, just don't reflow.
6446 if (!aReflowState
.mLineLayout
) {
6447 ClearMetrics(aMetrics
);
6448 aStatus
= NS_FRAME_COMPLETE
;
6452 ReflowText(*aReflowState
.mLineLayout
, aReflowState
.availableWidth
,
6453 aReflowState
.rendContext
, aReflowState
.mFlags
.mBlinks
,
6456 NS_FRAME_SET_TRUNCATION(aStatus
, aReflowState
, aMetrics
);
6461 nsTextFrame::ReflowText(nsLineLayout
& aLineLayout
, nscoord aAvailableWidth
,
6462 nsIRenderingContext
* aRenderingContext
,
6463 PRBool aShouldBlink
,
6464 nsHTMLReflowMetrics
& aMetrics
,
6465 nsReflowStatus
& aStatus
)
6469 printf(": BeginReflow: availableWidth=%d\n", aAvailableWidth
);
6472 nsPresContext
* presContext
= PresContext();
6474 /////////////////////////////////////////////////////////////////////
6475 // Set up flags and clear out state
6476 /////////////////////////////////////////////////////////////////////
6478 // Clear out the reflow state flags in mState (without destroying
6479 // the TEXT_BLINK_ON bit). We also clear the whitespace flags because this
6480 // can change whether the frame maps whitespace-only text or not.
6481 RemoveStateBits(TEXT_REFLOW_FLAGS
| TEXT_WHITESPACE_FLAGS
);
6483 // Temporarily map all possible content while we construct our new textrun.
6484 // so that when doing reflow our styles prevail over any part of the
6485 // textrun we look at. Note that next-in-flows may be mapping the same
6486 // content; gfxTextRun construction logic will ensure that we take priority.
6487 PRInt32 maxContentLength
= GetInFlowContentLength();
6489 // We don't need to reflow if there is no content.
6490 if (!maxContentLength
) {
6491 ClearMetrics(aMetrics
);
6492 aStatus
= NS_FRAME_COMPLETE
;
6497 if (0 == (mState
& TEXT_BLINK_ON
)) {
6498 mState
|= TEXT_BLINK_ON
;
6499 nsBlinkTimer::AddBlinkFrame(presContext
, this);
6503 if (0 != (mState
& TEXT_BLINK_ON
)) {
6504 mState
&= ~TEXT_BLINK_ON
;
6505 nsBlinkTimer::RemoveBlinkFrame(this);
6509 const nsStyleText
* textStyle
= GetStyleText();
6511 PRBool atStartOfLine
= aLineLayout
.LineAtStart();
6512 if (atStartOfLine
) {
6513 AddStateBits(TEXT_START_OF_LINE
);
6516 PRUint32 flowEndInTextRun
;
6517 nsIFrame
* lineContainer
= aLineLayout
.GetLineContainerFrame();
6518 gfxContext
* ctx
= aRenderingContext
->ThebesContext();
6519 const nsTextFragment
* frag
= mContent
->GetText();
6521 // DOM offsets of the text range we need to measure, after trimming
6522 // whitespace, restricting to first-letter, and restricting preformatted text
6523 // to nearest newline
6524 PRInt32 length
= maxContentLength
;
6525 PRInt32 offset
= GetContentOffset();
6527 // Restrict preformatted text to the nearest newline
6528 PRInt32 newLineOffset
= -1; // this will be -1 or a content offset
6529 PRInt32 contentNewLineOffset
= -1;
6530 // Pointer to the nsGkAtoms::newline set on this frame's element
6531 NewlineProperty
* cachedNewlineOffset
= nsnull
;
6532 if (textStyle
->NewlineIsSignificant()) {
6533 cachedNewlineOffset
=
6534 static_cast<NewlineProperty
*>(mContent
->GetProperty(nsGkAtoms::newline
));
6535 if (cachedNewlineOffset
&& cachedNewlineOffset
->mStartOffset
<= offset
&&
6536 (cachedNewlineOffset
->mNewlineOffset
== -1 ||
6537 cachedNewlineOffset
->mNewlineOffset
>= offset
)) {
6538 contentNewLineOffset
= cachedNewlineOffset
->mNewlineOffset
;
6540 contentNewLineOffset
= FindChar(frag
, offset
,
6541 mContent
->TextLength() - offset
, '\n');
6543 if (contentNewLineOffset
< offset
+ length
) {
6545 The new line offset could be outside this frame if the frame has been
6546 split by bidi resolution. In that case we won't use it in this reflow
6547 (newLineOffset will remain -1), but we will still cache it in mContent
6549 newLineOffset
= contentNewLineOffset
;
6551 if (newLineOffset
>= 0) {
6552 length
= newLineOffset
+ 1 - offset
;
6555 if (atStartOfLine
&& !textStyle
->WhiteSpaceIsSignificant()) {
6556 // Skip leading whitespace. Make sure we don't skip a 'pre-line'
6557 // newline if there is one.
6558 PRInt32 skipLength
= newLineOffset
>= 0 ? length
- 1 : length
;
6559 PRInt32 whitespaceCount
=
6560 GetTrimmableWhitespaceCount(frag
, offset
, skipLength
, 1);
6561 offset
+= whitespaceCount
;
6562 length
-= whitespaceCount
;
6565 PRBool completedFirstLetter
= PR_FALSE
;
6566 // Layout dependent styles are a problem because we need to reconstruct
6567 // the gfxTextRun based on our layout.
6568 if (aLineLayout
.GetInFirstLetter() || aLineLayout
.GetInFirstLine()) {
6569 SetLength(maxContentLength
, &aLineLayout
);
6571 if (aLineLayout
.GetInFirstLetter()) {
6572 // floating first-letter boundaries are significant in textrun
6573 // construction, so clear the textrun out every time we hit a first-letter
6574 // and have changed our length (which controls the first-letter boundary)
6575 ClearTextRun(nsnull
);
6576 // Find the length of the first-letter. We need a textrun for this.
6577 gfxSkipCharsIterator iter
=
6578 EnsureTextRun(ctx
, lineContainer
, aLineLayout
.GetLine(), &flowEndInTextRun
);
6581 PRInt32 firstLetterLength
= length
;
6582 if (aLineLayout
.GetFirstLetterStyleOK()) {
6583 completedFirstLetter
=
6584 FindFirstLetterRange(frag
, mTextRun
, offset
, iter
, &firstLetterLength
);
6585 if (newLineOffset
>= 0) {
6586 // Don't allow a preformatted newline to be part of a first-letter.
6587 firstLetterLength
= NS_MIN(firstLetterLength
, length
- 1);
6589 // There is no text to be consumed by the first-letter before the
6590 // preformatted newline. Note that the first letter is therefore
6591 // complete (FindFirstLetterRange will have returned false).
6592 completedFirstLetter
= PR_TRUE
;
6596 // We're in a first-letter frame's first in flow, so if there
6597 // was a first-letter, we'd be it. However, for one reason
6598 // or another (e.g., preformatted line break before this text),
6599 // we're not actually supposed to have first-letter style. So
6600 // just make a zero-length first-letter.
6601 firstLetterLength
= 0;
6602 completedFirstLetter
= PR_TRUE
;
6604 length
= firstLetterLength
;
6606 AddStateBits(TEXT_FIRST_LETTER
);
6608 // Change this frame's length to the first-letter length right now
6609 // so that when we rebuild the textrun it will be built with the
6610 // right first-letter boundary
6611 SetLength(offset
+ length
- GetContentOffset(), &aLineLayout
);
6612 // Ensure that the textrun will be rebuilt
6613 ClearTextRun(nsnull
);
6618 gfxSkipCharsIterator iter
=
6619 EnsureTextRun(ctx
, lineContainer
, aLineLayout
.GetLine(), &flowEndInTextRun
);
6621 if (mTextRun
&& iter
.GetOriginalEnd() < offset
+ length
) {
6622 // The textrun does not map enough text for this frame. This can happen
6623 // when the textrun was ended in the middle of a text node because a
6624 // preformatted newline was encountered, and prev-in-flow frames have
6625 // consumed all the text of the textrun. We need a new textrun.
6626 ClearTextRun(nsnull
);
6627 iter
= EnsureTextRun(ctx
, lineContainer
,
6628 aLineLayout
.GetLine(), &flowEndInTextRun
);
6632 ClearMetrics(aMetrics
);
6633 aStatus
= NS_FRAME_COMPLETE
;
6637 NS_ASSERTION(gfxSkipCharsIterator(iter
).ConvertOriginalToSkipped(offset
+ length
)
6638 <= mTextRun
->GetLength(),
6639 "Text run does not map enough text for our reflow");
6641 /////////////////////////////////////////////////////////////////////
6642 // See how much text should belong to this text frame, and measure it
6643 /////////////////////////////////////////////////////////////////////
6645 iter
.SetOriginalOffset(offset
);
6646 nscoord xOffsetForTabs
= (mTextRun
->GetFlags() & nsTextFrameUtils::TEXT_HAS_TAB
) ?
6647 (aLineLayout
.GetCurrentFrameXDistanceFromBlock() -
6648 lineContainer
->GetUsedBorderAndPadding().left
)
6650 PropertyProvider
provider(mTextRun
, textStyle
, frag
, this, iter
, length
,
6651 lineContainer
, xOffsetForTabs
);
6653 PRUint32 transformedOffset
= provider
.GetStart().GetSkippedOffset();
6655 // The metrics for the text go in here
6656 gfxTextRun::Metrics textMetrics
;
6657 gfxFont::BoundingBoxType boundingBoxType
= IsFloatingFirstLetterChild() ?
6658 gfxFont::TIGHT_HINTED_OUTLINE_EXTENTS
:
6659 gfxFont::LOOSE_INK_EXTENTS
;
6661 NS_ASSERTION(!(NS_REFLOW_CALC_BOUNDING_METRICS
& aMetrics
.mFlags
),
6662 "We shouldn't be passed NS_REFLOW_CALC_BOUNDING_METRICS anymore");
6665 PRInt32 limitLength
= length
;
6666 PRInt32 forceBreak
= aLineLayout
.GetForcedBreakPosition(mContent
);
6667 PRBool forceBreakAfter
= PR_FALSE
;
6668 if (forceBreak
>= offset
+ length
) {
6669 forceBreakAfter
= forceBreak
== offset
+ length
;
6670 // The break is not within the text considered for this textframe.
6673 if (forceBreak
>= 0) {
6674 limitLength
= forceBreak
- offset
;
6675 NS_ASSERTION(limitLength
>= 0, "Weird break found!");
6677 // This is the heart of text reflow right here! We don't know where
6678 // to break, so we need to see how much text fits in the available width.
6679 PRUint32 transformedLength
;
6680 if (offset
+ limitLength
>= PRInt32(frag
->GetLength())) {
6681 NS_ASSERTION(offset
+ limitLength
== PRInt32(frag
->GetLength()),
6682 "Content offset/length out of bounds");
6683 NS_ASSERTION(flowEndInTextRun
>= transformedOffset
,
6684 "Negative flow length?");
6685 transformedLength
= flowEndInTextRun
- transformedOffset
;
6687 // we're not looking at all the content, so we need to compute the
6688 // length of the transformed substring we're looking at
6689 gfxSkipCharsIterator
iter(provider
.GetStart());
6690 iter
.SetOriginalOffset(offset
+ limitLength
);
6691 transformedLength
= iter
.GetSkippedOffset() - transformedOffset
;
6693 PRUint32 transformedLastBreak
= 0;
6694 PRBool usedHyphenation
;
6695 gfxFloat trimmedWidth
= 0;
6696 gfxFloat availWidth
= aAvailableWidth
;
6697 PRBool canTrimTrailingWhitespace
= !textStyle
->WhiteSpaceIsSignificant();
6698 PRInt32 unusedOffset
;
6699 gfxBreakPriority breakPriority
;
6700 aLineLayout
.GetLastOptionalBreakPosition(&unusedOffset
, &breakPriority
);
6701 PRUint32 transformedCharsFit
=
6702 mTextRun
->BreakAndMeasureText(transformedOffset
, transformedLength
,
6703 (GetStateBits() & TEXT_START_OF_LINE
) != 0,
6705 &provider
, !aLineLayout
.LineIsBreakable(),
6706 canTrimTrailingWhitespace
? &trimmedWidth
: nsnull
,
6707 &textMetrics
, boundingBoxType
, ctx
,
6708 &usedHyphenation
, &transformedLastBreak
,
6709 textStyle
->WordCanWrap(), &breakPriority
);
6710 if (!length
&& !textMetrics
.mAscent
&& !textMetrics
.mDescent
) {
6711 // If we're measuring a zero-length piece of text, update
6712 // the height manually.
6713 nsIFontMetrics
* fm
= provider
.GetFontMetrics();
6715 nscoord ascent
, descent
;
6716 fm
->GetMaxAscent(ascent
);
6717 fm
->GetMaxDescent(descent
);
6718 textMetrics
.mAscent
= gfxFloat(ascent
);
6719 textMetrics
.mDescent
= gfxFloat(descent
);
6722 // The "end" iterator points to the first character after the string mapped
6723 // by this frame. Basically, its original-string offset is offset+charsFit
6724 // after we've computed charsFit.
6725 gfxSkipCharsIterator
end(provider
.GetEndHint());
6726 end
.SetSkippedOffset(transformedOffset
+ transformedCharsFit
);
6727 PRInt32 charsFit
= end
.GetOriginalOffset() - offset
;
6728 if (offset
+ charsFit
== newLineOffset
) {
6729 // We broke before a trailing preformatted '\n'. The newline should
6730 // be assigned to this frame. Note that newLineOffset will be -1 if
6731 // there was no preformatted newline, so we wouldn't get here in that
6735 // That might have taken us beyond our assigned content range (because
6736 // we might have advanced over some skipped chars that extend outside
6737 // this frame), so get back in.
6738 PRInt32 lastBreak
= -1;
6739 if (charsFit
>= limitLength
) {
6740 charsFit
= limitLength
;
6741 if (transformedLastBreak
!= PR_UINT32_MAX
) {
6742 // lastBreak is needed.
6743 // This may set lastBreak greater than 'length', but that's OK
6744 lastBreak
= end
.ConvertSkippedToOriginal(transformedOffset
+ transformedLastBreak
);
6746 end
.SetOriginalOffset(offset
+ charsFit
);
6747 // If we were forced to fit, and the break position is after a soft hyphen,
6748 // note that this is a hyphenation break.
6749 if ((forceBreak
>= 0 || forceBreakAfter
) &&
6750 HasSoftHyphenBefore(frag
, mTextRun
, offset
, end
)) {
6751 usedHyphenation
= PR_TRUE
;
6754 if (usedHyphenation
) {
6755 // Fix up metrics to include hyphen
6756 AddHyphenToMetrics(this, mTextRun
, &textMetrics
, boundingBoxType
, ctx
);
6757 AddStateBits(TEXT_HYPHEN_BREAK
| TEXT_HAS_NONCOLLAPSED_CHARACTERS
);
6760 gfxFloat trimmableWidth
= 0;
6761 PRBool brokeText
= forceBreak
>= 0 || transformedCharsFit
< transformedLength
;
6762 if (canTrimTrailingWhitespace
) {
6763 // Optimization: if we trimmed trailing whitespace, and we can be sure
6764 // this frame will be at the end of the line, then leave it trimmed off.
6765 // Otherwise we have to undo the trimming, in case we're not at the end of
6766 // the line. (If we actually do end up at the end of the line, we'll have
6767 // to trim it off again in TrimTrailingWhiteSpace, and we'd like to avoid
6768 // having to re-do it.)
6770 // We're definitely going to break so our trailing whitespace should
6771 // definitely be timmed. Record that we've already done it.
6772 AddStateBits(TEXT_TRIMMED_TRAILING_WHITESPACE
);
6774 // We might not be at the end of the line. (Note that even if this frame
6775 // ends in breakable whitespace, it might not be at the end of the line
6776 // because it might be followed by breakable, but preformatted, whitespace.)
6777 // Undo the trimming.
6778 textMetrics
.mAdvanceWidth
+= trimmedWidth
;
6779 trimmableWidth
= trimmedWidth
;
6780 if (mTextRun
->IsRightToLeft()) {
6781 // Space comes before text, so the bounding box is moved to the
6782 // right by trimmdWidth
6783 textMetrics
.mBoundingBox
.MoveBy(gfxPoint(trimmedWidth
, 0));
6788 if (!brokeText
&& lastBreak
>= 0) {
6789 // Since everything fit and no break was forced,
6790 // record the last break opportunity
6791 NS_ASSERTION(textMetrics
.mAdvanceWidth
- trimmableWidth
<= aAvailableWidth
,
6792 "If the text doesn't fit, and we have a break opportunity, why didn't MeasureText use it?");
6793 aLineLayout
.NotifyOptionalBreakPosition(mContent
, lastBreak
, PR_TRUE
, breakPriority
);
6796 PRInt32 contentLength
= offset
+ charsFit
- GetContentOffset();
6798 /////////////////////////////////////////////////////////////////////
6799 // Compute output metrics
6800 /////////////////////////////////////////////////////////////////////
6802 // first-letter frames should use the tight bounding box metrics for ascent/descent
6803 // for good drop-cap effects
6804 if (GetStateBits() & TEXT_FIRST_LETTER
) {
6805 textMetrics
.mAscent
= NS_MAX(gfxFloat(0.0), -textMetrics
.mBoundingBox
.Y());
6806 textMetrics
.mDescent
= NS_MAX(gfxFloat(0.0), textMetrics
.mBoundingBox
.YMost());
6809 // Setup metrics for caller
6810 // Disallow negative widths
6811 aMetrics
.width
= NSToCoordCeil(NS_MAX(gfxFloat(0.0), textMetrics
.mAdvanceWidth
));
6813 if (completedFirstLetter
&& transformedCharsFit
== 0 && !usedHyphenation
) {
6814 aMetrics
.ascent
= 0;
6815 aMetrics
.height
= 0;
6816 } else if (boundingBoxType
!= gfxFont::LOOSE_INK_EXTENTS
) {
6817 // Use actual text metrics for floating first letter frame.
6818 aMetrics
.ascent
= NSToCoordCeil(textMetrics
.mAscent
);
6819 aMetrics
.height
= aMetrics
.ascent
+ NSToCoordCeil(textMetrics
.mDescent
);
6821 // Otherwise, ascent should contain the overline drawable area.
6822 // And also descent should contain the underline drawable area.
6823 // nsIFontMetrics::GetMaxAscent/GetMaxDescent contains them.
6824 nscoord fontAscent
, fontDescent
;
6825 nsIFontMetrics
* fm
= provider
.GetFontMetrics();
6826 fm
->GetMaxAscent(fontAscent
);
6827 fm
->GetMaxDescent(fontDescent
);
6828 aMetrics
.ascent
= NS_MAX(NSToCoordCeil(textMetrics
.mAscent
), fontAscent
);
6829 nscoord descent
= NS_MAX(NSToCoordCeil(textMetrics
.mDescent
), fontDescent
);
6830 aMetrics
.height
= aMetrics
.ascent
+ descent
;
6833 NS_ASSERTION(aMetrics
.ascent
>= 0, "Negative ascent???");
6834 NS_ASSERTION(aMetrics
.height
- aMetrics
.ascent
>= 0, "Negative descent???");
6836 mAscent
= aMetrics
.ascent
;
6838 // Handle text that runs outside its normal bounds.
6839 nsRect boundingBox
= RoundOut(textMetrics
.mBoundingBox
) + nsPoint(0, mAscent
);
6840 aMetrics
.SetOverflowAreasToDesiredBounds();
6841 aMetrics
.VisualOverflow().UnionRect(aMetrics
.VisualOverflow(), boundingBox
);
6843 UnionTextDecorationOverflow(presContext
, provider
, &aMetrics
.VisualOverflow());
6845 /////////////////////////////////////////////////////////////////////
6846 // Clean up, update state
6847 /////////////////////////////////////////////////////////////////////
6849 // If all our characters are discarded or collapsed, then trimmable width
6850 // from the last textframe should be preserved. Otherwise the trimmable width
6851 // from this textframe overrides. (Currently in CSS trimmable width can be
6852 // at most one space so there's no way for trimmable width from a previous
6853 // frame to accumulate with trimmable width from this frame.)
6854 if (transformedCharsFit
> 0) {
6855 aLineLayout
.SetTrimmableWidth(NSToCoordFloor(trimmableWidth
));
6856 AddStateBits(TEXT_HAS_NONCOLLAPSED_CHARACTERS
);
6858 if (charsFit
> 0 && charsFit
== length
&&
6859 HasSoftHyphenBefore(frag
, mTextRun
, offset
, end
)) {
6860 // Record a potential break after final soft hyphen
6861 aLineLayout
.NotifyOptionalBreakPosition(mContent
, offset
+ length
,
6862 textMetrics
.mAdvanceWidth
+ provider
.GetHyphenWidth() <= availWidth
,
6865 PRBool breakAfter
= forceBreakAfter
;
6866 // length == 0 means either the text is empty or it's all collapsed away
6867 PRBool emptyTextAtStartOfLine
= atStartOfLine
&& length
== 0;
6868 if (!breakAfter
&& charsFit
== length
&& !emptyTextAtStartOfLine
&&
6869 transformedOffset
+ transformedLength
== mTextRun
->GetLength() &&
6870 (mTextRun
->GetFlags() & nsTextFrameUtils::TEXT_HAS_TRAILING_BREAK
)) {
6871 // We placed all the text in the textrun and we have a break opportunity at
6872 // the end of the textrun. We need to record it because the following
6873 // content may not care about nsLineBreaker.
6875 // Note that because we didn't break, we can be sure that (thanks to the
6876 // code up above) textMetrics.mAdvanceWidth includes the width of any
6877 // trailing whitespace. So we need to subtract trimmableWidth here
6878 // because if we did break at this point, that much width would be trimmed.
6879 if (textMetrics
.mAdvanceWidth
- trimmableWidth
> availWidth
) {
6880 breakAfter
= PR_TRUE
;
6882 aLineLayout
.NotifyOptionalBreakPosition(mContent
, offset
+ length
,
6883 PR_TRUE
, eNormalBreak
);
6887 // Compute reflow status
6888 aStatus
= contentLength
== maxContentLength
6889 ? NS_FRAME_COMPLETE
: NS_FRAME_NOT_COMPLETE
;
6891 if (charsFit
== 0 && length
> 0) {
6892 // Couldn't place any text
6893 aStatus
= NS_INLINE_LINE_BREAK_BEFORE();
6894 } else if (contentLength
> 0 && mContentOffset
+ contentLength
- 1 == newLineOffset
) {
6896 aStatus
= NS_INLINE_LINE_BREAK_AFTER(aStatus
);
6897 aLineLayout
.SetLineEndsInBR(PR_TRUE
);
6898 } else if (breakAfter
) {
6899 aStatus
= NS_INLINE_LINE_BREAK_AFTER(aStatus
);
6901 if (completedFirstLetter
) {
6902 aLineLayout
.SetFirstLetterStyleOK(PR_FALSE
);
6903 aStatus
|= NS_INLINE_BREAK_FIRST_LETTER_COMPLETE
;
6906 // Updated the cached NewlineProperty, or delete it.
6907 if (contentLength
< maxContentLength
&&
6908 textStyle
->NewlineIsSignificant() &&
6909 (contentNewLineOffset
< 0 ||
6910 mContentOffset
+ contentLength
<= contentNewLineOffset
)) {
6911 if (!cachedNewlineOffset
) {
6912 cachedNewlineOffset
= new NewlineProperty
;
6913 if (cachedNewlineOffset
) {
6914 if (NS_FAILED(mContent
->SetProperty(nsGkAtoms::newline
, cachedNewlineOffset
,
6915 NewlineProperty::Destroy
))) {
6916 delete cachedNewlineOffset
;
6917 cachedNewlineOffset
= nsnull
;
6921 if (cachedNewlineOffset
) {
6922 cachedNewlineOffset
->mStartOffset
= offset
;
6923 cachedNewlineOffset
->mNewlineOffset
= contentNewLineOffset
;
6925 } else if (cachedNewlineOffset
) {
6926 mContent
->DeleteProperty(nsGkAtoms::newline
);
6929 // Compute space and letter counts for justification, if required
6930 if (!textStyle
->WhiteSpaceIsSignificant() &&
6931 lineContainer
->GetStyleText()->mTextAlign
== NS_STYLE_TEXT_ALIGN_JUSTIFY
) {
6932 AddStateBits(TEXT_JUSTIFICATION_ENABLED
); // This will include a space for trailing whitespace, if any is present.
6933 // This is corrected for in nsLineLayout::TrimWhiteSpaceIn.
6934 PRInt32 numJustifiableCharacters
=
6935 provider
.ComputeJustifiableCharacters(offset
, charsFit
);
6937 NS_ASSERTION(numJustifiableCharacters
<= charsFit
,
6938 "Bad justifiable character count");
6939 aLineLayout
.SetTextJustificationWeights(numJustifiableCharacters
,
6940 charsFit
- numJustifiableCharacters
);
6943 SetLength(contentLength
, &aLineLayout
);
6945 if (mContent
->HasFlag(NS_TEXT_IN_SELECTION
)) {
6946 SelectionDetails
* details
= GetSelectionDetails();
6948 AddStateBits(NS_FRAME_SELECTED_CONTENT
);
6949 DestroySelectionDetails(details
);
6951 RemoveStateBits(NS_FRAME_SELECTED_CONTENT
);
6955 Invalidate(aMetrics
.VisualOverflow());
6959 printf(": desiredSize=%d,%d(b=%d) status=%x\n",
6960 aMetrics
.width
, aMetrics
.height
, aMetrics
.ascent
,
6964 #ifdef ACCESSIBILITY
6965 // Schedule the update of accessible tree when rendered text might be changed.
6966 nsAccessibilityService
* accService
= nsIPresShell::AccService();
6968 accService
->UpdateText(presContext
->PresShell(), mContent
);
6973 /* virtual */ PRBool
6974 nsTextFrame::CanContinueTextRun() const
6976 // We can continue a text run through a text frame
6980 nsTextFrame::TrimOutput
6981 nsTextFrame::TrimTrailingWhiteSpace(nsIRenderingContext
* aRC
)
6984 result
.mChanged
= PR_FALSE
;
6985 result
.mLastCharIsJustifiable
= PR_FALSE
;
6986 result
.mDeltaWidth
= 0;
6988 AddStateBits(TEXT_END_OF_LINE
);
6990 PRInt32 contentLength
= GetContentLength();
6994 gfxContext
* ctx
= aRC
->ThebesContext();
6995 gfxSkipCharsIterator start
= EnsureTextRun(ctx
);
6996 NS_ENSURE_TRUE(mTextRun
, result
);
6998 PRUint32 trimmedStart
= start
.GetSkippedOffset();
7000 const nsTextFragment
* frag
= mContent
->GetText();
7001 TrimmedOffsets trimmed
= GetTrimmedOffsets(frag
, PR_TRUE
);
7002 gfxSkipCharsIterator trimmedEndIter
= start
;
7003 const nsStyleText
* textStyle
= GetStyleText();
7005 PRUint32 trimmedEnd
= trimmedEndIter
.ConvertOriginalToSkipped(trimmed
.GetEnd());
7007 if (GetStateBits() & TEXT_TRIMMED_TRAILING_WHITESPACE
) {
7008 // We pre-trimmed this frame, so the last character is justifiable
7009 result
.mLastCharIsJustifiable
= PR_TRUE
;
7010 } else if (trimmed
.GetEnd() < GetContentEnd()) {
7011 gfxSkipCharsIterator end
= trimmedEndIter
;
7012 PRUint32 endOffset
= end
.ConvertOriginalToSkipped(GetContentOffset() + contentLength
);
7013 if (trimmedEnd
< endOffset
) {
7014 // We can't be dealing with tabs here ... they wouldn't be trimmed. So it's
7015 // OK to pass null for the line container.
7016 PropertyProvider
provider(mTextRun
, textStyle
, frag
, this, start
, contentLength
,
7018 delta
= mTextRun
->GetAdvanceWidth(trimmedEnd
, endOffset
- trimmedEnd
, &provider
);
7019 // non-compressed whitespace being skipped at end of line -> justifiable
7020 // XXX should we actually *count* justifiable characters that should be
7021 // removed from the overall count? I think so...
7022 result
.mLastCharIsJustifiable
= PR_TRUE
;
7023 result
.mChanged
= PR_TRUE
;
7027 if (!result
.mLastCharIsJustifiable
&&
7028 (GetStateBits() & TEXT_JUSTIFICATION_ENABLED
)) {
7029 // Check if any character in the last cluster is justifiable
7030 PropertyProvider
provider(mTextRun
, textStyle
, frag
, this, start
, contentLength
,
7032 PRBool isCJK
= IsChineseOrJapanese(this);
7033 gfxSkipCharsIterator
justificationStart(start
), justificationEnd(trimmedEndIter
);
7034 provider
.FindJustificationRange(&justificationStart
, &justificationEnd
);
7037 for (i
= justificationEnd
.GetOriginalOffset(); i
< trimmed
.GetEnd(); ++i
) {
7038 if (IsJustifiableCharacter(frag
, i
, isCJK
)) {
7039 result
.mLastCharIsJustifiable
= PR_TRUE
;
7044 gfxFloat advanceDelta
;
7045 mTextRun
->SetLineBreaks(trimmedStart
, trimmedEnd
- trimmedStart
,
7046 (GetStateBits() & TEXT_START_OF_LINE
) != 0, PR_TRUE
,
7047 &advanceDelta
, ctx
);
7048 if (advanceDelta
!= 0) {
7049 result
.mChanged
= PR_TRUE
;
7052 // aDeltaWidth is *subtracted* from our width.
7053 // If advanceDelta is positive then setting the line break made us longer,
7054 // so aDeltaWidth could go negative.
7055 result
.mDeltaWidth
= NSToCoordFloor(delta
- advanceDelta
);
7056 // If aDeltaWidth goes negative, that means this frame might not actually fit
7057 // anymore!!! We need higher level line layout to recover somehow.
7058 // If it's because the frame has a soft hyphen that is now being displayed,
7059 // this should actually be OK, because our reflow recorded the break
7060 // opportunity that allowed the soft hyphen to be used, and we wouldn't
7061 // have recorded the opportunity unless the hyphen fit (or was the first
7062 // opportunity on the line).
7063 // Otherwise this can/ really only happen when we have glyphs with special
7064 // shapes at the end of lines, I think. Breaking inside a kerning pair won't
7065 // do it because that would mean we broke inside this textrun, and
7066 // BreakAndMeasureText should make sure the resulting shaped substring fits.
7067 // Maybe if we passed a maxTextLength? But that only happens at direction
7068 // changes (so we wouldn't kern across the boundary) or for first-letter
7069 // (which always fits because it starts the line!).
7070 NS_WARN_IF_FALSE(result
.mDeltaWidth
>= 0,
7071 "Negative deltawidth, something odd is happening");
7075 printf(": trim => %d\n", result
.mDeltaWidth
);
7081 nsTextFrame::RecomputeOverflow()
7083 nsRect
bounds(nsPoint(0, 0), GetSize());
7084 nsOverflowAreas
result(bounds
, bounds
);
7086 gfxSkipCharsIterator iter
= EnsureTextRun();
7090 PropertyProvider
provider(this, iter
);
7091 provider
.InitializeForDisplay(PR_TRUE
);
7093 gfxTextRun::Metrics textMetrics
=
7094 mTextRun
->MeasureText(provider
.GetStart().GetSkippedOffset(),
7095 ComputeTransformedLength(provider
),
7096 gfxFont::LOOSE_INK_EXTENTS
, nsnull
,
7099 nsRect
&vis
= result
.VisualOverflow();
7100 vis
.UnionRect(vis
, RoundOut(textMetrics
.mBoundingBox
) + nsPoint(0, mAscent
));
7102 UnionTextDecorationOverflow(PresContext(), provider
, &vis
);
7107 static PRUnichar
TransformChar(const nsStyleText
* aStyle
, gfxTextRun
* aTextRun
,
7108 PRUint32 aSkippedOffset
, PRUnichar aChar
)
7110 if (aChar
== '\n') {
7111 return aStyle
->NewlineIsSignificant() ? aChar
: ' ';
7113 switch (aStyle
->mTextTransform
) {
7114 case NS_STYLE_TEXT_TRANSFORM_LOWERCASE
:
7115 aChar
= ToLowerCase(aChar
);
7117 case NS_STYLE_TEXT_TRANSFORM_UPPERCASE
:
7118 aChar
= ToUpperCase(aChar
);
7120 case NS_STYLE_TEXT_TRANSFORM_CAPITALIZE
:
7121 if (aTextRun
->CanBreakLineBefore(aSkippedOffset
)) {
7122 aChar
= ToTitleCase(aChar
);
7130 nsresult
nsTextFrame::GetRenderedText(nsAString
* aAppendToString
,
7131 gfxSkipChars
* aSkipChars
,
7132 gfxSkipCharsIterator
* aSkipIter
,
7133 PRUint32 aSkippedStartOffset
,
7134 PRUint32 aSkippedMaxLength
)
7136 // The handling of aSkippedStartOffset and aSkippedMaxLength could be more efficient...
7137 gfxSkipCharsBuilder skipCharsBuilder
;
7138 nsTextFrame
* textFrame
;
7139 const nsTextFragment
* textFrag
= mContent
->GetText();
7140 PRUint32 keptCharsLength
= 0;
7141 PRUint32 validCharsLength
= 0;
7143 // Build skipChars and copy text, for each text frame in this continuation block
7144 for (textFrame
= this; textFrame
;
7145 textFrame
= static_cast<nsTextFrame
*>(textFrame
->GetNextContinuation())) {
7146 // For each text frame continuation in this block ...
7148 // Ensure the text run and grab the gfxSkipCharsIterator for it
7149 gfxSkipCharsIterator iter
= textFrame
->EnsureTextRun();
7150 if (!textFrame
->mTextRun
)
7151 return NS_ERROR_FAILURE
;
7153 // Skip to the start of the text run, past ignored chars at start of line
7154 // XXX In the future we may decide to trim extra spaces before a hard line
7155 // break, in which case we need to accurately detect those sitations and
7156 // call GetTrimmedOffsets() with PR_TRUE to trim whitespace at the line's end
7157 TrimmedOffsets trimmedContentOffsets
= textFrame
->GetTrimmedOffsets(textFrag
, PR_FALSE
);
7158 PRInt32 startOfLineSkipChars
= trimmedContentOffsets
.mStart
- textFrame
->mContentOffset
;
7159 if (startOfLineSkipChars
> 0) {
7160 skipCharsBuilder
.SkipChars(startOfLineSkipChars
);
7161 iter
.SetOriginalOffset(trimmedContentOffsets
.mStart
);
7164 // Keep and copy the appropriate chars withing the caller's requested range
7165 const nsStyleText
* textStyle
= textFrame
->GetStyleText();
7166 while (iter
.GetOriginalOffset() < trimmedContentOffsets
.GetEnd() &&
7167 keptCharsLength
< aSkippedMaxLength
) {
7168 // For each original char from content text
7169 if (iter
.IsOriginalCharSkipped() || ++validCharsLength
<= aSkippedStartOffset
) {
7170 skipCharsBuilder
.SkipChar();
7173 skipCharsBuilder
.KeepChar();
7174 if (aAppendToString
) {
7175 aAppendToString
->Append(
7176 TransformChar(textStyle
, textFrame
->mTextRun
, iter
.GetSkippedOffset(),
7177 textFrag
->CharAt(iter
.GetOriginalOffset())));
7180 iter
.AdvanceOriginal(1);
7182 if (keptCharsLength
>= aSkippedMaxLength
) {
7183 break; // Already past the end, don't build string or gfxSkipCharsIter anymore
7188 aSkipChars
->TakeFrom(&skipCharsBuilder
); // Copy skipChars into aSkipChars
7190 // Caller must provide both pointers in order to retrieve a gfxSkipCharsIterator,
7191 // because the gfxSkipCharsIterator holds a weak pointer to the gfxSkipCars.
7192 *aSkipIter
= gfxSkipCharsIterator(*aSkipChars
, GetContentLength());
7200 // Translate the mapped content into a string that's printable
7202 nsTextFrame::ToCString(nsCString
& aBuf
, PRInt32
* aTotalContentLength
) const
7204 // Get the frames text content
7205 const nsTextFragment
* frag
= mContent
->GetText();
7210 // Compute the total length of the text content.
7211 *aTotalContentLength
= frag
->GetLength();
7213 PRInt32 contentLength
= GetContentLength();
7214 // Set current fragment and current fragment offset
7215 if (0 == contentLength
) {
7218 PRInt32 fragOffset
= GetContentOffset();
7219 PRInt32 n
= fragOffset
+ contentLength
;
7220 while (fragOffset
< n
) {
7221 PRUnichar ch
= frag
->CharAt(fragOffset
++);
7223 aBuf
.AppendLiteral("\\r");
7224 } else if (ch
== '\n') {
7225 aBuf
.AppendLiteral("\\n");
7226 } else if (ch
== '\t') {
7227 aBuf
.AppendLiteral("\\t");
7228 } else if ((ch
< ' ') || (ch
>= 127)) {
7229 aBuf
.Append(nsPrintfCString("\\u%04x", ch
));
7238 nsTextFrame::GetType() const
7240 return nsGkAtoms::textFrame
;
7243 /* virtual */ PRBool
7244 nsTextFrame::IsEmpty()
7246 NS_ASSERTION(!(mState
& TEXT_IS_ONLY_WHITESPACE
) ||
7247 !(mState
& TEXT_ISNOT_ONLY_WHITESPACE
),
7250 // XXXldb Should this check compatibility mode as well???
7251 const nsStyleText
* textStyle
= GetStyleText();
7252 if (textStyle
->WhiteSpaceIsSignificant()) {
7253 // XXX shouldn't we return true if the length is zero?
7257 if (mState
& TEXT_ISNOT_ONLY_WHITESPACE
) {
7261 if (mState
& TEXT_IS_ONLY_WHITESPACE
) {
7265 PRBool isEmpty
= IsAllWhitespace(mContent
->GetText(),
7266 textStyle
->mWhiteSpace
!= NS_STYLE_WHITESPACE_PRE_LINE
);
7267 mState
|= (isEmpty
? TEXT_IS_ONLY_WHITESPACE
: TEXT_ISNOT_ONLY_WHITESPACE
);
7273 nsTextFrame::GetFrameName(nsAString
& aResult
) const
7275 return MakeFrameName(NS_LITERAL_STRING("Text"), aResult
);
7278 NS_IMETHODIMP_(nsFrameState
)
7279 nsTextFrame::GetDebugStateBits() const
7281 // mask out our emptystate flags; those are just caches
7282 return nsFrame::GetDebugStateBits() &
7283 ~(TEXT_WHITESPACE_FLAGS
| TEXT_REFLOW_FLAGS
);
7287 nsTextFrame::List(FILE* out
, PRInt32 aIndent
) const
7290 IndentBy(out
, aIndent
);
7293 fprintf(out
, " [view=%p]", static_cast<void*>(GetView()));
7295 fprintf(out
, " [run=%p]", static_cast<void*>(mTextRun
));
7297 PRInt32 totalContentLength
;
7299 ToCString(tmp
, &totalContentLength
);
7301 // Output the first/last content offset and prev/next in flow info
7302 PRBool isComplete
= GetContentEnd() == totalContentLength
;
7303 fprintf(out
, "[%d,%d,%c] ",
7304 GetContentOffset(), GetContentLength(),
7305 isComplete
? 'T':'F');
7307 if (GetNextSibling()) {
7308 fprintf(out
, " next=%p", static_cast<void*>(GetNextSibling()));
7310 nsIFrame
* prevContinuation
= GetPrevContinuation();
7311 if (nsnull
!= prevContinuation
) {
7312 fprintf(out
, " prev-continuation=%p", static_cast<void*>(prevContinuation
));
7314 if (nsnull
!= mNextContinuation
) {
7315 fprintf(out
, " next-continuation=%p", static_cast<void*>(mNextContinuation
));
7318 // Output the rect and state
7319 fprintf(out
, " {%d,%d,%d,%d}", mRect
.x
, mRect
.y
, mRect
.width
, mRect
.height
);
7321 if (mState
& NS_FRAME_SELECTED_CONTENT
) {
7322 fprintf(out
, " [state=%016llx] SELECTED", mState
);
7324 fprintf(out
, " [state=%016llx]", mState
);
7327 fprintf(out
, " [content=%p]", static_cast<void*>(mContent
));
7328 if (HasOverflowAreas()) {
7329 nsRect overflowArea
= GetVisualOverflowRect();
7330 fprintf(out
, " [vis-overflow=%d,%d,%d,%d]",
7331 overflowArea
.x
, overflowArea
.y
,
7332 overflowArea
.width
, overflowArea
.height
);
7333 overflowArea
= GetScrollableOverflowRect();
7334 fprintf(out
, " [scr-overflow=%d,%d,%d,%d]",
7335 overflowArea
.x
, overflowArea
.y
,
7336 overflowArea
.width
, overflowArea
.height
);
7338 fprintf(out
, " sc=%p", static_cast<void*>(mStyleContext
));
7339 nsIAtom
* pseudoTag
= mStyleContext
->GetPseudo();
7341 nsAutoString atomString
;
7342 pseudoTag
->ToString(atomString
);
7343 fprintf(out
, " pst=%s",
7344 NS_LossyConvertUTF16toASCII(atomString
).get());
7351 IndentBy(out
, aIndent
);
7353 fputs(tmp
.get(), out
);
7357 IndentBy(out
, aIndent
);
7365 nsTextFrame::AdjustOffsetsForBidi(PRInt32 aStart
, PRInt32 aEnd
)
7367 AddStateBits(NS_FRAME_IS_BIDI
);
7370 * After Bidi resolution we may need to reassign text runs.
7371 * This is called during bidi resolution from the block container, so we
7372 * shouldn't be holding a local reference to a textrun anywhere.
7374 ClearTextRun(nsnull
);
7376 nsTextFrame
* prev
= static_cast<nsTextFrame
*>(GetPrevContinuation());
7378 // the bidi resolver can be very evil when columns/pages are involved. Don't
7379 // let it violate our invariants.
7380 PRInt32 prevOffset
= prev
->GetContentOffset();
7381 aStart
= NS_MAX(aStart
, prevOffset
);
7382 aEnd
= NS_MAX(aEnd
, prevOffset
);
7383 prev
->ClearTextRun(nsnull
);
7386 mContentOffset
= aStart
;
7387 SetLength(aEnd
- aStart
, nsnull
);
7391 * @return PR_TRUE if this text frame ends with a newline character. It should return
7392 * PR_FALSE if it is not a text frame.
7395 nsTextFrame::HasTerminalNewline() const
7397 return ::HasTerminalNewline(this);
7401 nsTextFrame::IsAtEndOfLine() const
7403 return (GetStateBits() & TEXT_END_OF_LINE
) != 0;
7407 nsTextFrame::GetBaseline() const
7413 nsTextFrame::HasAnyNoncollapsedCharacters()
7415 gfxSkipCharsIterator iter
= EnsureTextRun();
7416 PRInt32 offset
= GetContentOffset(),
7417 offsetEnd
= GetContentEnd();
7418 PRInt32 skippedOffset
= iter
.ConvertOriginalToSkipped(offset
);
7419 PRInt32 skippedOffsetEnd
= iter
.ConvertOriginalToSkipped(offsetEnd
);
7420 return skippedOffset
!= skippedOffsetEnd
;