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