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