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