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
4 : *
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/
9 : *
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.
14 : *
15 : * The Original Code is mozilla.org code.
16 : *
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.
21 : *
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>
37 : *
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.
49 : *
50 : * ***** END LICENSE BLOCK ***** */
51 :
52 : /* rendering object for textual content of elements */
53 :
54 : #include "mozilla/Util.h"
55 :
56 : #include "nsCOMPtr.h"
57 : #include "nsHTMLParts.h"
58 : #include "nsCRT.h"
59 : #include "nsSplittableFrame.h"
60 : #include "nsLineLayout.h"
61 : #include "nsString.h"
62 : #include "nsUnicharUtils.h"
63 : #include "nsPresContext.h"
64 : #include "nsIContent.h"
65 : #include "nsStyleConsts.h"
66 : #include "nsStyleContext.h"
67 : #include "nsCoord.h"
68 : #include "nsRenderingContext.h"
69 : #include "nsIPresShell.h"
70 : #include "nsITimer.h"
71 : #include "nsTArray.h"
72 : #include "nsIDocument.h"
73 : #include "nsCSSPseudoElements.h"
74 : #include "nsCSSFrameConstructor.h"
75 : #include "nsCompatibility.h"
76 : #include "nsCSSColorUtils.h"
77 : #include "nsLayoutUtils.h"
78 : #include "nsDisplayList.h"
79 : #include "nsFrame.h"
80 : #include "nsPlaceholderFrame.h"
81 : #include "nsTextFrameUtils.h"
82 : #include "nsTextRunTransformations.h"
83 : #include "nsFrameManager.h"
84 : #include "nsTextFrameTextRunCache.h"
85 : #include "nsExpirationTracker.h"
86 : #include "nsTextFrame.h"
87 : #include "nsUnicodeProperties.h"
88 : #include "nsUnicharUtilCIID.h"
89 :
90 : #include "nsTextFragment.h"
91 : #include "nsGkAtoms.h"
92 : #include "nsFrameSelection.h"
93 : #include "nsISelection.h"
94 : #include "nsIDOMRange.h"
95 : #include "nsRange.h"
96 : #include "nsCSSRendering.h"
97 : #include "nsContentUtils.h"
98 : #include "nsLineBreaker.h"
99 : #include "nsIWordBreaker.h"
100 : #include "nsGenericDOMDataNode.h"
101 :
102 : #include "nsILineIterator.h"
103 :
104 : #include "nsIServiceManager.h"
105 : #ifdef ACCESSIBILITY
106 : #include "nsAccessibilityService.h"
107 : #endif
108 : #include "nsAutoPtr.h"
109 :
110 : #include "nsBidiUtils.h"
111 : #include "nsPrintfCString.h"
112 :
113 : #include "gfxFont.h"
114 : #include "gfxContext.h"
115 : #include "gfxImageSurface.h"
116 :
117 : #include "mozilla/dom/Element.h"
118 : #include "mozilla/Util.h" // for DebugOnly
119 : #include "mozilla/LookAndFeel.h"
120 :
121 : #ifdef NS_DEBUG
122 : #undef NOISY_BLINK
123 : #undef NOISY_REFLOW
124 : #undef NOISY_TRIM
125 : #else
126 : #undef NOISY_BLINK
127 : #undef NOISY_REFLOW
128 : #undef NOISY_TRIM
129 : #endif
130 :
131 : using namespace mozilla;
132 : using namespace mozilla::dom;
133 :
134 0 : struct TabWidth {
135 0 : TabWidth(PRUint32 aOffset, PRUint32 aWidth)
136 0 : : mOffset(aOffset), mWidth(float(aWidth))
137 0 : { }
138 :
139 : PRUint32 mOffset; // character offset within the text covered by the
140 : // PropertyProvider
141 : float mWidth; // extra space to be added at this position (in app units)
142 : };
143 :
144 0 : struct TabWidthStore {
145 0 : TabWidthStore()
146 0 : : mLimit(0)
147 0 : { }
148 :
149 : // Apply tab widths to the aSpacing array, which corresponds to characters
150 : // beginning at aOffset and has length aLength. (Width records outside this
151 : // range will be ignored.)
152 : void ApplySpacing(gfxTextRun::PropertyProvider::Spacing *aSpacing,
153 : PRUint32 aOffset, PRUint32 aLength);
154 :
155 : PRUint32 mLimit; // offset up to which tabs have been measured;
156 : // positions beyond this have not been calculated
157 : // yet but may be appended if needed later
158 : nsTArray<TabWidth> mWidths; // (offset,width) records for each tab character
159 : };
160 :
161 : void
162 0 : TabWidthStore::ApplySpacing(gfxTextRun::PropertyProvider::Spacing *aSpacing,
163 : PRUint32 aOffset, PRUint32 aLength)
164 : {
165 : // We could binary-search for the first record that falls within the range,
166 : // but as the number of tabs is normally small and we usually process them
167 : // sequentially from the beginning of the line, it doesn't seem worth doing
168 : // at this point.
169 0 : for (PRUint32 i = 0; i < mWidths.Length(); ++i) {
170 0 : TabWidth& tw = mWidths[i];
171 0 : if (tw.mOffset < aOffset) {
172 0 : continue;
173 : }
174 0 : if (tw.mOffset - aOffset >= aLength) {
175 0 : break;
176 : }
177 0 : aSpacing[tw.mOffset - aOffset].mAfter += tw.mWidth;
178 : }
179 0 : }
180 :
181 0 : static void DestroyTabWidth(void* aPropertyValue)
182 : {
183 0 : delete static_cast<TabWidthStore*>(aPropertyValue);
184 0 : }
185 :
186 0 : NS_DECLARE_FRAME_PROPERTY(TabWidthProperty, DestroyTabWidth)
187 :
188 0 : NS_DECLARE_FRAME_PROPERTY(OffsetToFrameProperty, nsnull)
189 :
190 : // text runs are destroyed by the text run cache
191 0 : NS_DECLARE_FRAME_PROPERTY(UninflatedTextRunProperty, nsnull)
192 :
193 0 : NS_DECLARE_FRAME_PROPERTY(FontSizeInflationProperty, nsnull)
194 :
195 : // The following flags are set during reflow
196 :
197 : // This bit is set on the first frame in a continuation indicating
198 : // that it was chopped short because of :first-letter style.
199 : #define TEXT_FIRST_LETTER NS_FRAME_STATE_BIT(20)
200 : // This bit is set on frames that are logically adjacent to the start of the
201 : // line (i.e. no prior frame on line with actual displayed in-flow content).
202 : #define TEXT_START_OF_LINE NS_FRAME_STATE_BIT(21)
203 : // This bit is set on frames that are logically adjacent to the end of the
204 : // line (i.e. no following on line with actual displayed in-flow content).
205 : #define TEXT_END_OF_LINE NS_FRAME_STATE_BIT(22)
206 : // This bit is set on frames that end with a hyphenated break.
207 : #define TEXT_HYPHEN_BREAK NS_FRAME_STATE_BIT(23)
208 : // This bit is set on frames that trimmed trailing whitespace characters when
209 : // calculating their width during reflow.
210 : #define TEXT_TRIMMED_TRAILING_WHITESPACE NS_FRAME_STATE_BIT(24)
211 : // This bit is set on frames that have justification enabled. We record
212 : // this in a state bit because we don't always have the containing block
213 : // easily available to check text-align on.
214 : #define TEXT_JUSTIFICATION_ENABLED NS_FRAME_STATE_BIT(25)
215 : // Set this bit if the textframe has overflow area for IME/spellcheck underline.
216 : #define TEXT_SELECTION_UNDERLINE_OVERFLOWED NS_FRAME_STATE_BIT(26)
217 :
218 : #define TEXT_REFLOW_FLAGS \
219 : (TEXT_FIRST_LETTER|TEXT_START_OF_LINE|TEXT_END_OF_LINE|TEXT_HYPHEN_BREAK| \
220 : TEXT_TRIMMED_TRAILING_WHITESPACE|TEXT_JUSTIFICATION_ENABLED| \
221 : TEXT_HAS_NONCOLLAPSED_CHARACTERS|TEXT_SELECTION_UNDERLINE_OVERFLOWED)
222 :
223 : // Cache bits for IsEmpty().
224 : // Set this bit if the textframe is known to be only collapsible whitespace.
225 : #define TEXT_IS_ONLY_WHITESPACE NS_FRAME_STATE_BIT(27)
226 : // Set this bit if the textframe is known to be not only collapsible whitespace.
227 : #define TEXT_ISNOT_ONLY_WHITESPACE NS_FRAME_STATE_BIT(28)
228 :
229 : #define TEXT_WHITESPACE_FLAGS (TEXT_IS_ONLY_WHITESPACE | \
230 : TEXT_ISNOT_ONLY_WHITESPACE)
231 : // This bit is set while the frame is registered as a blinking frame.
232 : #define TEXT_BLINK_ON NS_FRAME_STATE_BIT(29)
233 :
234 : // Set when this text frame is mentioned in the userdata for a textrun
235 : #define TEXT_IN_TEXTRUN_USER_DATA NS_FRAME_STATE_BIT(30)
236 :
237 : // nsTextFrame.h has
238 : // #define TEXT_HAS_NONCOLLAPSED_CHARACTERS NS_FRAME_STATE_BIT(31)
239 : // #define TEXT_HAS_FONT_INFLATION NS_FRAME_STATE_BIT(61)
240 :
241 : // If true, then this frame is being removed due to a SetLength() on a
242 : // previous continuation and the style context of that previous
243 : // continuation is the same as this frame's
244 : #define TEXT_STYLE_MATCHES_PREV_CONTINUATION NS_FRAME_STATE_BIT(62)
245 :
246 : // Whether this frame is cached in the Offset Frame Cache (OffsetToFrameProperty)
247 : #define TEXT_IN_OFFSET_CACHE NS_FRAME_STATE_BIT(63)
248 :
249 : /*
250 : * Some general notes
251 : *
252 : * Text frames delegate work to gfxTextRun objects. The gfxTextRun object
253 : * transforms text to positioned glyphs. It can report the geometry of the
254 : * glyphs and paint them. Text frames configure gfxTextRuns by providing text,
255 : * spacing, language, and other information.
256 : *
257 : * A gfxTextRun can cover more than one DOM text node. This is necessary to
258 : * get kerning, ligatures and shaping for text that spans multiple text nodes
259 : * but is all the same font. The userdata for a gfxTextRun object is a
260 : * TextRunUserData* or an nsIFrame*.
261 : *
262 : * We go to considerable effort to make sure things work even if in-flow
263 : * siblings have different style contexts (i.e., first-letter and first-line).
264 : *
265 : * Our convention is that unsigned integer character offsets are offsets into
266 : * the transformed string. Signed integer character offsets are offsets into
267 : * the DOM string.
268 : *
269 : * XXX currently we don't handle hyphenated breaks between text frames where the
270 : * hyphen occurs at the end of the first text frame, e.g.
271 : * <b>Kit­</b>ty
272 : */
273 :
274 : /**
275 : * We use an array of these objects to record which text frames
276 : * are associated with the textrun. mStartFrame is the start of a list of
277 : * text frames. Some sequence of its continuations are covered by the textrun.
278 : * A content textnode can have at most one TextRunMappedFlow associated with it
279 : * for a given textrun.
280 : *
281 : * mDOMOffsetToBeforeTransformOffset is added to DOM offsets for those frames to obtain
282 : * the offset into the before-transformation text of the textrun. It can be
283 : * positive (when a text node starts in the middle of a text run) or
284 : * negative (when a text run starts in the middle of a text node). Of course
285 : * it can also be zero.
286 : */
287 : struct TextRunMappedFlow {
288 : nsTextFrame* mStartFrame;
289 : PRInt32 mDOMOffsetToBeforeTransformOffset;
290 : // The text mapped starts at mStartFrame->GetContentOffset() and is this long
291 : PRUint32 mContentLength;
292 : };
293 :
294 : /**
295 : * This is our user data for the textrun, when textRun->GetFlags() does not
296 : * have TEXT_IS_SIMPLE_FLOW set. When TEXT_IS_SIMPLE_FLOW is set, there is
297 : * just one flow, the textrun's user data pointer is a pointer to mStartFrame
298 : * for that flow, mDOMOffsetToBeforeTransformOffset is zero, and mContentLength
299 : * is the length of the text node.
300 : */
301 : struct TextRunUserData {
302 : TextRunMappedFlow* mMappedFlows;
303 : PRUint32 mMappedFlowCount;
304 : PRUint32 mLastFlowIndex;
305 : };
306 :
307 : /**
308 : * This helper object computes colors used for painting, and also IME
309 : * underline information. The data is computed lazily and cached as necessary.
310 : * These live for just the duration of one paint operation.
311 : */
312 : class nsTextPaintStyle {
313 : public:
314 : nsTextPaintStyle(nsTextFrame* aFrame);
315 :
316 : nscolor GetTextColor();
317 : /**
318 : * Compute the colors for normally-selected text. Returns false if
319 : * the normal selection is not being displayed.
320 : */
321 : bool GetSelectionColors(nscolor* aForeColor,
322 : nscolor* aBackColor);
323 : void GetHighlightColors(nscolor* aForeColor,
324 : nscolor* aBackColor);
325 : void GetURLSecondaryColor(nscolor* aForeColor);
326 : void GetIMESelectionColors(PRInt32 aIndex,
327 : nscolor* aForeColor,
328 : nscolor* aBackColor);
329 : // if this returns false, we don't need to draw underline.
330 : bool GetSelectionUnderlineForPaint(PRInt32 aIndex,
331 : nscolor* aLineColor,
332 : float* aRelativeSize,
333 : PRUint8* aStyle);
334 :
335 : // if this returns false, we don't need to draw underline.
336 : static bool GetSelectionUnderline(nsPresContext* aPresContext,
337 : PRInt32 aIndex,
338 : nscolor* aLineColor,
339 : float* aRelativeSize,
340 : PRUint8* aStyle);
341 :
342 0 : nsPresContext* PresContext() const { return mPresContext; }
343 :
344 : enum {
345 : eIndexRawInput = 0,
346 : eIndexSelRawText,
347 : eIndexConvText,
348 : eIndexSelConvText,
349 : eIndexSpellChecker
350 : };
351 :
352 0 : static PRInt32 GetUnderlineStyleIndexForSelectionType(PRInt32 aSelectionType)
353 : {
354 0 : switch (aSelectionType) {
355 : case nsISelectionController::SELECTION_IME_RAWINPUT:
356 0 : return eIndexRawInput;
357 : case nsISelectionController::SELECTION_IME_SELECTEDRAWTEXT:
358 0 : return eIndexSelRawText;
359 : case nsISelectionController::SELECTION_IME_CONVERTEDTEXT:
360 0 : return eIndexConvText;
361 : case nsISelectionController::SELECTION_IME_SELECTEDCONVERTEDTEXT:
362 0 : return eIndexSelConvText;
363 : case nsISelectionController::SELECTION_SPELLCHECK:
364 0 : return eIndexSpellChecker;
365 : default:
366 0 : NS_WARNING("non-IME selection type");
367 0 : return eIndexRawInput;
368 : }
369 : }
370 :
371 : protected:
372 : nsTextFrame* mFrame;
373 : nsPresContext* mPresContext;
374 : bool mInitCommonColors;
375 : bool mInitSelectionColors;
376 :
377 : // Selection data
378 :
379 : PRInt16 mSelectionStatus; // see nsIDocument.h SetDisplaySelection()
380 : nscolor mSelectionTextColor;
381 : nscolor mSelectionBGColor;
382 :
383 : // Common data
384 :
385 : PRInt32 mSufficientContrast;
386 : nscolor mFrameBackgroundColor;
387 :
388 : // selection colors and underline info, the colors are resolved colors,
389 : // i.e., the foreground color and background color are swapped if it's needed.
390 : // And also line color will be resolved from them.
391 : struct nsSelectionStyle {
392 : bool mInit;
393 : nscolor mTextColor;
394 : nscolor mBGColor;
395 : nscolor mUnderlineColor;
396 : PRUint8 mUnderlineStyle;
397 : float mUnderlineRelativeSize;
398 : };
399 : nsSelectionStyle mSelectionStyle[5];
400 :
401 : // Color initializations
402 : void InitCommonColors();
403 : bool InitSelectionColors();
404 :
405 : nsSelectionStyle* GetSelectionStyle(PRInt32 aIndex);
406 : void InitSelectionStyle(PRInt32 aIndex);
407 :
408 : bool EnsureSufficientContrast(nscolor *aForeColor, nscolor *aBackColor);
409 :
410 : nscolor GetResolvedForeColor(nscolor aColor, nscolor aDefaultForeColor,
411 : nscolor aBackColor);
412 : };
413 :
414 : static void
415 0 : DestroyUserData(void* aUserData)
416 : {
417 0 : TextRunUserData* userData = static_cast<TextRunUserData*>(aUserData);
418 0 : if (userData) {
419 0 : nsMemory::Free(userData);
420 : }
421 0 : }
422 :
423 : /**
424 : * Remove |aTextRun| from the frame continuation chain starting at
425 : * |aStartContinuation| if non-null, otherwise starting at |aFrame|.
426 : * Unmark |aFrame| as a text run owner if it's the frame we start at.
427 : * Return true if |aStartContinuation| is non-null and was found
428 : * in the next-continuation chain of |aFrame|.
429 : */
430 : static bool
431 0 : ClearAllTextRunReferences(nsTextFrame* aFrame, gfxTextRun* aTextRun,
432 : nsTextFrame* aStartContinuation)
433 : {
434 0 : NS_PRECONDITION(aFrame, "");
435 0 : NS_PRECONDITION(!aStartContinuation ||
436 : (!aStartContinuation->GetTextRun(nsTextFrame::eInflated) ||
437 : aStartContinuation->GetTextRun(nsTextFrame::eInflated) == aTextRun) ||
438 : (!aStartContinuation->GetTextRun(nsTextFrame::eNotInflated) ||
439 : aStartContinuation->GetTextRun(nsTextFrame::eNotInflated) == aTextRun),
440 : "wrong aStartContinuation for this text run");
441 :
442 0 : if (!aStartContinuation || aStartContinuation == aFrame) {
443 0 : aFrame->RemoveStateBits(TEXT_IN_TEXTRUN_USER_DATA);
444 : } else {
445 0 : do {
446 0 : NS_ASSERTION(aFrame->GetType() == nsGkAtoms::textFrame, "Bad frame");
447 0 : aFrame = static_cast<nsTextFrame*>(aFrame->GetNextContinuation());
448 : } while (aFrame && aFrame != aStartContinuation);
449 : }
450 0 : bool found = aStartContinuation == aFrame;
451 0 : while (aFrame) {
452 0 : NS_ASSERTION(aFrame->GetType() == nsGkAtoms::textFrame, "Bad frame");
453 0 : if (!aFrame->RemoveTextRun(aTextRun))
454 0 : break;
455 0 : aFrame = static_cast<nsTextFrame*>(aFrame->GetNextContinuation());
456 : }
457 0 : NS_POSTCONDITION(!found || aStartContinuation, "how did we find null?");
458 0 : return found;
459 : }
460 :
461 : /**
462 : * Kill all references to |aTextRun| starting at |aStartContinuation|.
463 : * It could be referenced by any of its owners, and all their in-flows.
464 : * If |aStartContinuation| is null then process all userdata frames
465 : * and their continuations.
466 : * @note the caller is expected to take care of possibly destroying the
467 : * text run if all userdata frames were reset (userdata is deallocated
468 : * by this function though). The caller can detect this has occured by
469 : * checking |aTextRun->GetUserData() == nsnull|.
470 : */
471 : static void
472 0 : UnhookTextRunFromFrames(gfxTextRun* aTextRun, nsTextFrame* aStartContinuation)
473 : {
474 0 : if (!aTextRun->GetUserData())
475 0 : return;
476 :
477 0 : if (aTextRun->GetFlags() & nsTextFrameUtils::TEXT_IS_SIMPLE_FLOW) {
478 0 : nsIFrame* userDataFrame = static_cast<nsIFrame*>(aTextRun->GetUserData());
479 : DebugOnly<bool> found =
480 : ClearAllTextRunReferences(static_cast<nsTextFrame*>(userDataFrame),
481 0 : aTextRun, aStartContinuation);
482 0 : NS_ASSERTION(!aStartContinuation || found,
483 : "aStartContinuation wasn't found in simple flow text run");
484 0 : if (!(userDataFrame->GetStateBits() & TEXT_IN_TEXTRUN_USER_DATA)) {
485 0 : aTextRun->SetUserData(nsnull);
486 : }
487 : } else {
488 : TextRunUserData* userData =
489 0 : static_cast<TextRunUserData*>(aTextRun->GetUserData());
490 0 : PRInt32 destroyFromIndex = aStartContinuation ? -1 : 0;
491 0 : for (PRUint32 i = 0; i < userData->mMappedFlowCount; ++i) {
492 0 : nsTextFrame* userDataFrame = userData->mMappedFlows[i].mStartFrame;
493 : bool found =
494 : ClearAllTextRunReferences(userDataFrame, aTextRun,
495 0 : aStartContinuation);
496 0 : if (found) {
497 0 : if (userDataFrame->GetStateBits() & TEXT_IN_TEXTRUN_USER_DATA) {
498 0 : destroyFromIndex = i + 1;
499 : }
500 : else {
501 0 : destroyFromIndex = i;
502 : }
503 0 : aStartContinuation = nsnull;
504 : }
505 : }
506 0 : NS_ASSERTION(destroyFromIndex >= 0,
507 : "aStartContinuation wasn't found in multi flow text run");
508 0 : if (destroyFromIndex == 0) {
509 0 : DestroyUserData(userData);
510 0 : aTextRun->SetUserData(nsnull);
511 : }
512 : else {
513 0 : userData->mMappedFlowCount = PRUint32(destroyFromIndex);
514 0 : if (userData->mLastFlowIndex >= PRUint32(destroyFromIndex)) {
515 0 : userData->mLastFlowIndex = PRUint32(destroyFromIndex) - 1;
516 : }
517 : }
518 : }
519 : }
520 :
521 : class FrameTextRunCache;
522 :
523 : static FrameTextRunCache *gTextRuns = nsnull;
524 :
525 : /*
526 : * Cache textruns and expire them after 3*10 seconds of no use.
527 : */
528 : class FrameTextRunCache : public nsExpirationTracker<gfxTextRun,3> {
529 : public:
530 : enum { TIMEOUT_SECONDS = 10 };
531 1404 : FrameTextRunCache()
532 1404 : : nsExpirationTracker<gfxTextRun,3>(TIMEOUT_SECONDS*1000) {}
533 2806 : ~FrameTextRunCache() {
534 1403 : AgeAllGenerations();
535 1403 : }
536 :
537 0 : void RemoveFromCache(gfxTextRun* aTextRun) {
538 0 : if (aTextRun->GetExpirationState()->IsTracked()) {
539 0 : RemoveObject(aTextRun);
540 : }
541 0 : }
542 :
543 : // This gets called when the timeout has expired on a gfxTextRun
544 0 : virtual void NotifyExpired(gfxTextRun* aTextRun) {
545 0 : UnhookTextRunFromFrames(aTextRun, nsnull);
546 0 : RemoveFromCache(aTextRun);
547 0 : delete aTextRun;
548 0 : }
549 : };
550 :
551 : // Helper to create a textrun and remember it in the textframe cache,
552 : // for either 8-bit or 16-bit text strings
553 : template<typename T>
554 : gfxTextRun *
555 0 : MakeTextRun(const T *aText, PRUint32 aLength,
556 : gfxFontGroup *aFontGroup, const gfxFontGroup::Parameters* aParams,
557 : PRUint32 aFlags)
558 : {
559 : nsAutoPtr<gfxTextRun> textRun(aFontGroup->MakeTextRun(aText, aLength,
560 0 : aParams, aFlags));
561 0 : if (!textRun) {
562 0 : return nsnull;
563 : }
564 0 : nsresult rv = gTextRuns->AddObject(textRun);
565 0 : if (NS_FAILED(rv)) {
566 0 : gTextRuns->RemoveFromCache(textRun);
567 0 : return nsnull;
568 : }
569 : #ifdef NOISY_BIDI
570 : printf("Created textrun\n");
571 : #endif
572 0 : return textRun.forget();
573 : }
574 :
575 : void
576 1404 : nsTextFrameTextRunCache::Init() {
577 1404 : gTextRuns = new FrameTextRunCache();
578 1404 : }
579 :
580 : void
581 1403 : nsTextFrameTextRunCache::Shutdown() {
582 1403 : delete gTextRuns;
583 1403 : gTextRuns = nsnull;
584 1403 : }
585 :
586 0 : PRInt32 nsTextFrame::GetContentEnd() const {
587 0 : nsTextFrame* next = static_cast<nsTextFrame*>(GetNextContinuation());
588 0 : return next ? next->GetContentOffset() : mContent->GetText()->GetLength();
589 : }
590 :
591 : struct FlowLengthProperty {
592 : PRInt32 mStartOffset;
593 : // The offset of the next fixed continuation after mStartOffset, or
594 : // of the end of the text if there is none
595 : PRInt32 mEndFlowOffset;
596 :
597 0 : static void Destroy(void* aObject, nsIAtom* aPropertyName,
598 : void* aPropertyValue, void* aData)
599 : {
600 : delete static_cast<FlowLengthProperty*>(aPropertyValue);
601 0 : }
602 : };
603 :
604 0 : PRInt32 nsTextFrame::GetInFlowContentLength() {
605 0 : if (!(mState & NS_FRAME_IS_BIDI)) {
606 0 : return mContent->TextLength() - mContentOffset;
607 : }
608 :
609 : FlowLengthProperty* flowLength =
610 0 : static_cast<FlowLengthProperty*>(mContent->GetProperty(nsGkAtoms::flowlength));
611 :
612 : /**
613 : * This frame must start inside the cached flow. If the flow starts at
614 : * mContentOffset but this frame is empty, logically it might be before the
615 : * start of the cached flow.
616 : */
617 0 : if (flowLength &&
618 : (flowLength->mStartOffset < mContentOffset ||
619 0 : (flowLength->mStartOffset == mContentOffset && GetContentEnd() > mContentOffset)) &&
620 : flowLength->mEndFlowOffset > mContentOffset) {
621 : #ifdef DEBUG
622 0 : NS_ASSERTION(flowLength->mEndFlowOffset >= GetContentEnd(),
623 : "frame crosses fixed continuation boundary");
624 : #endif
625 0 : return flowLength->mEndFlowOffset - mContentOffset;
626 : }
627 :
628 0 : nsTextFrame* nextBidi = static_cast<nsTextFrame*>(GetLastInFlow()->GetNextContinuation());
629 0 : PRInt32 endFlow = nextBidi ? nextBidi->GetContentOffset() : mContent->TextLength();
630 :
631 0 : if (!flowLength) {
632 0 : flowLength = new FlowLengthProperty;
633 0 : if (NS_FAILED(mContent->SetProperty(nsGkAtoms::flowlength, flowLength,
634 : FlowLengthProperty::Destroy))) {
635 : delete flowLength;
636 0 : flowLength = nsnull;
637 : }
638 : }
639 0 : if (flowLength) {
640 0 : flowLength->mStartOffset = mContentOffset;
641 0 : flowLength->mEndFlowOffset = endFlow;
642 : }
643 :
644 0 : return endFlow - mContentOffset;
645 : }
646 :
647 : // Smarter versions of XP_IS_SPACE.
648 : // Unicode is really annoying; sometimes a space character isn't whitespace ---
649 : // when it combines with another character
650 : // So we have several versions of IsSpace for use in different contexts.
651 :
652 0 : static bool IsSpaceCombiningSequenceTail(const nsTextFragment* aFrag, PRUint32 aPos)
653 : {
654 0 : NS_ASSERTION(aPos <= aFrag->GetLength(), "Bad offset");
655 0 : if (!aFrag->Is2b())
656 0 : return false;
657 : return nsTextFrameUtils::IsSpaceCombiningSequenceTail(
658 0 : aFrag->Get2b() + aPos, aFrag->GetLength() - aPos);
659 : }
660 :
661 : // Check whether aPos is a space for CSS 'word-spacing' purposes
662 0 : static bool IsCSSWordSpacingSpace(const nsTextFragment* aFrag,
663 : PRUint32 aPos, const nsStyleText* aStyleText)
664 : {
665 0 : NS_ASSERTION(aPos < aFrag->GetLength(), "No text for IsSpace!");
666 :
667 0 : PRUnichar ch = aFrag->CharAt(aPos);
668 0 : switch (ch) {
669 : case ' ':
670 : case CH_NBSP:
671 0 : return !IsSpaceCombiningSequenceTail(aFrag, aPos + 1);
672 : case '\r':
673 0 : case '\t': return !aStyleText->WhiteSpaceIsSignificant();
674 0 : case '\n': return !aStyleText->NewlineIsSignificant();
675 0 : default: return false;
676 : }
677 : }
678 :
679 : // Check whether the string aChars/aLength starts with space that's
680 : // trimmable according to CSS 'white-space:normal/nowrap'.
681 0 : static bool IsTrimmableSpace(const PRUnichar* aChars, PRUint32 aLength)
682 : {
683 0 : NS_ASSERTION(aLength > 0, "No text for IsSpace!");
684 :
685 0 : PRUnichar ch = *aChars;
686 0 : if (ch == ' ')
687 0 : return !nsTextFrameUtils::IsSpaceCombiningSequenceTail(aChars + 1, aLength - 1);
688 0 : return ch == '\t' || ch == '\f' || ch == '\n' || ch == '\r';
689 : }
690 :
691 : // Check whether the character aCh is trimmable according to CSS
692 : // 'white-space:normal/nowrap'
693 0 : static bool IsTrimmableSpace(char aCh)
694 : {
695 0 : return aCh == ' ' || aCh == '\t' || aCh == '\f' || aCh == '\n' || aCh == '\r';
696 : }
697 :
698 0 : static bool IsTrimmableSpace(const nsTextFragment* aFrag, PRUint32 aPos,
699 : const nsStyleText* aStyleText)
700 : {
701 0 : NS_ASSERTION(aPos < aFrag->GetLength(), "No text for IsSpace!");
702 :
703 0 : switch (aFrag->CharAt(aPos)) {
704 0 : case ' ': return !aStyleText->WhiteSpaceIsSignificant() &&
705 0 : !IsSpaceCombiningSequenceTail(aFrag, aPos + 1);
706 0 : case '\n': return !aStyleText->NewlineIsSignificant();
707 : case '\t':
708 : case '\r':
709 0 : case '\f': return !aStyleText->WhiteSpaceIsSignificant();
710 0 : default: return false;
711 : }
712 : }
713 :
714 0 : static bool IsSelectionSpace(const nsTextFragment* aFrag, PRUint32 aPos)
715 : {
716 0 : NS_ASSERTION(aPos < aFrag->GetLength(), "No text for IsSpace!");
717 0 : PRUnichar ch = aFrag->CharAt(aPos);
718 0 : if (ch == ' ' || ch == CH_NBSP)
719 0 : return !IsSpaceCombiningSequenceTail(aFrag, aPos + 1);
720 0 : return ch == '\t' || ch == '\n' || ch == '\f' || ch == '\r';
721 : }
722 :
723 : // Count the amount of trimmable whitespace (as per CSS
724 : // 'white-space:normal/nowrap') in a text fragment. The first
725 : // character is at offset aStartOffset; the maximum number of characters
726 : // to check is aLength. aDirection is -1 or 1 depending on whether we should
727 : // progress backwards or forwards.
728 : static PRUint32
729 0 : GetTrimmableWhitespaceCount(const nsTextFragment* aFrag,
730 : PRInt32 aStartOffset, PRInt32 aLength,
731 : PRInt32 aDirection)
732 : {
733 0 : PRInt32 count = 0;
734 0 : if (aFrag->Is2b()) {
735 0 : const PRUnichar* str = aFrag->Get2b() + aStartOffset;
736 0 : PRInt32 fragLen = aFrag->GetLength() - aStartOffset;
737 0 : for (; count < aLength; ++count) {
738 0 : if (!IsTrimmableSpace(str, fragLen))
739 0 : break;
740 0 : str += aDirection;
741 0 : fragLen -= aDirection;
742 : }
743 : } else {
744 0 : const char* str = aFrag->Get1b() + aStartOffset;
745 0 : for (; count < aLength; ++count) {
746 0 : if (!IsTrimmableSpace(*str))
747 0 : break;
748 0 : str += aDirection;
749 : }
750 : }
751 0 : return count;
752 : }
753 :
754 : static bool
755 0 : IsAllWhitespace(const nsTextFragment* aFrag, bool aAllowNewline)
756 : {
757 0 : if (aFrag->Is2b())
758 0 : return false;
759 0 : PRInt32 len = aFrag->GetLength();
760 0 : const char* str = aFrag->Get1b();
761 0 : for (PRInt32 i = 0; i < len; ++i) {
762 0 : char ch = str[i];
763 0 : if (ch == ' ' || ch == '\t' || ch == '\r' || (ch == '\n' && aAllowNewline))
764 0 : continue;
765 0 : return false;
766 : }
767 0 : return true;
768 : }
769 :
770 : /**
771 : * This class accumulates state as we scan a paragraph of text. It detects
772 : * textrun boundaries (changes from text to non-text, hard
773 : * line breaks, and font changes) and builds a gfxTextRun at each boundary.
774 : * It also detects linebreaker run boundaries (changes from text to non-text,
775 : * and hard line breaks) and at each boundary runs the linebreaker to compute
776 : * potential line breaks. It also records actual line breaks to store them in
777 : * the textruns.
778 : */
779 : class BuildTextRunsScanner {
780 : public:
781 0 : BuildTextRunsScanner(nsPresContext* aPresContext, gfxContext* aContext,
782 : nsIFrame* aLineContainer, nsTextFrame::TextRunType aWhichTextRun,
783 : float aInflation) :
784 : mCurrentFramesAllSameTextRun(nsnull),
785 : mContext(aContext),
786 : mLineContainer(aLineContainer),
787 : mInflation(aInflation),
788 0 : mBidiEnabled(aPresContext->BidiEnabled()),
789 : mSkipIncompleteTextRuns(false),
790 : mWhichTextRun(aWhichTextRun),
791 : mNextRunContextInfo(nsTextFrameUtils::INCOMING_NONE),
792 0 : mCurrentRunContextInfo(nsTextFrameUtils::INCOMING_NONE) {
793 0 : ResetRunInfo();
794 0 : }
795 0 : ~BuildTextRunsScanner() {
796 0 : NS_ASSERTION(mBreakSinks.IsEmpty(), "Should have been cleared");
797 0 : NS_ASSERTION(mTextRunsToDelete.IsEmpty(), "Should have been cleared");
798 0 : NS_ASSERTION(mLineBreakBeforeFrames.IsEmpty(), "Should have been cleared");
799 0 : NS_ASSERTION(mMappedFlows.IsEmpty(), "Should have been cleared");
800 0 : }
801 :
802 0 : void SetAtStartOfLine() {
803 0 : mStartOfLine = true;
804 0 : mCanStopOnThisLine = false;
805 0 : }
806 0 : void SetSkipIncompleteTextRuns(bool aSkip) {
807 0 : mSkipIncompleteTextRuns = aSkip;
808 0 : }
809 0 : void SetCommonAncestorWithLastFrame(nsIFrame* aFrame) {
810 0 : mCommonAncestorWithLastFrame = aFrame;
811 0 : }
812 0 : bool CanStopOnThisLine() {
813 0 : return mCanStopOnThisLine;
814 : }
815 : nsIFrame* GetCommonAncestorWithLastFrame() {
816 : return mCommonAncestorWithLastFrame;
817 : }
818 0 : void LiftCommonAncestorWithLastFrameToParent(nsIFrame* aFrame) {
819 0 : if (mCommonAncestorWithLastFrame &&
820 0 : mCommonAncestorWithLastFrame->GetParent() == aFrame) {
821 0 : mCommonAncestorWithLastFrame = aFrame;
822 : }
823 0 : }
824 : void ScanFrame(nsIFrame* aFrame);
825 : bool IsTextRunValidForMappedFlows(gfxTextRun* aTextRun);
826 : void FlushFrames(bool aFlushLineBreaks, bool aSuppressTrailingBreak);
827 : void FlushLineBreaks(gfxTextRun* aTrailingTextRun);
828 0 : void ResetRunInfo() {
829 0 : mLastFrame = nsnull;
830 0 : mMappedFlows.Clear();
831 0 : mLineBreakBeforeFrames.Clear();
832 0 : mMaxTextLength = 0;
833 0 : mDoubleByteText = false;
834 0 : }
835 : void AccumulateRunInfo(nsTextFrame* aFrame);
836 : /**
837 : * @return null to indicate either textrun construction failed or
838 : * we constructed just a partial textrun to set up linebreaker and other
839 : * state for following textruns.
840 : */
841 : gfxTextRun* BuildTextRunForFrames(void* aTextBuffer);
842 : bool SetupLineBreakerContext(gfxTextRun *aTextRun);
843 : void AssignTextRun(gfxTextRun* aTextRun);
844 : nsTextFrame* GetNextBreakBeforeFrame(PRUint32* aIndex);
845 : enum SetupBreakSinksFlags {
846 : SBS_DOUBLE_BYTE = (1 << 0),
847 : SBS_EXISTING_TEXTRUN = (1 << 1),
848 : SBS_SUPPRESS_SINK = (1 << 2)
849 : };
850 : void SetupBreakSinksForTextRun(gfxTextRun* aTextRun,
851 : const void* aTextPtr,
852 : PRUint32 aFlags);
853 : struct FindBoundaryState {
854 : nsIFrame* mStopAtFrame;
855 : nsTextFrame* mFirstTextFrame;
856 : nsTextFrame* mLastTextFrame;
857 : bool mSeenTextRunBoundaryOnLaterLine;
858 : bool mSeenTextRunBoundaryOnThisLine;
859 : bool mSeenSpaceForLineBreakingOnThisLine;
860 : };
861 : enum FindBoundaryResult {
862 : FB_CONTINUE,
863 : FB_STOPPED_AT_STOP_FRAME,
864 : FB_FOUND_VALID_TEXTRUN_BOUNDARY
865 : };
866 : FindBoundaryResult FindBoundaries(nsIFrame* aFrame, FindBoundaryState* aState);
867 :
868 : bool ContinueTextRunAcrossFrames(nsTextFrame* aFrame1, nsTextFrame* aFrame2);
869 :
870 : // Like TextRunMappedFlow but with some differences. mStartFrame to mEndFrame
871 : // (exclusive) are a sequence of in-flow frames (if mEndFrame is null, then
872 : // continuations starting from mStartFrame are a sequence of in-flow frames).
873 0 : struct MappedFlow {
874 : nsTextFrame* mStartFrame;
875 : nsTextFrame* mEndFrame;
876 : // When we consider breaking between elements, the nearest common
877 : // ancestor of the elements containing the characters is the one whose
878 : // CSS 'white-space' property governs. So this records the nearest common
879 : // ancestor of mStartFrame and the previous text frame, or null if there
880 : // was no previous text frame on this line.
881 : nsIFrame* mAncestorControllingInitialBreak;
882 :
883 0 : PRInt32 GetContentEnd() {
884 0 : return mEndFrame ? mEndFrame->GetContentOffset()
885 0 : : mStartFrame->GetContent()->GetText()->GetLength();
886 : }
887 : };
888 :
889 : class BreakSink : public nsILineBreakSink {
890 : public:
891 0 : BreakSink(gfxTextRun* aTextRun, gfxContext* aContext, PRUint32 aOffsetIntoTextRun,
892 : bool aExistingTextRun) :
893 : mTextRun(aTextRun), mContext(aContext),
894 : mOffsetIntoTextRun(aOffsetIntoTextRun),
895 0 : mChangedBreaks(false), mExistingTextRun(aExistingTextRun) {}
896 :
897 0 : virtual void SetBreaks(PRUint32 aOffset, PRUint32 aLength,
898 : PRUint8* aBreakBefore) {
899 0 : if (mTextRun->SetPotentialLineBreaks(aOffset + mOffsetIntoTextRun, aLength,
900 0 : aBreakBefore, mContext)) {
901 0 : mChangedBreaks = true;
902 : // Be conservative and assume that some breaks have been set
903 0 : mTextRun->ClearFlagBits(nsTextFrameUtils::TEXT_NO_BREAKS);
904 : }
905 0 : }
906 :
907 0 : virtual void SetCapitalization(PRUint32 aOffset, PRUint32 aLength,
908 : bool* aCapitalize) {
909 0 : NS_ASSERTION(mTextRun->GetFlags() & nsTextFrameUtils::TEXT_IS_TRANSFORMED,
910 : "Text run should be transformed!");
911 : nsTransformedTextRun* transformedTextRun =
912 0 : static_cast<nsTransformedTextRun*>(mTextRun);
913 : transformedTextRun->SetCapitalization(aOffset + mOffsetIntoTextRun, aLength,
914 0 : aCapitalize, mContext);
915 0 : }
916 :
917 0 : void Finish() {
918 0 : NS_ASSERTION(!(mTextRun->GetFlags() &
919 : (gfxTextRunFactory::TEXT_UNUSED_FLAGS |
920 : nsTextFrameUtils::TEXT_UNUSED_FLAG)),
921 : "Flag set that should never be set! (memory safety error?)");
922 0 : if (mTextRun->GetFlags() & nsTextFrameUtils::TEXT_IS_TRANSFORMED) {
923 : nsTransformedTextRun* transformedTextRun =
924 0 : static_cast<nsTransformedTextRun*>(mTextRun);
925 0 : transformedTextRun->FinishSettingProperties(mContext);
926 : }
927 0 : }
928 :
929 : gfxTextRun* mTextRun;
930 : gfxContext* mContext;
931 : PRUint32 mOffsetIntoTextRun;
932 : bool mChangedBreaks;
933 : bool mExistingTextRun;
934 : };
935 :
936 : private:
937 : nsAutoTArray<MappedFlow,10> mMappedFlows;
938 : nsAutoTArray<nsTextFrame*,50> mLineBreakBeforeFrames;
939 : nsAutoTArray<nsAutoPtr<BreakSink>,10> mBreakSinks;
940 : nsAutoTArray<gfxTextRun*,5> mTextRunsToDelete;
941 : nsLineBreaker mLineBreaker;
942 : gfxTextRun* mCurrentFramesAllSameTextRun;
943 : gfxContext* mContext;
944 : nsIFrame* mLineContainer;
945 : nsTextFrame* mLastFrame;
946 : // The common ancestor of the current frame and the previous leaf frame
947 : // on the line, or null if there was no previous leaf frame.
948 : nsIFrame* mCommonAncestorWithLastFrame;
949 : // mMaxTextLength is an upper bound on the size of the text in all mapped frames
950 : // The value PR_UINT32_MAX represents overflow; text will be discarded
951 : PRUint32 mMaxTextLength;
952 : float mInflation;
953 : bool mDoubleByteText;
954 : bool mBidiEnabled;
955 : bool mStartOfLine;
956 : bool mSkipIncompleteTextRuns;
957 : bool mCanStopOnThisLine;
958 : nsTextFrame::TextRunType mWhichTextRun;
959 : PRUint8 mNextRunContextInfo;
960 : PRUint8 mCurrentRunContextInfo;
961 : };
962 :
963 : static nsIFrame*
964 0 : FindLineContainer(nsIFrame* aFrame)
965 : {
966 0 : while (aFrame && aFrame->CanContinueTextRun()) {
967 0 : aFrame = aFrame->GetParent();
968 : }
969 0 : return aFrame;
970 : }
971 :
972 : static bool
973 0 : IsLineBreakingWhiteSpace(PRUnichar aChar)
974 : {
975 : // 0x0A (\n) is not handled as white-space by the line breaker, since
976 : // we break before it, if it isn't transformed to a normal space.
977 : // (If we treat it as normal white-space then we'd only break after it.)
978 : // However, it does induce a line break or is converted to a regular
979 : // space, and either way it can be used to bound the region of text
980 : // that needs to be analyzed for line breaking.
981 0 : return nsLineBreaker::IsSpace(aChar) || aChar == 0x0A;
982 : }
983 :
984 : static bool
985 0 : TextContainsLineBreakerWhiteSpace(const void* aText, PRUint32 aLength,
986 : bool aIsDoubleByte)
987 : {
988 : PRUint32 i;
989 0 : if (aIsDoubleByte) {
990 0 : const PRUnichar* chars = static_cast<const PRUnichar*>(aText);
991 0 : for (i = 0; i < aLength; ++i) {
992 0 : if (IsLineBreakingWhiteSpace(chars[i]))
993 0 : return true;
994 : }
995 0 : return false;
996 : } else {
997 0 : const PRUint8* chars = static_cast<const PRUint8*>(aText);
998 0 : for (i = 0; i < aLength; ++i) {
999 0 : if (IsLineBreakingWhiteSpace(chars[i]))
1000 0 : return true;
1001 : }
1002 0 : return false;
1003 : }
1004 : }
1005 :
1006 : struct FrameTextTraversal {
1007 : // These fields identify which frames should be recursively scanned
1008 : // The first normal frame to scan (or null, if no such frame should be scanned)
1009 : nsIFrame* mFrameToScan;
1010 : // The first overflow frame to scan (or null, if no such frame should be scanned)
1011 : nsIFrame* mOverflowFrameToScan;
1012 : // Whether to scan the siblings of mFrameToDescendInto/mOverflowFrameToDescendInto
1013 : bool mScanSiblings;
1014 :
1015 : // These identify the boundaries of the context required for
1016 : // line breaking or textrun construction
1017 : bool mLineBreakerCanCrossFrameBoundary;
1018 : bool mTextRunCanCrossFrameBoundary;
1019 :
1020 0 : nsIFrame* NextFrameToScan() {
1021 : nsIFrame* f;
1022 0 : if (mFrameToScan) {
1023 0 : f = mFrameToScan;
1024 0 : mFrameToScan = mScanSiblings ? f->GetNextSibling() : nsnull;
1025 0 : } else if (mOverflowFrameToScan) {
1026 0 : f = mOverflowFrameToScan;
1027 0 : mOverflowFrameToScan = mScanSiblings ? f->GetNextSibling() : nsnull;
1028 : } else {
1029 0 : f = nsnull;
1030 : }
1031 0 : return f;
1032 : }
1033 : };
1034 :
1035 : static FrameTextTraversal
1036 0 : CanTextCrossFrameBoundary(nsIFrame* aFrame, nsIAtom* aType)
1037 : {
1038 0 : NS_ASSERTION(aType == aFrame->GetType(), "Wrong type");
1039 :
1040 : FrameTextTraversal result;
1041 :
1042 0 : bool continuesTextRun = aFrame->CanContinueTextRun();
1043 0 : if (aType == nsGkAtoms::placeholderFrame) {
1044 : // placeholders are "invisible", so a text run should be able to span
1045 : // across one. But don't descend into the out-of-flow.
1046 0 : result.mLineBreakerCanCrossFrameBoundary = true;
1047 0 : result.mOverflowFrameToScan = nsnull;
1048 0 : if (continuesTextRun) {
1049 : // ... Except for first-letter floats, which are really in-flow
1050 : // from the point of view of capitalization etc, so we'd better
1051 : // descend into them. But we actually need to break the textrun for
1052 : // first-letter floats since things look bad if, say, we try to make a
1053 : // ligature across the float boundary.
1054 : result.mFrameToScan =
1055 0 : (static_cast<nsPlaceholderFrame*>(aFrame))->GetOutOfFlowFrame();
1056 0 : result.mScanSiblings = false;
1057 0 : result.mTextRunCanCrossFrameBoundary = false;
1058 : } else {
1059 0 : result.mFrameToScan = nsnull;
1060 0 : result.mTextRunCanCrossFrameBoundary = true;
1061 : }
1062 : } else {
1063 0 : if (continuesTextRun) {
1064 0 : result.mFrameToScan = aFrame->GetFirstPrincipalChild();
1065 : result.mOverflowFrameToScan =
1066 0 : aFrame->GetFirstChild(nsIFrame::kOverflowList);
1067 0 : NS_WARN_IF_FALSE(!result.mOverflowFrameToScan,
1068 : "Scanning overflow inline frames is something we should avoid");
1069 0 : result.mScanSiblings = true;
1070 0 : result.mTextRunCanCrossFrameBoundary = true;
1071 0 : result.mLineBreakerCanCrossFrameBoundary = true;
1072 : } else {
1073 0 : result.mFrameToScan = nsnull;
1074 0 : result.mOverflowFrameToScan = nsnull;
1075 0 : result.mTextRunCanCrossFrameBoundary = false;
1076 0 : result.mLineBreakerCanCrossFrameBoundary = false;
1077 : }
1078 : }
1079 : return result;
1080 : }
1081 :
1082 : BuildTextRunsScanner::FindBoundaryResult
1083 0 : BuildTextRunsScanner::FindBoundaries(nsIFrame* aFrame, FindBoundaryState* aState)
1084 : {
1085 0 : nsIAtom* frameType = aFrame->GetType();
1086 : nsTextFrame* textFrame = frameType == nsGkAtoms::textFrame
1087 0 : ? static_cast<nsTextFrame*>(aFrame) : nsnull;
1088 0 : if (textFrame) {
1089 0 : if (aState->mLastTextFrame &&
1090 0 : textFrame != aState->mLastTextFrame->GetNextInFlow() &&
1091 0 : !ContinueTextRunAcrossFrames(aState->mLastTextFrame, textFrame)) {
1092 0 : aState->mSeenTextRunBoundaryOnThisLine = true;
1093 0 : if (aState->mSeenSpaceForLineBreakingOnThisLine)
1094 0 : return FB_FOUND_VALID_TEXTRUN_BOUNDARY;
1095 : }
1096 0 : if (!aState->mFirstTextFrame) {
1097 0 : aState->mFirstTextFrame = textFrame;
1098 : }
1099 0 : aState->mLastTextFrame = textFrame;
1100 : }
1101 :
1102 0 : if (aFrame == aState->mStopAtFrame)
1103 0 : return FB_STOPPED_AT_STOP_FRAME;
1104 :
1105 0 : if (textFrame) {
1106 0 : if (!aState->mSeenSpaceForLineBreakingOnThisLine) {
1107 0 : const nsTextFragment* frag = textFrame->GetContent()->GetText();
1108 0 : PRUint32 start = textFrame->GetContentOffset();
1109 0 : const void* text = frag->Is2b()
1110 0 : ? static_cast<const void*>(frag->Get2b() + start)
1111 0 : : static_cast<const void*>(frag->Get1b() + start);
1112 0 : if (TextContainsLineBreakerWhiteSpace(text, textFrame->GetContentLength(),
1113 0 : frag->Is2b())) {
1114 0 : aState->mSeenSpaceForLineBreakingOnThisLine = true;
1115 0 : if (aState->mSeenTextRunBoundaryOnLaterLine)
1116 0 : return FB_FOUND_VALID_TEXTRUN_BOUNDARY;
1117 : }
1118 : }
1119 0 : return FB_CONTINUE;
1120 : }
1121 :
1122 : FrameTextTraversal traversal =
1123 0 : CanTextCrossFrameBoundary(aFrame, frameType);
1124 0 : if (!traversal.mTextRunCanCrossFrameBoundary) {
1125 0 : aState->mSeenTextRunBoundaryOnThisLine = true;
1126 0 : if (aState->mSeenSpaceForLineBreakingOnThisLine)
1127 0 : return FB_FOUND_VALID_TEXTRUN_BOUNDARY;
1128 : }
1129 :
1130 0 : for (nsIFrame* f = traversal.NextFrameToScan(); f;
1131 : f = traversal.NextFrameToScan()) {
1132 0 : FindBoundaryResult result = FindBoundaries(f, aState);
1133 0 : if (result != FB_CONTINUE)
1134 0 : return result;
1135 : }
1136 :
1137 0 : if (!traversal.mTextRunCanCrossFrameBoundary) {
1138 0 : aState->mSeenTextRunBoundaryOnThisLine = true;
1139 0 : if (aState->mSeenSpaceForLineBreakingOnThisLine)
1140 0 : return FB_FOUND_VALID_TEXTRUN_BOUNDARY;
1141 : }
1142 :
1143 0 : return FB_CONTINUE;
1144 : }
1145 :
1146 : // build text runs for the 200 lines following aForFrame, and stop after that
1147 : // when we get a chance.
1148 : #define NUM_LINES_TO_BUILD_TEXT_RUNS 200
1149 :
1150 : /**
1151 : * General routine for building text runs. This is hairy because of the need
1152 : * to build text runs that span content nodes.
1153 : *
1154 : * @param aForFrameLine the line containing aForFrame; if null, we'll figure
1155 : * out the line (slowly)
1156 : * @param aLineContainer the line container containing aForFrame; if null,
1157 : * we'll walk the ancestors to find it. It's required to be non-null when
1158 : * aForFrameLine is non-null.
1159 : */
1160 : static void
1161 0 : BuildTextRuns(gfxContext* aContext, nsTextFrame* aForFrame,
1162 : nsIFrame* aLineContainer,
1163 : const nsLineList::iterator* aForFrameLine,
1164 : nsTextFrame::TextRunType aWhichTextRun, float aInflation)
1165 : {
1166 0 : NS_ASSERTION(aForFrame || aLineContainer,
1167 : "One of aForFrame or aLineContainer must be set!");
1168 0 : NS_ASSERTION(!aForFrameLine || aLineContainer,
1169 : "line but no line container");
1170 :
1171 0 : nsIFrame* lineContainerChild = aForFrame;
1172 0 : if (!aLineContainer) {
1173 0 : if (aForFrame->IsFloatingFirstLetterChild()) {
1174 0 : lineContainerChild = aForFrame->PresContext()->PresShell()->
1175 0 : GetPlaceholderFrameFor(aForFrame->GetParent());
1176 : }
1177 0 : aLineContainer = FindLineContainer(lineContainerChild);
1178 : } else {
1179 0 : NS_ASSERTION(!aForFrame ||
1180 : (aLineContainer == FindLineContainer(aForFrame) ||
1181 : (aLineContainer->GetType() == nsGkAtoms::letterFrame &&
1182 : aLineContainer->GetStyleDisplay()->IsFloating())),
1183 : "Wrong line container hint");
1184 : }
1185 :
1186 0 : nsPresContext* presContext = aLineContainer->PresContext();
1187 : BuildTextRunsScanner scanner(presContext, aContext, aLineContainer,
1188 0 : aWhichTextRun, aInflation);
1189 :
1190 0 : nsBlockFrame* block = nsLayoutUtils::GetAsBlock(aLineContainer);
1191 :
1192 0 : if (!block) {
1193 0 : NS_ASSERTION(!aLineContainer->GetPrevInFlow() && !aLineContainer->GetNextInFlow(),
1194 : "Breakable non-block line containers not supported");
1195 : // Just loop through all the children of the linecontainer ... it's really
1196 : // just one line
1197 0 : scanner.SetAtStartOfLine();
1198 0 : scanner.SetCommonAncestorWithLastFrame(nsnull);
1199 0 : nsIFrame* child = aLineContainer->GetFirstPrincipalChild();
1200 0 : while (child) {
1201 0 : scanner.ScanFrame(child);
1202 0 : child = child->GetNextSibling();
1203 : }
1204 : // Set mStartOfLine so FlushFrames knows its textrun ends a line
1205 0 : scanner.SetAtStartOfLine();
1206 0 : scanner.FlushFrames(true, false);
1207 : return;
1208 : }
1209 :
1210 : // Find the line containing 'lineContainerChild'.
1211 :
1212 0 : bool isValid = true;
1213 0 : nsBlockInFlowLineIterator backIterator(block, &isValid);
1214 0 : if (aForFrameLine) {
1215 0 : backIterator = nsBlockInFlowLineIterator(block, *aForFrameLine);
1216 : } else {
1217 0 : backIterator = nsBlockInFlowLineIterator(block, lineContainerChild, &isValid);
1218 0 : NS_ASSERTION(isValid, "aForFrame not found in block, someone lied to us");
1219 0 : NS_ASSERTION(backIterator.GetContainer() == block,
1220 : "Someone lied to us about the block");
1221 : }
1222 0 : nsBlockFrame::line_iterator startLine = backIterator.GetLine();
1223 :
1224 : // Find a line where we can start building text runs. We choose the last line
1225 : // where:
1226 : // -- there is a textrun boundary between the start of the line and the
1227 : // start of aForFrame
1228 : // -- there is a space between the start of the line and the textrun boundary
1229 : // (this is so we can be sure the line breaks will be set properly
1230 : // on the textruns we construct).
1231 : // The possibly-partial text runs up to and including the first space
1232 : // are not reconstructed. We construct partial text runs for that text ---
1233 : // for the sake of simplifying the code and feeding the linebreaker ---
1234 : // but we discard them instead of assigning them to frames.
1235 : // This is a little awkward because we traverse lines in the reverse direction
1236 : // but we traverse the frames in each line in the forward direction.
1237 0 : nsBlockInFlowLineIterator forwardIterator = backIterator;
1238 0 : nsIFrame* stopAtFrame = lineContainerChild;
1239 0 : nsTextFrame* nextLineFirstTextFrame = nsnull;
1240 0 : bool seenTextRunBoundaryOnLaterLine = false;
1241 0 : bool mayBeginInTextRun = true;
1242 0 : while (true) {
1243 0 : forwardIterator = backIterator;
1244 0 : nsBlockFrame::line_iterator line = backIterator.GetLine();
1245 0 : if (!backIterator.Prev() || backIterator.GetLine()->IsBlock()) {
1246 0 : mayBeginInTextRun = false;
1247 0 : break;
1248 : }
1249 :
1250 : BuildTextRunsScanner::FindBoundaryState state = { stopAtFrame, nsnull, nsnull,
1251 0 : bool(seenTextRunBoundaryOnLaterLine), false, false };
1252 0 : nsIFrame* child = line->mFirstChild;
1253 0 : bool foundBoundary = false;
1254 : PRInt32 i;
1255 0 : for (i = line->GetChildCount() - 1; i >= 0; --i) {
1256 : BuildTextRunsScanner::FindBoundaryResult result =
1257 0 : scanner.FindBoundaries(child, &state);
1258 0 : if (result == BuildTextRunsScanner::FB_FOUND_VALID_TEXTRUN_BOUNDARY) {
1259 0 : foundBoundary = true;
1260 0 : break;
1261 0 : } else if (result == BuildTextRunsScanner::FB_STOPPED_AT_STOP_FRAME) {
1262 0 : break;
1263 : }
1264 0 : child = child->GetNextSibling();
1265 : }
1266 0 : if (foundBoundary)
1267 0 : break;
1268 0 : if (!stopAtFrame && state.mLastTextFrame && nextLineFirstTextFrame &&
1269 0 : !scanner.ContinueTextRunAcrossFrames(state.mLastTextFrame, nextLineFirstTextFrame)) {
1270 : // Found a usable textrun boundary at the end of the line
1271 0 : if (state.mSeenSpaceForLineBreakingOnThisLine)
1272 0 : break;
1273 0 : seenTextRunBoundaryOnLaterLine = true;
1274 0 : } else if (state.mSeenTextRunBoundaryOnThisLine) {
1275 0 : seenTextRunBoundaryOnLaterLine = true;
1276 : }
1277 0 : stopAtFrame = nsnull;
1278 0 : if (state.mFirstTextFrame) {
1279 0 : nextLineFirstTextFrame = state.mFirstTextFrame;
1280 : }
1281 : }
1282 0 : scanner.SetSkipIncompleteTextRuns(mayBeginInTextRun);
1283 :
1284 : // Now iterate over all text frames starting from the current line. First-in-flow
1285 : // text frames will be accumulated into textRunFrames as we go. When a
1286 : // text run boundary is required we flush textRunFrames ((re)building their
1287 : // gfxTextRuns as necessary).
1288 0 : bool seenStartLine = false;
1289 0 : PRUint32 linesAfterStartLine = 0;
1290 0 : do {
1291 0 : nsBlockFrame::line_iterator line = forwardIterator.GetLine();
1292 0 : if (line->IsBlock())
1293 0 : break;
1294 0 : line->SetInvalidateTextRuns(false);
1295 0 : scanner.SetAtStartOfLine();
1296 0 : scanner.SetCommonAncestorWithLastFrame(nsnull);
1297 0 : nsIFrame* child = line->mFirstChild;
1298 : PRInt32 i;
1299 0 : for (i = line->GetChildCount() - 1; i >= 0; --i) {
1300 0 : scanner.ScanFrame(child);
1301 0 : child = child->GetNextSibling();
1302 : }
1303 0 : if (line.get() == startLine.get()) {
1304 0 : seenStartLine = true;
1305 : }
1306 0 : if (seenStartLine) {
1307 0 : ++linesAfterStartLine;
1308 0 : if (linesAfterStartLine >= NUM_LINES_TO_BUILD_TEXT_RUNS && scanner.CanStopOnThisLine()) {
1309 : // Don't flush frames; we may be in the middle of a textrun
1310 : // that we can't end here. That's OK, we just won't build it.
1311 : // Note that we must already have finished the textrun for aForFrame,
1312 : // because we've seen the end of a textrun in a line after the line
1313 : // containing aForFrame.
1314 0 : scanner.FlushLineBreaks(nsnull);
1315 : // This flushes out mMappedFlows and mLineBreakBeforeFrames, which
1316 : // silences assertions in the scanner destructor.
1317 0 : scanner.ResetRunInfo();
1318 : return;
1319 : }
1320 : }
1321 : } while (forwardIterator.Next());
1322 :
1323 : // Set mStartOfLine so FlushFrames knows its textrun ends a line
1324 0 : scanner.SetAtStartOfLine();
1325 0 : scanner.FlushFrames(true, false);
1326 : }
1327 :
1328 : static PRUnichar*
1329 0 : ExpandBuffer(PRUnichar* aDest, PRUint8* aSrc, PRUint32 aCount)
1330 : {
1331 0 : while (aCount) {
1332 0 : *aDest = *aSrc;
1333 0 : ++aDest;
1334 0 : ++aSrc;
1335 0 : --aCount;
1336 : }
1337 0 : return aDest;
1338 : }
1339 :
1340 0 : bool BuildTextRunsScanner::IsTextRunValidForMappedFlows(gfxTextRun* aTextRun)
1341 : {
1342 0 : if (aTextRun->GetFlags() & nsTextFrameUtils::TEXT_IS_SIMPLE_FLOW)
1343 0 : return mMappedFlows.Length() == 1 &&
1344 0 : mMappedFlows[0].mStartFrame == static_cast<nsTextFrame*>(aTextRun->GetUserData()) &&
1345 0 : mMappedFlows[0].mEndFrame == nsnull;
1346 :
1347 0 : TextRunUserData* userData = static_cast<TextRunUserData*>(aTextRun->GetUserData());
1348 0 : if (userData->mMappedFlowCount != mMappedFlows.Length())
1349 0 : return false;
1350 : PRUint32 i;
1351 0 : for (i = 0; i < mMappedFlows.Length(); ++i) {
1352 0 : if (userData->mMappedFlows[i].mStartFrame != mMappedFlows[i].mStartFrame ||
1353 0 : PRInt32(userData->mMappedFlows[i].mContentLength) !=
1354 0 : mMappedFlows[i].GetContentEnd() - mMappedFlows[i].mStartFrame->GetContentOffset())
1355 0 : return false;
1356 : }
1357 0 : return true;
1358 : }
1359 :
1360 : /**
1361 : * This gets called when we need to make a text run for the current list of
1362 : * frames.
1363 : */
1364 0 : void BuildTextRunsScanner::FlushFrames(bool aFlushLineBreaks, bool aSuppressTrailingBreak)
1365 : {
1366 0 : gfxTextRun* textRun = nsnull;
1367 0 : if (!mMappedFlows.IsEmpty()) {
1368 0 : if (!mSkipIncompleteTextRuns && mCurrentFramesAllSameTextRun &&
1369 0 : ((mCurrentFramesAllSameTextRun->GetFlags() & nsTextFrameUtils::TEXT_INCOMING_WHITESPACE) != 0) ==
1370 : ((mCurrentRunContextInfo & nsTextFrameUtils::INCOMING_WHITESPACE) != 0) &&
1371 0 : ((mCurrentFramesAllSameTextRun->GetFlags() & gfxTextRunFactory::TEXT_INCOMING_ARABICCHAR) != 0) ==
1372 : ((mCurrentRunContextInfo & nsTextFrameUtils::INCOMING_ARABICCHAR) != 0) &&
1373 0 : IsTextRunValidForMappedFlows(mCurrentFramesAllSameTextRun)) {
1374 : // Optimization: We do not need to (re)build the textrun.
1375 0 : textRun = mCurrentFramesAllSameTextRun;
1376 :
1377 : // Feed this run's text into the linebreaker to provide context.
1378 0 : if (!SetupLineBreakerContext(textRun)) {
1379 0 : return;
1380 : }
1381 :
1382 : // Update mNextRunContextInfo appropriately
1383 0 : mNextRunContextInfo = nsTextFrameUtils::INCOMING_NONE;
1384 0 : if (textRun->GetFlags() & nsTextFrameUtils::TEXT_TRAILING_WHITESPACE) {
1385 0 : mNextRunContextInfo |= nsTextFrameUtils::INCOMING_WHITESPACE;
1386 : }
1387 0 : if (textRun->GetFlags() & gfxTextRunFactory::TEXT_TRAILING_ARABICCHAR) {
1388 0 : mNextRunContextInfo |= nsTextFrameUtils::INCOMING_ARABICCHAR;
1389 : }
1390 : } else {
1391 0 : AutoFallibleTArray<PRUint8,BIG_TEXT_NODE_SIZE> buffer;
1392 0 : PRUint32 bufferSize = mMaxTextLength*(mDoubleByteText ? 2 : 1);
1393 0 : if (bufferSize < mMaxTextLength || bufferSize == PR_UINT32_MAX ||
1394 0 : !buffer.AppendElements(bufferSize)) {
1395 : return;
1396 : }
1397 0 : textRun = BuildTextRunForFrames(buffer.Elements());
1398 : }
1399 : }
1400 :
1401 0 : if (aFlushLineBreaks) {
1402 0 : FlushLineBreaks(aSuppressTrailingBreak ? nsnull : textRun);
1403 : }
1404 :
1405 0 : mCanStopOnThisLine = true;
1406 0 : ResetRunInfo();
1407 : }
1408 :
1409 0 : void BuildTextRunsScanner::FlushLineBreaks(gfxTextRun* aTrailingTextRun)
1410 : {
1411 : bool trailingLineBreak;
1412 0 : nsresult rv = mLineBreaker.Reset(&trailingLineBreak);
1413 : // textRun may be null for various reasons, including because we constructed
1414 : // a partial textrun just to get the linebreaker and other state set up
1415 : // to build the next textrun.
1416 0 : if (NS_SUCCEEDED(rv) && trailingLineBreak && aTrailingTextRun) {
1417 0 : aTrailingTextRun->SetFlagBits(nsTextFrameUtils::TEXT_HAS_TRAILING_BREAK);
1418 : }
1419 :
1420 : PRUint32 i;
1421 0 : for (i = 0; i < mBreakSinks.Length(); ++i) {
1422 0 : if (!mBreakSinks[i]->mExistingTextRun || mBreakSinks[i]->mChangedBreaks) {
1423 : // TODO cause frames associated with the textrun to be reflowed, if they
1424 : // aren't being reflowed already!
1425 : }
1426 0 : mBreakSinks[i]->Finish();
1427 : }
1428 0 : mBreakSinks.Clear();
1429 :
1430 0 : for (i = 0; i < mTextRunsToDelete.Length(); ++i) {
1431 0 : gfxTextRun* deleteTextRun = mTextRunsToDelete[i];
1432 0 : gTextRuns->RemoveFromCache(deleteTextRun);
1433 0 : delete deleteTextRun;
1434 : }
1435 0 : mTextRunsToDelete.Clear();
1436 0 : }
1437 :
1438 0 : void BuildTextRunsScanner::AccumulateRunInfo(nsTextFrame* aFrame)
1439 : {
1440 0 : if (mMaxTextLength != PR_UINT32_MAX) {
1441 0 : NS_ASSERTION(mMaxTextLength < PR_UINT32_MAX - aFrame->GetContentLength(), "integer overflow");
1442 0 : if (mMaxTextLength >= PR_UINT32_MAX - aFrame->GetContentLength()) {
1443 0 : mMaxTextLength = PR_UINT32_MAX;
1444 : } else {
1445 0 : mMaxTextLength += aFrame->GetContentLength();
1446 : }
1447 : }
1448 0 : mDoubleByteText |= aFrame->GetContent()->GetText()->Is2b();
1449 0 : mLastFrame = aFrame;
1450 0 : mCommonAncestorWithLastFrame = aFrame->GetParent();
1451 :
1452 0 : MappedFlow* mappedFlow = &mMappedFlows[mMappedFlows.Length() - 1];
1453 0 : NS_ASSERTION(mappedFlow->mStartFrame == aFrame ||
1454 : mappedFlow->GetContentEnd() == aFrame->GetContentOffset(),
1455 : "Overlapping or discontiguous frames => BAD");
1456 0 : mappedFlow->mEndFrame = static_cast<nsTextFrame*>(aFrame->GetNextContinuation());
1457 0 : if (mCurrentFramesAllSameTextRun != aFrame->GetTextRun(mWhichTextRun)) {
1458 0 : mCurrentFramesAllSameTextRun = nsnull;
1459 : }
1460 :
1461 0 : if (mStartOfLine) {
1462 0 : mLineBreakBeforeFrames.AppendElement(aFrame);
1463 0 : mStartOfLine = false;
1464 : }
1465 0 : }
1466 :
1467 0 : static nscoord StyleToCoord(const nsStyleCoord& aCoord)
1468 : {
1469 0 : if (eStyleUnit_Coord == aCoord.GetUnit()) {
1470 0 : return aCoord.GetCoordValue();
1471 : } else {
1472 0 : return 0;
1473 : }
1474 : }
1475 :
1476 : static bool
1477 0 : HasTerminalNewline(const nsTextFrame* aFrame)
1478 : {
1479 0 : if (aFrame->GetContentLength() == 0)
1480 0 : return false;
1481 0 : const nsTextFragment* frag = aFrame->GetContent()->GetText();
1482 0 : return frag->CharAt(aFrame->GetContentEnd() - 1) == '\n';
1483 : }
1484 :
1485 : bool
1486 0 : BuildTextRunsScanner::ContinueTextRunAcrossFrames(nsTextFrame* aFrame1, nsTextFrame* aFrame2)
1487 : {
1488 : // We don't need to check font size inflation, since
1489 : // |FindLineContainer| above (via |nsIFrame::CanContinueTextRun|)
1490 : // ensures that text runs never cross block boundaries. This means
1491 : // that the font size inflation on all text frames in the text run is
1492 : // already guaranteed to be the same as each other (and for the line
1493 : // container).
1494 0 : if (mBidiEnabled &&
1495 0 : NS_GET_EMBEDDING_LEVEL(aFrame1) != NS_GET_EMBEDDING_LEVEL(aFrame2))
1496 0 : return false;
1497 :
1498 0 : nsStyleContext* sc1 = aFrame1->GetStyleContext();
1499 0 : const nsStyleText* textStyle1 = sc1->GetStyleText();
1500 : // If the first frame ends in a preformatted newline, then we end the textrun
1501 : // here. This avoids creating giant textruns for an entire plain text file.
1502 : // Note that we create a single text frame for a preformatted text node,
1503 : // even if it has newlines in it, so typically we won't see trailing newlines
1504 : // until after reflow has broken up the frame into one (or more) frames per
1505 : // line. That's OK though.
1506 0 : if (textStyle1->NewlineIsSignificant() && HasTerminalNewline(aFrame1))
1507 0 : return false;
1508 :
1509 0 : if (aFrame1->GetContent() == aFrame2->GetContent() &&
1510 0 : aFrame1->GetNextInFlow() != aFrame2) {
1511 : // aFrame2 must be a non-fluid continuation of aFrame1. This can happen
1512 : // sometimes when the unicode-bidi property is used; the bidi resolver
1513 : // breaks text into different frames even though the text has the same
1514 : // direction. We can't allow these two frames to share the same textrun
1515 : // because that would violate our invariant that two flows in the same
1516 : // textrun have different content elements.
1517 0 : return false;
1518 : }
1519 :
1520 0 : nsStyleContext* sc2 = aFrame2->GetStyleContext();
1521 0 : if (sc1 == sc2)
1522 0 : return true;
1523 0 : const nsStyleFont* fontStyle1 = sc1->GetStyleFont();
1524 0 : const nsStyleFont* fontStyle2 = sc2->GetStyleFont();
1525 0 : const nsStyleText* textStyle2 = sc2->GetStyleText();
1526 0 : return fontStyle1->mFont.BaseEquals(fontStyle2->mFont) &&
1527 0 : sc1->GetStyleFont()->mLanguage == sc2->GetStyleFont()->mLanguage &&
1528 0 : nsLayoutUtils::GetTextRunFlagsForStyle(sc1, textStyle1, fontStyle1) ==
1529 0 : nsLayoutUtils::GetTextRunFlagsForStyle(sc2, textStyle2, fontStyle2);
1530 : }
1531 :
1532 0 : void BuildTextRunsScanner::ScanFrame(nsIFrame* aFrame)
1533 : {
1534 : // First check if we can extend the current mapped frame block. This is common.
1535 0 : if (mMappedFlows.Length() > 0) {
1536 0 : MappedFlow* mappedFlow = &mMappedFlows[mMappedFlows.Length() - 1];
1537 0 : if (mappedFlow->mEndFrame == aFrame &&
1538 0 : (aFrame->GetStateBits() & NS_FRAME_IS_FLUID_CONTINUATION)) {
1539 0 : NS_ASSERTION(aFrame->GetType() == nsGkAtoms::textFrame,
1540 : "Flow-sibling of a text frame is not a text frame?");
1541 :
1542 : // Don't do this optimization if mLastFrame has a terminal newline...
1543 : // it's quite likely preformatted and we might want to end the textrun here.
1544 : // This is almost always true:
1545 0 : if (mLastFrame->GetStyleContext() == aFrame->GetStyleContext() &&
1546 0 : !HasTerminalNewline(mLastFrame)) {
1547 0 : AccumulateRunInfo(static_cast<nsTextFrame*>(aFrame));
1548 0 : return;
1549 : }
1550 : }
1551 : }
1552 :
1553 0 : nsIAtom* frameType = aFrame->GetType();
1554 : // Now see if we can add a new set of frames to the current textrun
1555 0 : if (frameType == nsGkAtoms::textFrame) {
1556 0 : nsTextFrame* frame = static_cast<nsTextFrame*>(aFrame);
1557 :
1558 0 : if (mLastFrame) {
1559 0 : if (!ContinueTextRunAcrossFrames(mLastFrame, frame)) {
1560 0 : FlushFrames(false, false);
1561 : } else {
1562 0 : if (mLastFrame->GetContent() == frame->GetContent()) {
1563 0 : AccumulateRunInfo(frame);
1564 0 : return;
1565 : }
1566 : }
1567 : }
1568 :
1569 0 : MappedFlow* mappedFlow = mMappedFlows.AppendElement();
1570 0 : if (!mappedFlow)
1571 0 : return;
1572 :
1573 0 : mappedFlow->mStartFrame = frame;
1574 0 : mappedFlow->mAncestorControllingInitialBreak = mCommonAncestorWithLastFrame;
1575 :
1576 0 : AccumulateRunInfo(frame);
1577 0 : if (mMappedFlows.Length() == 1) {
1578 0 : mCurrentFramesAllSameTextRun = frame->GetTextRun(mWhichTextRun);
1579 0 : mCurrentRunContextInfo = mNextRunContextInfo;
1580 : }
1581 0 : return;
1582 : }
1583 :
1584 : FrameTextTraversal traversal =
1585 0 : CanTextCrossFrameBoundary(aFrame, frameType);
1586 0 : bool isBR = frameType == nsGkAtoms::brFrame;
1587 0 : if (!traversal.mLineBreakerCanCrossFrameBoundary) {
1588 : // BR frames are special. We do not need or want to record a break opportunity
1589 : // before a BR frame.
1590 0 : FlushFrames(true, isBR);
1591 0 : mCommonAncestorWithLastFrame = aFrame;
1592 0 : mNextRunContextInfo &= ~nsTextFrameUtils::INCOMING_WHITESPACE;
1593 0 : mStartOfLine = false;
1594 0 : } else if (!traversal.mTextRunCanCrossFrameBoundary) {
1595 0 : FlushFrames(false, false);
1596 : }
1597 :
1598 0 : for (nsIFrame* f = traversal.NextFrameToScan(); f;
1599 : f = traversal.NextFrameToScan()) {
1600 0 : ScanFrame(f);
1601 : }
1602 :
1603 0 : if (!traversal.mLineBreakerCanCrossFrameBoundary) {
1604 : // Really if we're a BR frame this is unnecessary since descendInto will be
1605 : // false. In fact this whole "if" statement should move into the descendInto.
1606 0 : FlushFrames(true, isBR);
1607 0 : mCommonAncestorWithLastFrame = aFrame;
1608 0 : mNextRunContextInfo &= ~nsTextFrameUtils::INCOMING_WHITESPACE;
1609 0 : } else if (!traversal.mTextRunCanCrossFrameBoundary) {
1610 0 : FlushFrames(false, false);
1611 : }
1612 :
1613 0 : LiftCommonAncestorWithLastFrameToParent(aFrame->GetParent());
1614 : }
1615 :
1616 : nsTextFrame*
1617 0 : BuildTextRunsScanner::GetNextBreakBeforeFrame(PRUint32* aIndex)
1618 : {
1619 0 : PRUint32 index = *aIndex;
1620 0 : if (index >= mLineBreakBeforeFrames.Length())
1621 0 : return nsnull;
1622 0 : *aIndex = index + 1;
1623 0 : return static_cast<nsTextFrame*>(mLineBreakBeforeFrames.ElementAt(index));
1624 : }
1625 :
1626 : static PRUint32
1627 0 : GetSpacingFlags(nscoord spacing)
1628 : {
1629 0 : return spacing ? gfxTextRunFactory::TEXT_ENABLE_SPACING : 0;
1630 : }
1631 :
1632 : static gfxFontGroup*
1633 0 : GetFontGroupForFrame(nsIFrame* aFrame, float aFontSizeInflation,
1634 : nsFontMetrics** aOutFontMetrics = nsnull)
1635 : {
1636 0 : if (aOutFontMetrics)
1637 0 : *aOutFontMetrics = nsnull;
1638 :
1639 0 : nsRefPtr<nsFontMetrics> metrics;
1640 : nsLayoutUtils::GetFontMetricsForFrame(aFrame, getter_AddRefs(metrics),
1641 0 : aFontSizeInflation);
1642 :
1643 0 : if (!metrics)
1644 0 : return nsnull;
1645 :
1646 0 : if (aOutFontMetrics) {
1647 0 : *aOutFontMetrics = metrics;
1648 0 : NS_ADDREF(*aOutFontMetrics);
1649 : }
1650 : // XXX this is a bit bogus, we're releasing 'metrics' so the
1651 : // returned font-group might actually be torn down, although because
1652 : // of the way the device context caches font metrics, this seems to
1653 : // not actually happen. But we should fix this.
1654 0 : return metrics->GetThebesFontGroup();
1655 : }
1656 :
1657 : static already_AddRefed<gfxContext>
1658 0 : GetReferenceRenderingContext(nsTextFrame* aTextFrame, nsRenderingContext* aRC)
1659 : {
1660 0 : nsRefPtr<nsRenderingContext> tmp = aRC;
1661 0 : if (!tmp) {
1662 0 : tmp = aTextFrame->PresContext()->PresShell()->GetReferenceRenderingContext();
1663 0 : if (!tmp)
1664 0 : return nsnull;
1665 : }
1666 :
1667 0 : gfxContext* ctx = tmp->ThebesContext();
1668 0 : NS_ADDREF(ctx);
1669 0 : return ctx;
1670 : }
1671 :
1672 : /**
1673 : * The returned textrun must be deleted when no longer needed.
1674 : */
1675 : static gfxTextRun*
1676 0 : GetHyphenTextRun(gfxTextRun* aTextRun, gfxContext* aContext, nsTextFrame* aTextFrame)
1677 : {
1678 0 : nsRefPtr<gfxContext> ctx = aContext;
1679 0 : if (!ctx) {
1680 0 : ctx = GetReferenceRenderingContext(aTextFrame, nsnull);
1681 : }
1682 0 : if (!ctx)
1683 0 : return nsnull;
1684 :
1685 0 : gfxFontGroup* fontGroup = aTextRun->GetFontGroup();
1686 0 : PRUint32 flags = gfxFontGroup::TEXT_IS_PERSISTENT;
1687 :
1688 : // only use U+2010 if it is supported by the first font in the group;
1689 : // it's better to use ASCII '-' from the primary font than to fall back to U+2010
1690 : // from some other, possibly poorly-matching face
1691 : static const PRUnichar unicodeHyphen = 0x2010;
1692 0 : gfxFont *font = fontGroup->GetFontAt(0);
1693 0 : if (font && font->HasCharacter(unicodeHyphen)) {
1694 : return fontGroup->MakeTextRun(&unicodeHyphen, 1, ctx,
1695 0 : aTextRun->GetAppUnitsPerDevUnit(), flags);
1696 : }
1697 :
1698 : static const PRUint8 dash = '-';
1699 : return fontGroup->MakeTextRun(&dash, 1, ctx,
1700 0 : aTextRun->GetAppUnitsPerDevUnit(), flags);
1701 : }
1702 :
1703 : static gfxFont::Metrics
1704 0 : GetFirstFontMetrics(gfxFontGroup* aFontGroup)
1705 : {
1706 0 : if (!aFontGroup)
1707 0 : return gfxFont::Metrics();
1708 0 : gfxFont* font = aFontGroup->GetFontAt(0);
1709 0 : if (!font)
1710 0 : return gfxFont::Metrics();
1711 0 : return font->GetMetrics();
1712 : }
1713 :
1714 : PR_STATIC_ASSERT(NS_STYLE_WHITESPACE_NORMAL == 0);
1715 : PR_STATIC_ASSERT(NS_STYLE_WHITESPACE_PRE == 1);
1716 : PR_STATIC_ASSERT(NS_STYLE_WHITESPACE_NOWRAP == 2);
1717 : PR_STATIC_ASSERT(NS_STYLE_WHITESPACE_PRE_WRAP == 3);
1718 : PR_STATIC_ASSERT(NS_STYLE_WHITESPACE_PRE_LINE == 4);
1719 :
1720 : static const nsTextFrameUtils::CompressionMode CSSWhitespaceToCompressionMode[] =
1721 : {
1722 : nsTextFrameUtils::COMPRESS_WHITESPACE_NEWLINE, // normal
1723 : nsTextFrameUtils::COMPRESS_NONE, // pre
1724 : nsTextFrameUtils::COMPRESS_WHITESPACE_NEWLINE, // nowrap
1725 : nsTextFrameUtils::COMPRESS_NONE, // pre-wrap
1726 : nsTextFrameUtils::COMPRESS_WHITESPACE // pre-line
1727 : };
1728 :
1729 : gfxTextRun*
1730 0 : BuildTextRunsScanner::BuildTextRunForFrames(void* aTextBuffer)
1731 : {
1732 0 : gfxSkipCharsBuilder builder;
1733 :
1734 0 : const void* textPtr = aTextBuffer;
1735 0 : bool anySmallcapsStyle = false;
1736 0 : bool anyTextTransformStyle = false;
1737 0 : PRUint32 textFlags = nsTextFrameUtils::TEXT_NO_BREAKS;
1738 :
1739 0 : if (mCurrentRunContextInfo & nsTextFrameUtils::INCOMING_WHITESPACE) {
1740 0 : textFlags |= nsTextFrameUtils::TEXT_INCOMING_WHITESPACE;
1741 : }
1742 0 : if (mCurrentRunContextInfo & nsTextFrameUtils::INCOMING_ARABICCHAR) {
1743 0 : textFlags |= gfxTextRunFactory::TEXT_INCOMING_ARABICCHAR;
1744 : }
1745 :
1746 0 : nsAutoTArray<PRInt32,50> textBreakPoints;
1747 : TextRunUserData dummyData;
1748 : TextRunMappedFlow dummyMappedFlow;
1749 :
1750 : TextRunUserData* userData;
1751 : TextRunUserData* userDataToDestroy;
1752 : // If the situation is particularly simple (and common) we don't need to
1753 : // allocate userData.
1754 0 : if (mMappedFlows.Length() == 1 && !mMappedFlows[0].mEndFrame &&
1755 0 : mMappedFlows[0].mStartFrame->GetContentOffset() == 0) {
1756 0 : userData = &dummyData;
1757 0 : userDataToDestroy = nsnull;
1758 0 : dummyData.mMappedFlows = &dummyMappedFlow;
1759 : } else {
1760 : userData = static_cast<TextRunUserData*>
1761 0 : (nsMemory::Alloc(sizeof(TextRunUserData) + mMappedFlows.Length()*sizeof(TextRunMappedFlow)));
1762 0 : userDataToDestroy = userData;
1763 0 : userData->mMappedFlows = reinterpret_cast<TextRunMappedFlow*>(userData + 1);
1764 : }
1765 0 : userData->mMappedFlowCount = mMappedFlows.Length();
1766 0 : userData->mLastFlowIndex = 0;
1767 :
1768 0 : PRUint32 currentTransformedTextOffset = 0;
1769 :
1770 0 : PRUint32 nextBreakIndex = 0;
1771 0 : nsTextFrame* nextBreakBeforeFrame = GetNextBreakBeforeFrame(&nextBreakIndex);
1772 : bool enabledJustification = mLineContainer &&
1773 0 : (mLineContainer->GetStyleText()->mTextAlign == NS_STYLE_TEXT_ALIGN_JUSTIFY ||
1774 0 : mLineContainer->GetStyleText()->mTextAlignLast == NS_STYLE_TEXT_ALIGN_JUSTIFY);
1775 :
1776 : PRUint32 i;
1777 0 : const nsStyleText* textStyle = nsnull;
1778 0 : const nsStyleFont* fontStyle = nsnull;
1779 0 : nsStyleContext* lastStyleContext = nsnull;
1780 0 : for (i = 0; i < mMappedFlows.Length(); ++i) {
1781 0 : MappedFlow* mappedFlow = &mMappedFlows[i];
1782 0 : nsTextFrame* f = mappedFlow->mStartFrame;
1783 :
1784 0 : lastStyleContext = f->GetStyleContext();
1785 : // Detect use of text-transform or font-variant anywhere in the run
1786 0 : textStyle = f->GetStyleText();
1787 0 : if (NS_STYLE_TEXT_TRANSFORM_NONE != textStyle->mTextTransform) {
1788 0 : anyTextTransformStyle = true;
1789 : }
1790 0 : textFlags |= GetSpacingFlags(StyleToCoord(textStyle->mLetterSpacing));
1791 0 : textFlags |= GetSpacingFlags(textStyle->mWordSpacing);
1792 : nsTextFrameUtils::CompressionMode compression =
1793 0 : CSSWhitespaceToCompressionMode[textStyle->mWhiteSpace];
1794 0 : if (enabledJustification && !textStyle->WhiteSpaceIsSignificant()) {
1795 0 : textFlags |= gfxTextRunFactory::TEXT_ENABLE_SPACING;
1796 : }
1797 0 : fontStyle = f->GetStyleFont();
1798 0 : if (NS_STYLE_FONT_VARIANT_SMALL_CAPS == fontStyle->mFont.variant) {
1799 0 : anySmallcapsStyle = true;
1800 : }
1801 :
1802 : // Figure out what content is included in this flow.
1803 0 : nsIContent* content = f->GetContent();
1804 0 : const nsTextFragment* frag = content->GetText();
1805 0 : PRInt32 contentStart = mappedFlow->mStartFrame->GetContentOffset();
1806 0 : PRInt32 contentEnd = mappedFlow->GetContentEnd();
1807 0 : PRInt32 contentLength = contentEnd - contentStart;
1808 :
1809 0 : TextRunMappedFlow* newFlow = &userData->mMappedFlows[i];
1810 0 : newFlow->mStartFrame = mappedFlow->mStartFrame;
1811 0 : newFlow->mDOMOffsetToBeforeTransformOffset = builder.GetCharCount() -
1812 0 : mappedFlow->mStartFrame->GetContentOffset();
1813 0 : newFlow->mContentLength = contentLength;
1814 :
1815 0 : while (nextBreakBeforeFrame && nextBreakBeforeFrame->GetContent() == content) {
1816 : textBreakPoints.AppendElement(
1817 0 : nextBreakBeforeFrame->GetContentOffset() + newFlow->mDOMOffsetToBeforeTransformOffset);
1818 0 : nextBreakBeforeFrame = GetNextBreakBeforeFrame(&nextBreakIndex);
1819 : }
1820 :
1821 : PRUint32 analysisFlags;
1822 0 : if (frag->Is2b()) {
1823 0 : NS_ASSERTION(mDoubleByteText, "Wrong buffer char size!");
1824 0 : PRUnichar* bufStart = static_cast<PRUnichar*>(aTextBuffer);
1825 : PRUnichar* bufEnd = nsTextFrameUtils::TransformText(
1826 0 : frag->Get2b() + contentStart, contentLength, bufStart,
1827 0 : compression, &mNextRunContextInfo, &builder, &analysisFlags);
1828 0 : aTextBuffer = bufEnd;
1829 : } else {
1830 0 : if (mDoubleByteText) {
1831 : // Need to expand the text. First transform it into a temporary buffer,
1832 : // then expand.
1833 0 : AutoFallibleTArray<PRUint8,BIG_TEXT_NODE_SIZE> tempBuf;
1834 0 : PRUint8* bufStart = tempBuf.AppendElements(contentLength);
1835 0 : if (!bufStart) {
1836 0 : DestroyUserData(userDataToDestroy);
1837 0 : return nsnull;
1838 : }
1839 : PRUint8* end = nsTextFrameUtils::TransformText(
1840 0 : reinterpret_cast<const PRUint8*>(frag->Get1b()) + contentStart, contentLength,
1841 0 : bufStart, compression, &mNextRunContextInfo, &builder, &analysisFlags);
1842 : aTextBuffer = ExpandBuffer(static_cast<PRUnichar*>(aTextBuffer),
1843 0 : tempBuf.Elements(), end - tempBuf.Elements());
1844 : } else {
1845 0 : PRUint8* bufStart = static_cast<PRUint8*>(aTextBuffer);
1846 : PRUint8* end = nsTextFrameUtils::TransformText(
1847 0 : reinterpret_cast<const PRUint8*>(frag->Get1b()) + contentStart, contentLength,
1848 0 : bufStart, compression, &mNextRunContextInfo, &builder, &analysisFlags);
1849 0 : aTextBuffer = end;
1850 : }
1851 : }
1852 0 : textFlags |= analysisFlags;
1853 :
1854 : currentTransformedTextOffset =
1855 0 : (static_cast<const PRUint8*>(aTextBuffer) - static_cast<const PRUint8*>(textPtr)) >> mDoubleByteText;
1856 : }
1857 :
1858 : // Check for out-of-memory in gfxSkipCharsBuilder
1859 0 : if (!builder.IsOK()) {
1860 0 : DestroyUserData(userDataToDestroy);
1861 0 : return nsnull;
1862 : }
1863 :
1864 : void* finalUserData;
1865 0 : if (userData == &dummyData) {
1866 0 : textFlags |= nsTextFrameUtils::TEXT_IS_SIMPLE_FLOW;
1867 0 : userData = nsnull;
1868 0 : finalUserData = mMappedFlows[0].mStartFrame;
1869 : } else {
1870 0 : finalUserData = userData;
1871 : }
1872 :
1873 0 : PRUint32 transformedLength = currentTransformedTextOffset;
1874 :
1875 : // Now build the textrun
1876 0 : nsTextFrame* firstFrame = mMappedFlows[0].mStartFrame;
1877 0 : gfxFontGroup* fontGroup = GetFontGroupForFrame(firstFrame, mInflation);
1878 0 : if (!fontGroup) {
1879 0 : DestroyUserData(userDataToDestroy);
1880 0 : return nsnull;
1881 : }
1882 :
1883 0 : if (textFlags & nsTextFrameUtils::TEXT_HAS_TAB) {
1884 0 : textFlags |= gfxTextRunFactory::TEXT_ENABLE_SPACING;
1885 : }
1886 0 : if (textFlags & nsTextFrameUtils::TEXT_HAS_SHY) {
1887 0 : textFlags |= gfxTextRunFactory::TEXT_ENABLE_HYPHEN_BREAKS;
1888 : }
1889 0 : if (mBidiEnabled && (NS_GET_EMBEDDING_LEVEL(firstFrame) & 1)) {
1890 0 : textFlags |= gfxTextRunFactory::TEXT_IS_RTL;
1891 : }
1892 0 : if (mNextRunContextInfo & nsTextFrameUtils::INCOMING_WHITESPACE) {
1893 0 : textFlags |= nsTextFrameUtils::TEXT_TRAILING_WHITESPACE;
1894 : }
1895 0 : if (mNextRunContextInfo & nsTextFrameUtils::INCOMING_ARABICCHAR) {
1896 0 : textFlags |= gfxTextRunFactory::TEXT_TRAILING_ARABICCHAR;
1897 : }
1898 : // ContinueTextRunAcrossFrames guarantees that it doesn't matter which
1899 : // frame's style is used, so use the last frame's
1900 : textFlags |= nsLayoutUtils::GetTextRunFlagsForStyle(lastStyleContext,
1901 0 : textStyle, fontStyle);
1902 : // XXX this is a bit of a hack. For performance reasons, if we're favouring
1903 : // performance over quality, don't try to get accurate glyph extents.
1904 0 : if (!(textFlags & gfxTextRunFactory::TEXT_OPTIMIZE_SPEED)) {
1905 0 : textFlags |= gfxTextRunFactory::TEXT_NEED_BOUNDING_BOX;
1906 : }
1907 :
1908 0 : gfxSkipChars skipChars;
1909 0 : skipChars.TakeFrom(&builder);
1910 : // Convert linebreak coordinates to transformed string offsets
1911 0 : NS_ASSERTION(nextBreakIndex == mLineBreakBeforeFrames.Length(),
1912 : "Didn't find all the frames to break-before...");
1913 0 : gfxSkipCharsIterator iter(skipChars);
1914 0 : nsAutoTArray<PRUint32,50> textBreakPointsAfterTransform;
1915 0 : for (i = 0; i < textBreakPoints.Length(); ++i) {
1916 : nsTextFrameUtils::AppendLineBreakOffset(&textBreakPointsAfterTransform,
1917 0 : iter.ConvertOriginalToSkipped(textBreakPoints[i]));
1918 : }
1919 0 : if (mStartOfLine) {
1920 : nsTextFrameUtils::AppendLineBreakOffset(&textBreakPointsAfterTransform,
1921 0 : transformedLength);
1922 : }
1923 :
1924 : // Setup factory chain
1925 0 : nsAutoPtr<nsTransformingTextRunFactory> transformingFactory;
1926 0 : if (anySmallcapsStyle) {
1927 0 : transformingFactory = new nsFontVariantTextRunFactory();
1928 : }
1929 0 : if (anyTextTransformStyle) {
1930 : transformingFactory =
1931 0 : new nsCaseTransformTextRunFactory(transformingFactory.forget());
1932 : }
1933 0 : nsTArray<nsStyleContext*> styles;
1934 0 : if (transformingFactory) {
1935 0 : iter.SetOriginalOffset(0);
1936 0 : for (i = 0; i < mMappedFlows.Length(); ++i) {
1937 0 : MappedFlow* mappedFlow = &mMappedFlows[i];
1938 : nsTextFrame* f;
1939 0 : for (f = mappedFlow->mStartFrame; f != mappedFlow->mEndFrame;
1940 0 : f = static_cast<nsTextFrame*>(f->GetNextContinuation())) {
1941 0 : PRUint32 offset = iter.GetSkippedOffset();
1942 0 : iter.AdvanceOriginal(f->GetContentLength());
1943 0 : PRUint32 end = iter.GetSkippedOffset();
1944 0 : nsStyleContext* sc = f->GetStyleContext();
1945 : PRUint32 j;
1946 0 : for (j = offset; j < end; ++j) {
1947 0 : styles.AppendElement(sc);
1948 : }
1949 : }
1950 : }
1951 0 : textFlags |= nsTextFrameUtils::TEXT_IS_TRANSFORMED;
1952 0 : NS_ASSERTION(iter.GetSkippedOffset() == transformedLength,
1953 : "We didn't cover all the characters in the text run!");
1954 : }
1955 :
1956 : gfxTextRun* textRun;
1957 : gfxTextRunFactory::Parameters params =
1958 : { mContext, finalUserData, &skipChars,
1959 0 : textBreakPointsAfterTransform.Elements(), textBreakPointsAfterTransform.Length(),
1960 0 : firstFrame->PresContext()->AppUnitsPerDevPixel() };
1961 :
1962 0 : if (mDoubleByteText) {
1963 0 : const PRUnichar* text = static_cast<const PRUnichar*>(textPtr);
1964 0 : if (transformingFactory) {
1965 : textRun = transformingFactory->MakeTextRun(text, transformedLength, ¶ms,
1966 0 : fontGroup, textFlags, styles.Elements());
1967 0 : if (textRun) {
1968 : // ownership of the factory has passed to the textrun
1969 0 : transformingFactory.forget();
1970 : }
1971 : } else {
1972 0 : textRun = MakeTextRun(text, transformedLength, fontGroup, ¶ms, textFlags);
1973 : }
1974 : } else {
1975 0 : const PRUint8* text = static_cast<const PRUint8*>(textPtr);
1976 0 : textFlags |= gfxFontGroup::TEXT_IS_8BIT;
1977 0 : if (transformingFactory) {
1978 : textRun = transformingFactory->MakeTextRun(text, transformedLength, ¶ms,
1979 0 : fontGroup, textFlags, styles.Elements());
1980 0 : if (textRun) {
1981 : // ownership of the factory has passed to the textrun
1982 0 : transformingFactory.forget();
1983 : }
1984 : } else {
1985 0 : textRun = MakeTextRun(text, transformedLength, fontGroup, ¶ms, textFlags);
1986 : }
1987 : }
1988 0 : if (!textRun) {
1989 0 : DestroyUserData(userDataToDestroy);
1990 0 : return nsnull;
1991 : }
1992 :
1993 : // We have to set these up after we've created the textrun, because
1994 : // the breaks may be stored in the textrun during this very call.
1995 : // This is a bit annoying because it requires another loop over the frames
1996 : // making up the textrun, but I don't see a way to avoid this.
1997 0 : PRUint32 flags = 0;
1998 0 : if (mDoubleByteText) {
1999 0 : flags |= SBS_DOUBLE_BYTE;
2000 : }
2001 0 : if (mSkipIncompleteTextRuns) {
2002 0 : flags |= SBS_SUPPRESS_SINK;
2003 : }
2004 0 : SetupBreakSinksForTextRun(textRun, textPtr, flags);
2005 :
2006 0 : if (mSkipIncompleteTextRuns) {
2007 : mSkipIncompleteTextRuns = !TextContainsLineBreakerWhiteSpace(textPtr,
2008 0 : transformedLength, mDoubleByteText);
2009 : // Arrange for this textrun to be deleted the next time the linebreaker
2010 : // is flushed out
2011 0 : mTextRunsToDelete.AppendElement(textRun);
2012 : // Since we're doing to destroy the user data now, avoid a dangling
2013 : // pointer. Strictly speaking we don't need to do this since it should
2014 : // not be used (since this textrun will not be used and will be
2015 : // itself deleted soon), but it's always better to not have dangling
2016 : // pointers around.
2017 0 : textRun->SetUserData(nsnull);
2018 0 : DestroyUserData(userDataToDestroy);
2019 0 : return nsnull;
2020 : }
2021 :
2022 : // Actually wipe out the textruns associated with the mapped frames and associate
2023 : // those frames with this text run.
2024 0 : AssignTextRun(textRun);
2025 0 : return textRun;
2026 : }
2027 :
2028 : // This is a cut-down version of BuildTextRunForFrames used to set up
2029 : // context for the line-breaker, when the textrun has already been created.
2030 : // So it does the same walk over the mMappedFlows, but doesn't actually
2031 : // build a new textrun.
2032 : bool
2033 0 : BuildTextRunsScanner::SetupLineBreakerContext(gfxTextRun *aTextRun)
2034 : {
2035 0 : AutoFallibleTArray<PRUint8,BIG_TEXT_NODE_SIZE> buffer;
2036 0 : PRUint32 bufferSize = mMaxTextLength*(mDoubleByteText ? 2 : 1);
2037 0 : if (bufferSize < mMaxTextLength || bufferSize == PR_UINT32_MAX) {
2038 0 : return false;
2039 : }
2040 0 : void *textPtr = buffer.AppendElements(bufferSize);
2041 0 : if (!textPtr) {
2042 0 : return false;
2043 : }
2044 :
2045 0 : gfxSkipCharsBuilder builder;
2046 :
2047 0 : nsAutoTArray<PRInt32,50> textBreakPoints;
2048 : TextRunUserData dummyData;
2049 : TextRunMappedFlow dummyMappedFlow;
2050 :
2051 : TextRunUserData* userData;
2052 : TextRunUserData* userDataToDestroy;
2053 : // If the situation is particularly simple (and common) we don't need to
2054 : // allocate userData.
2055 0 : if (mMappedFlows.Length() == 1 && !mMappedFlows[0].mEndFrame &&
2056 0 : mMappedFlows[0].mStartFrame->GetContentOffset() == 0) {
2057 0 : userData = &dummyData;
2058 0 : userDataToDestroy = nsnull;
2059 0 : dummyData.mMappedFlows = &dummyMappedFlow;
2060 : } else {
2061 : userData = static_cast<TextRunUserData*>
2062 0 : (nsMemory::Alloc(sizeof(TextRunUserData) + mMappedFlows.Length()*sizeof(TextRunMappedFlow)));
2063 0 : userDataToDestroy = userData;
2064 0 : userData->mMappedFlows = reinterpret_cast<TextRunMappedFlow*>(userData + 1);
2065 : }
2066 0 : userData->mMappedFlowCount = mMappedFlows.Length();
2067 0 : userData->mLastFlowIndex = 0;
2068 :
2069 0 : PRUint32 nextBreakIndex = 0;
2070 0 : nsTextFrame* nextBreakBeforeFrame = GetNextBreakBeforeFrame(&nextBreakIndex);
2071 :
2072 : PRUint32 i;
2073 0 : const nsStyleText* textStyle = nsnull;
2074 0 : for (i = 0; i < mMappedFlows.Length(); ++i) {
2075 0 : MappedFlow* mappedFlow = &mMappedFlows[i];
2076 0 : nsTextFrame* f = mappedFlow->mStartFrame;
2077 :
2078 0 : textStyle = f->GetStyleText();
2079 : nsTextFrameUtils::CompressionMode compression =
2080 0 : CSSWhitespaceToCompressionMode[textStyle->mWhiteSpace];
2081 :
2082 : // Figure out what content is included in this flow.
2083 0 : nsIContent* content = f->GetContent();
2084 0 : const nsTextFragment* frag = content->GetText();
2085 0 : PRInt32 contentStart = mappedFlow->mStartFrame->GetContentOffset();
2086 0 : PRInt32 contentEnd = mappedFlow->GetContentEnd();
2087 0 : PRInt32 contentLength = contentEnd - contentStart;
2088 :
2089 0 : TextRunMappedFlow* newFlow = &userData->mMappedFlows[i];
2090 0 : newFlow->mStartFrame = mappedFlow->mStartFrame;
2091 0 : newFlow->mDOMOffsetToBeforeTransformOffset = builder.GetCharCount() -
2092 0 : mappedFlow->mStartFrame->GetContentOffset();
2093 0 : newFlow->mContentLength = contentLength;
2094 :
2095 0 : while (nextBreakBeforeFrame && nextBreakBeforeFrame->GetContent() == content) {
2096 : textBreakPoints.AppendElement(
2097 0 : nextBreakBeforeFrame->GetContentOffset() + newFlow->mDOMOffsetToBeforeTransformOffset);
2098 0 : nextBreakBeforeFrame = GetNextBreakBeforeFrame(&nextBreakIndex);
2099 : }
2100 :
2101 : PRUint32 analysisFlags;
2102 0 : if (frag->Is2b()) {
2103 0 : NS_ASSERTION(mDoubleByteText, "Wrong buffer char size!");
2104 0 : PRUnichar* bufStart = static_cast<PRUnichar*>(textPtr);
2105 : PRUnichar* bufEnd = nsTextFrameUtils::TransformText(
2106 0 : frag->Get2b() + contentStart, contentLength, bufStart,
2107 0 : compression, &mNextRunContextInfo, &builder, &analysisFlags);
2108 0 : textPtr = bufEnd;
2109 : } else {
2110 0 : if (mDoubleByteText) {
2111 : // Need to expand the text. First transform it into a temporary buffer,
2112 : // then expand.
2113 0 : AutoFallibleTArray<PRUint8,BIG_TEXT_NODE_SIZE> tempBuf;
2114 0 : PRUint8* bufStart = tempBuf.AppendElements(contentLength);
2115 0 : if (!bufStart) {
2116 0 : DestroyUserData(userDataToDestroy);
2117 0 : return false;
2118 : }
2119 : PRUint8* end = nsTextFrameUtils::TransformText(
2120 0 : reinterpret_cast<const PRUint8*>(frag->Get1b()) + contentStart, contentLength,
2121 0 : bufStart, compression, &mNextRunContextInfo, &builder, &analysisFlags);
2122 : textPtr = ExpandBuffer(static_cast<PRUnichar*>(textPtr),
2123 0 : tempBuf.Elements(), end - tempBuf.Elements());
2124 : } else {
2125 0 : PRUint8* bufStart = static_cast<PRUint8*>(textPtr);
2126 : PRUint8* end = nsTextFrameUtils::TransformText(
2127 0 : reinterpret_cast<const PRUint8*>(frag->Get1b()) + contentStart, contentLength,
2128 0 : bufStart, compression, &mNextRunContextInfo, &builder, &analysisFlags);
2129 0 : textPtr = end;
2130 : }
2131 : }
2132 : }
2133 :
2134 : // We have to set these up after we've created the textrun, because
2135 : // the breaks may be stored in the textrun during this very call.
2136 : // This is a bit annoying because it requires another loop over the frames
2137 : // making up the textrun, but I don't see a way to avoid this.
2138 0 : PRUint32 flags = 0;
2139 0 : if (mDoubleByteText) {
2140 0 : flags |= SBS_DOUBLE_BYTE;
2141 : }
2142 0 : if (mSkipIncompleteTextRuns) {
2143 0 : flags |= SBS_SUPPRESS_SINK;
2144 : }
2145 0 : SetupBreakSinksForTextRun(aTextRun, buffer.Elements(), flags);
2146 :
2147 0 : DestroyUserData(userDataToDestroy);
2148 :
2149 0 : return true;
2150 : }
2151 :
2152 : static bool
2153 0 : HasCompressedLeadingWhitespace(nsTextFrame* aFrame, const nsStyleText* aStyleText,
2154 : PRInt32 aContentEndOffset,
2155 : const gfxSkipCharsIterator& aIterator)
2156 : {
2157 0 : if (!aIterator.IsOriginalCharSkipped())
2158 0 : return false;
2159 :
2160 0 : gfxSkipCharsIterator iter = aIterator;
2161 0 : PRInt32 frameContentOffset = aFrame->GetContentOffset();
2162 0 : const nsTextFragment* frag = aFrame->GetContent()->GetText();
2163 0 : while (frameContentOffset < aContentEndOffset && iter.IsOriginalCharSkipped()) {
2164 0 : if (IsTrimmableSpace(frag, frameContentOffset, aStyleText))
2165 0 : return true;
2166 0 : ++frameContentOffset;
2167 0 : iter.AdvanceOriginal(1);
2168 : }
2169 0 : return false;
2170 : }
2171 :
2172 : void
2173 0 : BuildTextRunsScanner::SetupBreakSinksForTextRun(gfxTextRun* aTextRun,
2174 : const void* aTextPtr,
2175 : PRUint32 aFlags)
2176 : {
2177 : // textruns have uniform language
2178 0 : nsIAtom* language = mMappedFlows[0].mStartFrame->GetStyleFont()->mLanguage;
2179 : // We keep this pointed at the skip-chars data for the current mappedFlow.
2180 : // This lets us cheaply check whether the flow has compressed initial
2181 : // whitespace...
2182 0 : gfxSkipCharsIterator iter(aTextRun->GetSkipChars());
2183 :
2184 : PRUint32 i;
2185 0 : for (i = 0; i < mMappedFlows.Length(); ++i) {
2186 0 : MappedFlow* mappedFlow = &mMappedFlows[i];
2187 0 : PRUint32 offset = iter.GetSkippedOffset();
2188 0 : gfxSkipCharsIterator iterNext = iter;
2189 0 : iterNext.AdvanceOriginal(mappedFlow->GetContentEnd() -
2190 0 : mappedFlow->mStartFrame->GetContentOffset());
2191 :
2192 : nsAutoPtr<BreakSink>* breakSink = mBreakSinks.AppendElement(
2193 : new BreakSink(aTextRun, mContext, offset,
2194 0 : (aFlags & SBS_EXISTING_TEXTRUN) != 0));
2195 0 : if (!breakSink || !*breakSink)
2196 0 : return;
2197 :
2198 0 : PRUint32 length = iterNext.GetSkippedOffset() - offset;
2199 0 : PRUint32 flags = 0;
2200 0 : nsIFrame* initialBreakController = mappedFlow->mAncestorControllingInitialBreak;
2201 0 : if (!initialBreakController) {
2202 0 : initialBreakController = mLineContainer;
2203 : }
2204 0 : if (!initialBreakController->GetStyleText()->WhiteSpaceCanWrap()) {
2205 0 : flags |= nsLineBreaker::BREAK_SUPPRESS_INITIAL;
2206 : }
2207 0 : nsTextFrame* startFrame = mappedFlow->mStartFrame;
2208 0 : const nsStyleText* textStyle = startFrame->GetStyleText();
2209 0 : if (!textStyle->WhiteSpaceCanWrap()) {
2210 0 : flags |= nsLineBreaker::BREAK_SUPPRESS_INSIDE;
2211 : }
2212 0 : if (aTextRun->GetFlags() & nsTextFrameUtils::TEXT_NO_BREAKS) {
2213 0 : flags |= nsLineBreaker::BREAK_SKIP_SETTING_NO_BREAKS;
2214 : }
2215 0 : if (textStyle->mTextTransform == NS_STYLE_TEXT_TRANSFORM_CAPITALIZE) {
2216 0 : flags |= nsLineBreaker::BREAK_NEED_CAPITALIZATION;
2217 : }
2218 0 : if (textStyle->mHyphens == NS_STYLE_HYPHENS_AUTO) {
2219 0 : flags |= nsLineBreaker::BREAK_USE_AUTO_HYPHENATION;
2220 : }
2221 :
2222 0 : if (HasCompressedLeadingWhitespace(startFrame, textStyle,
2223 0 : mappedFlow->GetContentEnd(), iter)) {
2224 0 : mLineBreaker.AppendInvisibleWhitespace(flags);
2225 : }
2226 :
2227 0 : if (length > 0) {
2228 : BreakSink* sink =
2229 0 : (aFlags & SBS_SUPPRESS_SINK) ? nsnull : (*breakSink).get();
2230 0 : if (aFlags & SBS_DOUBLE_BYTE) {
2231 0 : const PRUnichar* text = reinterpret_cast<const PRUnichar*>(aTextPtr);
2232 : mLineBreaker.AppendText(language, text + offset,
2233 0 : length, flags, sink);
2234 : } else {
2235 0 : const PRUint8* text = reinterpret_cast<const PRUint8*>(aTextPtr);
2236 : mLineBreaker.AppendText(language, text + offset,
2237 0 : length, flags, sink);
2238 : }
2239 : }
2240 :
2241 0 : iter = iterNext;
2242 : }
2243 : }
2244 :
2245 : // Find the flow corresponding to aContent in aUserData
2246 : static inline TextRunMappedFlow*
2247 0 : FindFlowForContent(TextRunUserData* aUserData, nsIContent* aContent)
2248 : {
2249 : // Find the flow that contains us
2250 0 : PRInt32 i = aUserData->mLastFlowIndex;
2251 0 : PRInt32 delta = 1;
2252 0 : PRInt32 sign = 1;
2253 : // Search starting at the current position and examine close-by
2254 : // positions first, moving further and further away as we go.
2255 0 : while (i >= 0 && PRUint32(i) < aUserData->mMappedFlowCount) {
2256 0 : TextRunMappedFlow* flow = &aUserData->mMappedFlows[i];
2257 0 : if (flow->mStartFrame->GetContent() == aContent) {
2258 0 : return flow;
2259 : }
2260 :
2261 0 : i += delta;
2262 0 : sign = -sign;
2263 0 : delta = -delta + sign;
2264 : }
2265 :
2266 : // We ran into an array edge. Add |delta| to |i| once more to get
2267 : // back to the side where we still need to search, then step in
2268 : // the |sign| direction.
2269 0 : i += delta;
2270 0 : if (sign > 0) {
2271 0 : for (; i < PRInt32(aUserData->mMappedFlowCount); ++i) {
2272 0 : TextRunMappedFlow* flow = &aUserData->mMappedFlows[i];
2273 0 : if (flow->mStartFrame->GetContent() == aContent) {
2274 0 : return flow;
2275 : }
2276 : }
2277 : } else {
2278 0 : for (; i >= 0; --i) {
2279 0 : TextRunMappedFlow* flow = &aUserData->mMappedFlows[i];
2280 0 : if (flow->mStartFrame->GetContent() == aContent) {
2281 0 : return flow;
2282 : }
2283 : }
2284 : }
2285 :
2286 0 : return nsnull;
2287 : }
2288 :
2289 : void
2290 0 : BuildTextRunsScanner::AssignTextRun(gfxTextRun* aTextRun)
2291 : {
2292 : PRUint32 i;
2293 0 : for (i = 0; i < mMappedFlows.Length(); ++i) {
2294 0 : MappedFlow* mappedFlow = &mMappedFlows[i];
2295 0 : nsTextFrame* startFrame = mappedFlow->mStartFrame;
2296 0 : nsTextFrame* endFrame = mappedFlow->mEndFrame;
2297 : nsTextFrame* f;
2298 0 : for (f = startFrame; f != endFrame;
2299 0 : f = static_cast<nsTextFrame*>(f->GetNextContinuation())) {
2300 : #ifdef DEBUG_roc
2301 : if (f->GetTextRun(mWhichTextRun)) {
2302 : gfxTextRun* textRun = f->GetTextRun(mWhichTextRun);
2303 : if (textRun->GetFlags() & nsTextFrameUtils::TEXT_IS_SIMPLE_FLOW) {
2304 : if (mMappedFlows[0].mStartFrame != static_cast<nsTextFrame*>(textRun->GetUserData())) {
2305 : NS_WARNING("REASSIGNING SIMPLE FLOW TEXT RUN!");
2306 : }
2307 : } else {
2308 : TextRunUserData* userData =
2309 : static_cast<TextRunUserData*>(textRun->GetUserData());
2310 :
2311 : if (userData->mMappedFlowCount >= mMappedFlows.Length() ||
2312 : userData->mMappedFlows[userData->mMappedFlowCount - 1].mStartFrame !=
2313 : mMappedFlows[userData->mMappedFlowCount - 1].mStartFrame) {
2314 : NS_WARNING("REASSIGNING MULTIFLOW TEXT RUN (not append)!");
2315 : }
2316 : }
2317 : }
2318 : #endif
2319 :
2320 0 : gfxTextRun* oldTextRun = f->GetTextRun(mWhichTextRun);
2321 0 : if (oldTextRun) {
2322 0 : nsTextFrame* firstFrame = nsnull;
2323 0 : PRUint32 startOffset = 0;
2324 0 : if (oldTextRun->GetFlags() & nsTextFrameUtils::TEXT_IS_SIMPLE_FLOW) {
2325 0 : firstFrame = static_cast<nsTextFrame*>(oldTextRun->GetUserData());
2326 : }
2327 : else {
2328 0 : TextRunUserData* userData = static_cast<TextRunUserData*>(oldTextRun->GetUserData());
2329 0 : firstFrame = userData->mMappedFlows[0].mStartFrame;
2330 0 : if (NS_UNLIKELY(f != firstFrame)) {
2331 0 : TextRunMappedFlow* flow = FindFlowForContent(userData, f->GetContent());
2332 0 : if (flow) {
2333 0 : startOffset = flow->mDOMOffsetToBeforeTransformOffset;
2334 : }
2335 : else {
2336 0 : NS_ERROR("Can't find flow containing frame 'f'");
2337 : }
2338 : }
2339 : }
2340 :
2341 : // Optimization: if |f| is the first frame in the flow then there are no
2342 : // prev-continuations that use |oldTextRun|.
2343 0 : nsTextFrame* clearFrom = nsnull;
2344 0 : if (NS_UNLIKELY(f != firstFrame)) {
2345 : // If all the frames in the mapped flow starting at |f| (inclusive)
2346 : // are empty then we let the prev-continuations keep the old text run.
2347 0 : gfxSkipCharsIterator iter(oldTextRun->GetSkipChars(), startOffset, f->GetContentOffset());
2348 0 : PRUint32 textRunOffset = iter.ConvertOriginalToSkipped(f->GetContentOffset());
2349 0 : clearFrom = textRunOffset == oldTextRun->GetLength() ? f : nsnull;
2350 : }
2351 0 : f->ClearTextRun(clearFrom, mWhichTextRun);
2352 :
2353 : #ifdef DEBUG
2354 0 : if (firstFrame && !firstFrame->GetTextRun(mWhichTextRun)) {
2355 : // oldTextRun was destroyed - assert that we don't reference it.
2356 0 : for (PRUint32 i = 0; i < mBreakSinks.Length(); ++i) {
2357 0 : NS_ASSERTION(oldTextRun != mBreakSinks[i]->mTextRun,
2358 : "destroyed text run is still in use");
2359 : }
2360 : }
2361 : #endif
2362 : }
2363 0 : f->SetTextRun(aTextRun, mWhichTextRun, mInflation);
2364 : }
2365 : // Set this bit now; we can't set it any earlier because
2366 : // f->ClearTextRun() might clear it out.
2367 0 : startFrame->AddStateBits(TEXT_IN_TEXTRUN_USER_DATA);
2368 : }
2369 0 : }
2370 :
2371 : gfxSkipCharsIterator
2372 0 : nsTextFrame::EnsureTextRun(TextRunType aWhichTextRun,
2373 : float aInflation,
2374 : gfxContext* aReferenceContext,
2375 : nsIFrame* aLineContainer,
2376 : const nsLineList::iterator* aLine,
2377 : PRUint32* aFlowEndInTextRun)
2378 : {
2379 0 : gfxTextRun *textRun = GetTextRun(aWhichTextRun);
2380 0 : if (textRun && (!aLine || !(*aLine)->GetInvalidateTextRuns())) {
2381 0 : if (textRun->GetExpirationState()->IsTracked()) {
2382 0 : gTextRuns->MarkUsed(textRun);
2383 : }
2384 : } else {
2385 0 : nsRefPtr<gfxContext> ctx = aReferenceContext;
2386 0 : if (!ctx) {
2387 0 : ctx = GetReferenceRenderingContext(this, nsnull);
2388 : }
2389 0 : if (ctx) {
2390 : BuildTextRuns(ctx, this, aLineContainer, aLine, aWhichTextRun,
2391 0 : aInflation);
2392 : }
2393 0 : textRun = GetTextRun(aWhichTextRun);
2394 0 : if (!textRun) {
2395 : // A text run was not constructed for this frame. This is bad. The caller
2396 : // will check mTextRun.
2397 0 : static const gfxSkipChars emptySkipChars;
2398 0 : return gfxSkipCharsIterator(emptySkipChars, 0);
2399 : }
2400 : }
2401 :
2402 0 : if (textRun->GetFlags() & nsTextFrameUtils::TEXT_IS_SIMPLE_FLOW) {
2403 0 : if (aFlowEndInTextRun) {
2404 0 : *aFlowEndInTextRun = textRun->GetLength();
2405 : }
2406 0 : return gfxSkipCharsIterator(textRun->GetSkipChars(), 0, mContentOffset);
2407 : }
2408 :
2409 0 : TextRunUserData* userData = static_cast<TextRunUserData*>(textRun->GetUserData());
2410 0 : TextRunMappedFlow* flow = FindFlowForContent(userData, mContent);
2411 0 : if (flow) {
2412 : // Since textruns can only contain one flow for a given content element,
2413 : // this must be our flow.
2414 0 : PRUint32 flowIndex = flow - userData->mMappedFlows;
2415 0 : userData->mLastFlowIndex = flowIndex;
2416 0 : gfxSkipCharsIterator iter(textRun->GetSkipChars(),
2417 0 : flow->mDOMOffsetToBeforeTransformOffset, mContentOffset);
2418 0 : if (aFlowEndInTextRun) {
2419 0 : if (flowIndex + 1 < userData->mMappedFlowCount) {
2420 0 : gfxSkipCharsIterator end(textRun->GetSkipChars());
2421 : *aFlowEndInTextRun = end.ConvertOriginalToSkipped(
2422 0 : flow[1].mStartFrame->GetContentOffset() + flow[1].mDOMOffsetToBeforeTransformOffset);
2423 : } else {
2424 0 : *aFlowEndInTextRun = textRun->GetLength();
2425 : }
2426 : }
2427 0 : return iter;
2428 : }
2429 :
2430 0 : NS_ERROR("Can't find flow containing this frame???");
2431 0 : static const gfxSkipChars emptySkipChars;
2432 0 : return gfxSkipCharsIterator(emptySkipChars, 0);
2433 : }
2434 :
2435 : static PRUint32
2436 0 : GetEndOfTrimmedText(const nsTextFragment* aFrag, const nsStyleText* aStyleText,
2437 : PRUint32 aStart, PRUint32 aEnd,
2438 : gfxSkipCharsIterator* aIterator)
2439 : {
2440 0 : aIterator->SetSkippedOffset(aEnd);
2441 0 : while (aIterator->GetSkippedOffset() > aStart) {
2442 0 : aIterator->AdvanceSkipped(-1);
2443 0 : if (!IsTrimmableSpace(aFrag, aIterator->GetOriginalOffset(), aStyleText))
2444 0 : return aIterator->GetSkippedOffset() + 1;
2445 : }
2446 0 : return aStart;
2447 : }
2448 :
2449 : nsTextFrame::TrimmedOffsets
2450 0 : nsTextFrame::GetTrimmedOffsets(const nsTextFragment* aFrag,
2451 : bool aTrimAfter)
2452 : {
2453 0 : NS_ASSERTION(mTextRun, "Need textrun here");
2454 : // This should not be used during reflow. We need our TEXT_REFLOW_FLAGS
2455 : // to be set correctly. If our parent wasn't reflowed due to the frame
2456 : // tree being too deep then the return value doesn't matter.
2457 0 : NS_ASSERTION(!(GetStateBits() & NS_FRAME_FIRST_REFLOW) ||
2458 : (GetParent()->GetStateBits() & NS_FRAME_TOO_DEEP_IN_FRAME_TREE),
2459 : "Can only call this on frames that have been reflowed");
2460 0 : NS_ASSERTION(!(GetStateBits() & NS_FRAME_IN_REFLOW),
2461 : "Can only call this on frames that are not being reflowed");
2462 :
2463 0 : TrimmedOffsets offsets = { GetContentOffset(), GetContentLength() };
2464 0 : const nsStyleText* textStyle = GetStyleText();
2465 : // Note that pre-line newlines should still allow us to trim spaces
2466 : // for display
2467 0 : if (textStyle->WhiteSpaceIsSignificant())
2468 0 : return offsets;
2469 :
2470 0 : if (GetStateBits() & TEXT_START_OF_LINE) {
2471 : PRInt32 whitespaceCount =
2472 : GetTrimmableWhitespaceCount(aFrag,
2473 0 : offsets.mStart, offsets.mLength, 1);
2474 0 : offsets.mStart += whitespaceCount;
2475 0 : offsets.mLength -= whitespaceCount;
2476 : }
2477 :
2478 0 : if (aTrimAfter && (GetStateBits() & TEXT_END_OF_LINE)) {
2479 : // This treats a trailing 'pre-line' newline as trimmable. That's fine,
2480 : // it's actually what we want since we want whitespace before it to
2481 : // be trimmed.
2482 : PRInt32 whitespaceCount =
2483 : GetTrimmableWhitespaceCount(aFrag,
2484 0 : offsets.GetEnd() - 1, offsets.mLength, -1);
2485 0 : offsets.mLength -= whitespaceCount;
2486 : }
2487 0 : return offsets;
2488 : }
2489 :
2490 : /*
2491 : * Currently only Unicode characters below 0x10000 have their spacing modified
2492 : * by justification. If characters above 0x10000 turn out to need
2493 : * justification spacing, that will require extra work. Currently,
2494 : * this function must not include 0xd800 to 0xdbff because these characters
2495 : * are surrogates.
2496 : */
2497 0 : static bool IsJustifiableCharacter(const nsTextFragment* aFrag, PRInt32 aPos,
2498 : bool aLangIsCJ)
2499 : {
2500 0 : PRUnichar ch = aFrag->CharAt(aPos);
2501 0 : if (ch == '\n' || ch == '\t' || ch == '\r')
2502 0 : return true;
2503 0 : if (ch == ' ' || ch == CH_NBSP) {
2504 : // Don't justify spaces that are combined with diacriticals
2505 0 : if (!aFrag->Is2b())
2506 0 : return true;
2507 : return !nsTextFrameUtils::IsSpaceCombiningSequenceTail(
2508 0 : aFrag->Get2b() + aPos + 1, aFrag->GetLength() - (aPos + 1));
2509 : }
2510 0 : if (ch < 0x2150u)
2511 0 : return false;
2512 0 : if (aLangIsCJ && (
2513 : (0x2150u <= ch && ch <= 0x22ffu) || // Number Forms, Arrows, Mathematical Operators
2514 : (0x2460u <= ch && ch <= 0x24ffu) || // Enclosed Alphanumerics
2515 : (0x2580u <= ch && ch <= 0x27bfu) || // Block Elements, Geometric Shapes, Miscellaneous Symbols, Dingbats
2516 : (0x27f0u <= ch && ch <= 0x2bffu) || // Supplemental Arrows-A, Braille Patterns, Supplemental Arrows-B,
2517 : // Miscellaneous Mathematical Symbols-B, Supplemental Mathematical Operators,
2518 : // Miscellaneous Symbols and Arrows
2519 : (0x2e80u <= ch && ch <= 0x312fu) || // CJK Radicals Supplement, CJK Radicals Supplement,
2520 : // Ideographic Description Characters, CJK Symbols and Punctuation,
2521 : // Hiragana, Katakana, Bopomofo
2522 : (0x3190u <= ch && ch <= 0xabffu) || // Kanbun, Bopomofo Extended, Katakana Phonetic Extensions,
2523 : // Enclosed CJK Letters and Months, CJK Compatibility,
2524 : // CJK Unified Ideographs Extension A, Yijing Hexagram Symbols,
2525 : // CJK Unified Ideographs, Yi Syllables, Yi Radicals
2526 : (0xf900u <= ch && ch <= 0xfaffu) || // CJK Compatibility Ideographs
2527 : (0xff5eu <= ch && ch <= 0xff9fu) // Halfwidth and Fullwidth Forms(a part)
2528 : ))
2529 0 : return true;
2530 0 : return false;
2531 : }
2532 :
2533 : void
2534 0 : nsTextFrame::ClearMetrics(nsHTMLReflowMetrics& aMetrics)
2535 : {
2536 0 : aMetrics.width = 0;
2537 0 : aMetrics.height = 0;
2538 0 : aMetrics.ascent = 0;
2539 0 : mAscent = 0;
2540 0 : }
2541 :
2542 0 : static PRInt32 FindChar(const nsTextFragment* frag,
2543 : PRInt32 aOffset, PRInt32 aLength, PRUnichar ch)
2544 : {
2545 0 : PRInt32 i = 0;
2546 0 : if (frag->Is2b()) {
2547 0 : const PRUnichar* str = frag->Get2b() + aOffset;
2548 0 : for (; i < aLength; ++i) {
2549 0 : if (*str == ch)
2550 0 : return i + aOffset;
2551 0 : ++str;
2552 : }
2553 : } else {
2554 0 : if (PRUint16(ch) <= 0xFF) {
2555 0 : const char* str = frag->Get1b() + aOffset;
2556 0 : const void* p = memchr(str, ch, aLength);
2557 0 : if (p)
2558 0 : return (static_cast<const char*>(p) - str) + aOffset;
2559 : }
2560 : }
2561 0 : return -1;
2562 : }
2563 :
2564 0 : static bool IsChineseOrJapanese(nsIFrame* aFrame)
2565 : {
2566 0 : nsIAtom* language = aFrame->GetStyleFont()->mLanguage;
2567 0 : if (!language) {
2568 0 : return false;
2569 : }
2570 0 : const PRUnichar *lang = language->GetUTF16String();
2571 0 : return (!nsCRT::strncmp(lang, NS_LITERAL_STRING("ja").get(), 2) ||
2572 0 : !nsCRT::strncmp(lang, NS_LITERAL_STRING("zh").get(), 2)) &&
2573 0 : (language->GetLength() == 2 || lang[2] == '-');
2574 : }
2575 :
2576 : #ifdef DEBUG
2577 0 : static bool IsInBounds(const gfxSkipCharsIterator& aStart, PRInt32 aContentLength,
2578 : PRUint32 aOffset, PRUint32 aLength) {
2579 0 : if (aStart.GetSkippedOffset() > aOffset)
2580 0 : return false;
2581 0 : if (aContentLength == PR_INT32_MAX)
2582 0 : return true;
2583 0 : gfxSkipCharsIterator iter(aStart);
2584 0 : iter.AdvanceOriginal(aContentLength);
2585 0 : return iter.GetSkippedOffset() >= aOffset + aLength;
2586 : }
2587 : #endif
2588 :
2589 0 : class NS_STACK_CLASS PropertyProvider : public gfxTextRun::PropertyProvider {
2590 : public:
2591 : /**
2592 : * Use this constructor for reflow, when we don't know what text is
2593 : * really mapped by the frame and we have a lot of other data around.
2594 : *
2595 : * @param aLength can be PR_INT32_MAX to indicate we cover all the text
2596 : * associated with aFrame up to where its flow chain ends in the given
2597 : * textrun. If PR_INT32_MAX is passed, justification and hyphen-related methods
2598 : * cannot be called, nor can GetOriginalLength().
2599 : */
2600 0 : PropertyProvider(gfxTextRun* aTextRun, const nsStyleText* aTextStyle,
2601 : const nsTextFragment* aFrag, nsTextFrame* aFrame,
2602 : const gfxSkipCharsIterator& aStart, PRInt32 aLength,
2603 : nsIFrame* aLineContainer,
2604 : nscoord aOffsetFromBlockOriginForTabs,
2605 : nsTextFrame::TextRunType aWhichTextRun)
2606 : : mTextRun(aTextRun), mFontGroup(nsnull),
2607 : mTextStyle(aTextStyle), mFrag(aFrag),
2608 : mLineContainer(aLineContainer),
2609 : mFrame(aFrame), mStart(aStart), mTempIterator(aStart),
2610 : mTabWidths(nsnull), mTabWidthsAnalyzedLimit(0),
2611 : mLength(aLength),
2612 : mWordSpacing(mTextStyle->mWordSpacing),
2613 0 : mLetterSpacing(StyleToCoord(mTextStyle->mLetterSpacing)),
2614 : mJustificationSpacing(0),
2615 : mHyphenWidth(-1),
2616 : mOffsetFromBlockOriginForTabs(aOffsetFromBlockOriginForTabs),
2617 : mReflowing(true),
2618 0 : mWhichTextRun(aWhichTextRun)
2619 : {
2620 0 : NS_ASSERTION(mStart.IsInitialized(), "Start not initialized?");
2621 0 : }
2622 :
2623 : /**
2624 : * Use this constructor after the frame has been reflowed and we don't
2625 : * have other data around. Gets everything from the frame. EnsureTextRun
2626 : * *must* be called before this!!!
2627 : */
2628 0 : PropertyProvider(nsTextFrame* aFrame, const gfxSkipCharsIterator& aStart,
2629 : nsTextFrame::TextRunType aWhichTextRun)
2630 0 : : mTextRun(aFrame->GetTextRun(aWhichTextRun)), mFontGroup(nsnull),
2631 0 : mTextStyle(aFrame->GetStyleText()),
2632 0 : mFrag(aFrame->GetContent()->GetText()),
2633 : mLineContainer(nsnull),
2634 : mFrame(aFrame), mStart(aStart), mTempIterator(aStart),
2635 : mTabWidths(nsnull), mTabWidthsAnalyzedLimit(0),
2636 0 : mLength(aFrame->GetContentLength()),
2637 : mWordSpacing(mTextStyle->mWordSpacing),
2638 0 : mLetterSpacing(StyleToCoord(mTextStyle->mLetterSpacing)),
2639 : mJustificationSpacing(0),
2640 : mHyphenWidth(-1),
2641 : mOffsetFromBlockOriginForTabs(0),
2642 : mReflowing(false),
2643 0 : mWhichTextRun(aWhichTextRun)
2644 : {
2645 0 : NS_ASSERTION(mTextRun, "Textrun not initialized!");
2646 0 : }
2647 :
2648 : // Call this after construction if you're not going to reflow the text
2649 : void InitializeForDisplay(bool aTrimAfter);
2650 :
2651 : virtual void GetSpacing(PRUint32 aStart, PRUint32 aLength, Spacing* aSpacing);
2652 : virtual gfxFloat GetHyphenWidth();
2653 : virtual void GetHyphenationBreaks(PRUint32 aStart, PRUint32 aLength,
2654 : bool* aBreakBefore);
2655 0 : virtual PRInt8 GetHyphensOption() {
2656 0 : return mTextStyle->mHyphens;
2657 : }
2658 :
2659 : void GetSpacingInternal(PRUint32 aStart, PRUint32 aLength, Spacing* aSpacing,
2660 : bool aIgnoreTabs);
2661 :
2662 : /**
2663 : * Count the number of justifiable characters in the given DOM range
2664 : */
2665 : PRUint32 ComputeJustifiableCharacters(PRInt32 aOffset, PRInt32 aLength);
2666 : /**
2667 : * Find the start and end of the justifiable characters. Does not depend on the
2668 : * position of aStart or aEnd, although it's most efficient if they are near the
2669 : * start and end of the text frame.
2670 : */
2671 : void FindJustificationRange(gfxSkipCharsIterator* aStart,
2672 : gfxSkipCharsIterator* aEnd);
2673 :
2674 : const nsStyleText* GetStyleText() { return mTextStyle; }
2675 0 : nsTextFrame* GetFrame() { return mFrame; }
2676 : // This may not be equal to the frame offset/length in because we may have
2677 : // adjusted for whitespace trimming according to the state bits set in the frame
2678 : // (for the static provider)
2679 0 : const gfxSkipCharsIterator& GetStart() { return mStart; }
2680 : // May return PR_INT32_MAX if that was given to the constructor
2681 0 : PRUint32 GetOriginalLength() {
2682 0 : NS_ASSERTION(mLength != PR_INT32_MAX, "Length not known");
2683 0 : return mLength;
2684 : }
2685 0 : const nsTextFragment* GetFragment() { return mFrag; }
2686 :
2687 0 : gfxFontGroup* GetFontGroup() {
2688 0 : if (!mFontGroup)
2689 0 : InitFontGroupAndFontMetrics();
2690 0 : return mFontGroup;
2691 : }
2692 :
2693 0 : nsFontMetrics* GetFontMetrics() {
2694 0 : if (!mFontMetrics)
2695 0 : InitFontGroupAndFontMetrics();
2696 0 : return mFontMetrics;
2697 : }
2698 :
2699 : void CalcTabWidths(PRUint32 aTransformedStart, PRUint32 aTransformedLength);
2700 :
2701 0 : const gfxSkipCharsIterator& GetEndHint() { return mTempIterator; }
2702 :
2703 : protected:
2704 : void SetupJustificationSpacing();
2705 :
2706 0 : void InitFontGroupAndFontMetrics() {
2707 : float inflation = (mWhichTextRun == nsTextFrame::eInflated)
2708 0 : ? mFrame->GetFontSizeInflation() : 1.0f;
2709 : mFontGroup = GetFontGroupForFrame(mFrame, inflation,
2710 0 : getter_AddRefs(mFontMetrics));
2711 0 : }
2712 :
2713 : gfxTextRun* mTextRun;
2714 : gfxFontGroup* mFontGroup;
2715 : nsRefPtr<nsFontMetrics> mFontMetrics;
2716 : const nsStyleText* mTextStyle;
2717 : const nsTextFragment* mFrag;
2718 : nsIFrame* mLineContainer;
2719 : nsTextFrame* mFrame;
2720 : gfxSkipCharsIterator mStart; // Offset in original and transformed string
2721 : gfxSkipCharsIterator mTempIterator;
2722 :
2723 : // Either null, or pointing to the frame's tabWidthProperty.
2724 : TabWidthStore* mTabWidths;
2725 : // how far we've done tab-width calculation; this is ONLY valid
2726 : // when mTabWidths is NULL (otherwise rely on mTabWidths->mLimit instead)
2727 : PRUint32 mTabWidthsAnalyzedLimit;
2728 :
2729 : PRInt32 mLength; // DOM string length, may be PR_INT32_MAX
2730 : gfxFloat mWordSpacing; // space for each whitespace char
2731 : gfxFloat mLetterSpacing; // space for each letter
2732 : gfxFloat mJustificationSpacing;
2733 : gfxFloat mHyphenWidth;
2734 : gfxFloat mOffsetFromBlockOriginForTabs;
2735 : bool mReflowing;
2736 : nsTextFrame::TextRunType mWhichTextRun;
2737 : };
2738 :
2739 : PRUint32
2740 0 : PropertyProvider::ComputeJustifiableCharacters(PRInt32 aOffset, PRInt32 aLength)
2741 : {
2742 : // Scan non-skipped characters and count justifiable chars.
2743 : nsSkipCharsRunIterator
2744 0 : run(mStart, nsSkipCharsRunIterator::LENGTH_INCLUDES_SKIPPED, aLength);
2745 0 : run.SetOriginalOffset(aOffset);
2746 0 : PRUint32 justifiableChars = 0;
2747 0 : bool isCJK = IsChineseOrJapanese(mFrame);
2748 0 : while (run.NextRun()) {
2749 : PRInt32 i;
2750 0 : for (i = 0; i < run.GetRunLength(); ++i) {
2751 : justifiableChars +=
2752 0 : IsJustifiableCharacter(mFrag, run.GetOriginalOffset() + i, isCJK);
2753 : }
2754 : }
2755 0 : return justifiableChars;
2756 : }
2757 :
2758 : /**
2759 : * Finds the offset of the first character of the cluster containing aPos
2760 : */
2761 0 : static void FindClusterStart(gfxTextRun* aTextRun, PRInt32 aOriginalStart,
2762 : gfxSkipCharsIterator* aPos)
2763 : {
2764 0 : while (aPos->GetOriginalOffset() > aOriginalStart) {
2765 0 : if (aPos->IsOriginalCharSkipped() ||
2766 0 : aTextRun->IsClusterStart(aPos->GetSkippedOffset())) {
2767 0 : break;
2768 : }
2769 0 : aPos->AdvanceOriginal(-1);
2770 : }
2771 0 : }
2772 :
2773 : /**
2774 : * Finds the offset of the last character of the cluster containing aPos
2775 : */
2776 0 : static void FindClusterEnd(gfxTextRun* aTextRun, PRInt32 aOriginalEnd,
2777 : gfxSkipCharsIterator* aPos)
2778 : {
2779 0 : NS_PRECONDITION(aPos->GetOriginalOffset() < aOriginalEnd,
2780 : "character outside string");
2781 0 : aPos->AdvanceOriginal(1);
2782 0 : while (aPos->GetOriginalOffset() < aOriginalEnd) {
2783 0 : if (aPos->IsOriginalCharSkipped() ||
2784 0 : aTextRun->IsClusterStart(aPos->GetSkippedOffset())) {
2785 0 : break;
2786 : }
2787 0 : aPos->AdvanceOriginal(1);
2788 : }
2789 0 : aPos->AdvanceOriginal(-1);
2790 0 : }
2791 :
2792 : // aStart, aLength in transformed string offsets
2793 : void
2794 0 : PropertyProvider::GetSpacing(PRUint32 aStart, PRUint32 aLength,
2795 : Spacing* aSpacing)
2796 : {
2797 : GetSpacingInternal(aStart, aLength, aSpacing,
2798 0 : (mTextRun->GetFlags() & nsTextFrameUtils::TEXT_HAS_TAB) == 0);
2799 0 : }
2800 :
2801 : static bool
2802 0 : CanAddSpacingAfter(gfxTextRun* aTextRun, PRUint32 aOffset)
2803 : {
2804 0 : if (aOffset + 1 >= aTextRun->GetLength())
2805 0 : return true;
2806 0 : return aTextRun->IsClusterStart(aOffset + 1) &&
2807 0 : aTextRun->IsLigatureGroupStart(aOffset + 1);
2808 : }
2809 :
2810 : void
2811 0 : PropertyProvider::GetSpacingInternal(PRUint32 aStart, PRUint32 aLength,
2812 : Spacing* aSpacing, bool aIgnoreTabs)
2813 : {
2814 0 : NS_PRECONDITION(IsInBounds(mStart, mLength, aStart, aLength), "Range out of bounds");
2815 :
2816 : PRUint32 index;
2817 0 : for (index = 0; index < aLength; ++index) {
2818 0 : aSpacing[index].mBefore = 0.0;
2819 0 : aSpacing[index].mAfter = 0.0;
2820 : }
2821 :
2822 : // Find our offset into the original+transformed string
2823 0 : gfxSkipCharsIterator start(mStart);
2824 0 : start.SetSkippedOffset(aStart);
2825 :
2826 : // First, compute the word and letter spacing
2827 0 : if (mWordSpacing || mLetterSpacing) {
2828 : // Iterate over non-skipped characters
2829 : nsSkipCharsRunIterator
2830 0 : run(start, nsSkipCharsRunIterator::LENGTH_UNSKIPPED_ONLY, aLength);
2831 0 : while (run.NextRun()) {
2832 0 : PRUint32 runOffsetInSubstring = run.GetSkippedOffset() - aStart;
2833 : PRInt32 i;
2834 0 : gfxSkipCharsIterator iter = run.GetPos();
2835 0 : for (i = 0; i < run.GetRunLength(); ++i) {
2836 0 : if (CanAddSpacingAfter(mTextRun, run.GetSkippedOffset() + i)) {
2837 : // End of a cluster, not in a ligature: put letter-spacing after it
2838 0 : aSpacing[runOffsetInSubstring + i].mAfter += mLetterSpacing;
2839 : }
2840 0 : if (IsCSSWordSpacingSpace(mFrag, i + run.GetOriginalOffset(),
2841 0 : mTextStyle)) {
2842 : // It kinda sucks, but space characters can be part of clusters,
2843 : // and even still be whitespace (I think!)
2844 0 : iter.SetSkippedOffset(run.GetSkippedOffset() + i);
2845 0 : FindClusterEnd(mTextRun, run.GetOriginalOffset() + run.GetRunLength(),
2846 0 : &iter);
2847 0 : aSpacing[iter.GetSkippedOffset() - aStart].mAfter += mWordSpacing;
2848 : }
2849 : }
2850 : }
2851 : }
2852 :
2853 : // Ignore tab spacing rather than computing it, if the tab size is 0
2854 0 : if (!aIgnoreTabs)
2855 0 : aIgnoreTabs = mFrame->GetStyleText()->mTabSize == 0;
2856 :
2857 : // Now add tab spacing, if there is any
2858 0 : if (!aIgnoreTabs) {
2859 0 : CalcTabWidths(aStart, aLength);
2860 0 : if (mTabWidths) {
2861 : mTabWidths->ApplySpacing(aSpacing,
2862 0 : aStart - mStart.GetSkippedOffset(), aLength);
2863 : }
2864 : }
2865 :
2866 : // Now add in justification spacing
2867 0 : if (mJustificationSpacing) {
2868 0 : gfxFloat halfJustificationSpace = mJustificationSpacing/2;
2869 : // Scan non-skipped characters and adjust justifiable chars, adding
2870 : // justification space on either side of the cluster
2871 0 : bool isCJK = IsChineseOrJapanese(mFrame);
2872 0 : gfxSkipCharsIterator justificationStart(mStart), justificationEnd(mStart);
2873 0 : FindJustificationRange(&justificationStart, &justificationEnd);
2874 :
2875 : nsSkipCharsRunIterator
2876 0 : run(start, nsSkipCharsRunIterator::LENGTH_UNSKIPPED_ONLY, aLength);
2877 0 : while (run.NextRun()) {
2878 : PRInt32 i;
2879 0 : gfxSkipCharsIterator iter = run.GetPos();
2880 0 : PRInt32 runOriginalOffset = run.GetOriginalOffset();
2881 0 : for (i = 0; i < run.GetRunLength(); ++i) {
2882 0 : PRInt32 iterOriginalOffset = runOriginalOffset + i;
2883 0 : if (IsJustifiableCharacter(mFrag, iterOriginalOffset, isCJK)) {
2884 0 : iter.SetOriginalOffset(iterOriginalOffset);
2885 0 : FindClusterStart(mTextRun, runOriginalOffset, &iter);
2886 0 : PRUint32 clusterFirstChar = iter.GetSkippedOffset();
2887 0 : FindClusterEnd(mTextRun, runOriginalOffset + run.GetRunLength(), &iter);
2888 0 : PRUint32 clusterLastChar = iter.GetSkippedOffset();
2889 : // Only apply justification to characters before justificationEnd
2890 0 : if (clusterFirstChar >= justificationStart.GetSkippedOffset() &&
2891 0 : clusterLastChar < justificationEnd.GetSkippedOffset()) {
2892 0 : aSpacing[clusterFirstChar - aStart].mBefore += halfJustificationSpace;
2893 0 : aSpacing[clusterLastChar - aStart].mAfter += halfJustificationSpace;
2894 : }
2895 : }
2896 : }
2897 : }
2898 : }
2899 0 : }
2900 :
2901 : static gfxFloat
2902 0 : ComputeTabWidthAppUnits(nsIFrame* aFrame, gfxTextRun* aTextRun)
2903 : {
2904 : // Get the number of spaces from CSS -moz-tab-size
2905 0 : const nsStyleText* textStyle = aFrame->GetStyleText();
2906 :
2907 : // Round the space width when converting to appunits the same way
2908 : // textruns do
2909 : gfxFloat spaceWidthAppUnits =
2910 0 : NS_round(GetFirstFontMetrics(aTextRun->GetFontGroup()).spaceWidth *
2911 0 : aTextRun->GetAppUnitsPerDevUnit());
2912 0 : return textStyle->mTabSize * spaceWidthAppUnits;
2913 : }
2914 :
2915 : // aX and the result are in whole appunits.
2916 : static gfxFloat
2917 0 : AdvanceToNextTab(gfxFloat aX, nsIFrame* aFrame,
2918 : gfxTextRun* aTextRun, gfxFloat* aCachedTabWidth)
2919 : {
2920 0 : if (*aCachedTabWidth < 0) {
2921 0 : *aCachedTabWidth = ComputeTabWidthAppUnits(aFrame, aTextRun);
2922 : }
2923 :
2924 : // Advance aX to the next multiple of *aCachedTabWidth. We must advance
2925 : // by at least 1 appunit.
2926 : // XXX should we make this 1 CSS pixel?
2927 0 : return ceil((aX + 1)/(*aCachedTabWidth))*(*aCachedTabWidth);
2928 : }
2929 :
2930 : void
2931 0 : PropertyProvider::CalcTabWidths(PRUint32 aStart, PRUint32 aLength)
2932 : {
2933 0 : if (!mTabWidths) {
2934 0 : if (mReflowing && !mLineContainer) {
2935 : // Intrinsic width computation does its own tab processing. We
2936 : // just don't do anything here.
2937 0 : return;
2938 : }
2939 0 : if (!mReflowing) {
2940 : mTabWidths = static_cast<TabWidthStore*>
2941 0 : (mFrame->Properties().Get(TabWidthProperty()));
2942 : #ifdef DEBUG
2943 : // If we're not reflowing, we should have already computed the
2944 : // tab widths; check that they're available as far as the last
2945 : // tab character present (if any)
2946 0 : for (PRUint32 i = aStart + aLength; i > aStart; --i) {
2947 0 : if (mTextRun->CharIsTab(i - 1)) {
2948 0 : NS_ASSERTION(mTabWidths && mTabWidths->mLimit >= i,
2949 : "Precomputed tab widths are missing!");
2950 0 : break;
2951 : }
2952 : }
2953 : #endif
2954 0 : return;
2955 : }
2956 : }
2957 :
2958 0 : PRUint32 startOffset = mStart.GetSkippedOffset();
2959 : PRUint32 tabsEnd = mTabWidths ?
2960 0 : mTabWidths->mLimit : NS_MAX(mTabWidthsAnalyzedLimit, startOffset);
2961 :
2962 0 : if (tabsEnd < aStart + aLength) {
2963 0 : NS_ASSERTION(mReflowing,
2964 : "We need precomputed tab widths, but don't have enough.");
2965 :
2966 0 : gfxFloat tabWidth = -1;
2967 0 : for (PRUint32 i = tabsEnd; i < aStart + aLength; ++i) {
2968 : Spacing spacing;
2969 0 : GetSpacingInternal(i, 1, &spacing, true);
2970 0 : mOffsetFromBlockOriginForTabs += spacing.mBefore;
2971 :
2972 0 : if (!mTextRun->CharIsTab(i)) {
2973 0 : if (mTextRun->IsClusterStart(i)) {
2974 0 : PRUint32 clusterEnd = i + 1;
2975 0 : while (clusterEnd < mTextRun->GetLength() &&
2976 0 : !mTextRun->IsClusterStart(clusterEnd)) {
2977 0 : ++clusterEnd;
2978 : }
2979 : mOffsetFromBlockOriginForTabs +=
2980 0 : mTextRun->GetAdvanceWidth(i, clusterEnd - i, nsnull);
2981 : }
2982 : } else {
2983 0 : if (!mTabWidths) {
2984 0 : mTabWidths = new TabWidthStore();
2985 0 : mFrame->Properties().Set(TabWidthProperty(), mTabWidths);
2986 : }
2987 : double nextTab = AdvanceToNextTab(mOffsetFromBlockOriginForTabs,
2988 0 : mFrame, mTextRun, &tabWidth);
2989 : mTabWidths->mWidths.AppendElement(TabWidth(i - startOffset,
2990 0 : NSToIntRound(nextTab - mOffsetFromBlockOriginForTabs)));
2991 0 : mOffsetFromBlockOriginForTabs = nextTab;
2992 : }
2993 :
2994 0 : mOffsetFromBlockOriginForTabs += spacing.mAfter;
2995 : }
2996 :
2997 0 : if (mTabWidths) {
2998 0 : mTabWidths->mLimit = aStart + aLength;
2999 : }
3000 : }
3001 :
3002 0 : if (!mTabWidths) {
3003 : // Delete any stale property that may be left on the frame
3004 0 : mFrame->Properties().Delete(TabWidthProperty());
3005 : mTabWidthsAnalyzedLimit = NS_MAX(mTabWidthsAnalyzedLimit,
3006 0 : aStart + aLength);
3007 : }
3008 : }
3009 :
3010 : gfxFloat
3011 0 : PropertyProvider::GetHyphenWidth()
3012 : {
3013 0 : if (mHyphenWidth < 0) {
3014 0 : nsAutoPtr<gfxTextRun> hyphenTextRun(GetHyphenTextRun(mTextRun, nsnull, mFrame));
3015 0 : mHyphenWidth = mLetterSpacing;
3016 0 : if (hyphenTextRun.get()) {
3017 0 : mHyphenWidth += hyphenTextRun->GetAdvanceWidth(0, hyphenTextRun->GetLength(), nsnull);
3018 : }
3019 : }
3020 0 : return mHyphenWidth;
3021 : }
3022 :
3023 : void
3024 0 : PropertyProvider::GetHyphenationBreaks(PRUint32 aStart, PRUint32 aLength,
3025 : bool* aBreakBefore)
3026 : {
3027 0 : NS_PRECONDITION(IsInBounds(mStart, mLength, aStart, aLength), "Range out of bounds");
3028 0 : NS_PRECONDITION(mLength != PR_INT32_MAX, "Can't call this with undefined length");
3029 :
3030 0 : if (!mTextStyle->WhiteSpaceCanWrap() ||
3031 : mTextStyle->mHyphens == NS_STYLE_HYPHENS_NONE)
3032 : {
3033 0 : memset(aBreakBefore, false, aLength*sizeof(bool));
3034 0 : return;
3035 : }
3036 :
3037 : // Iterate through the original-string character runs
3038 : nsSkipCharsRunIterator
3039 0 : run(mStart, nsSkipCharsRunIterator::LENGTH_UNSKIPPED_ONLY, aLength);
3040 0 : run.SetSkippedOffset(aStart);
3041 : // We need to visit skipped characters so that we can detect SHY
3042 0 : run.SetVisitSkipped();
3043 :
3044 0 : PRInt32 prevTrailingCharOffset = run.GetPos().GetOriginalOffset() - 1;
3045 : bool allowHyphenBreakBeforeNextChar =
3046 0 : prevTrailingCharOffset >= mStart.GetOriginalOffset() &&
3047 0 : prevTrailingCharOffset < mStart.GetOriginalOffset() + mLength &&
3048 0 : mFrag->CharAt(prevTrailingCharOffset) == CH_SHY;
3049 :
3050 0 : while (run.NextRun()) {
3051 0 : NS_ASSERTION(run.GetRunLength() > 0, "Shouldn't return zero-length runs");
3052 0 : if (run.IsSkipped()) {
3053 : // Check if there's a soft hyphen which would let us hyphenate before
3054 : // the next non-skipped character. Don't look at soft hyphens followed
3055 : // by other skipped characters, we won't use them.
3056 : allowHyphenBreakBeforeNextChar =
3057 0 : mFrag->CharAt(run.GetOriginalOffset() + run.GetRunLength() - 1) == CH_SHY;
3058 : } else {
3059 0 : PRInt32 runOffsetInSubstring = run.GetSkippedOffset() - aStart;
3060 0 : memset(aBreakBefore + runOffsetInSubstring, false, run.GetRunLength()*sizeof(bool));
3061 : // Don't allow hyphen breaks at the start of the line
3062 0 : aBreakBefore[runOffsetInSubstring] = allowHyphenBreakBeforeNextChar &&
3063 0 : (!(mFrame->GetStateBits() & TEXT_START_OF_LINE) ||
3064 0 : run.GetSkippedOffset() > mStart.GetSkippedOffset());
3065 0 : allowHyphenBreakBeforeNextChar = false;
3066 : }
3067 : }
3068 :
3069 0 : if (mTextStyle->mHyphens == NS_STYLE_HYPHENS_AUTO) {
3070 0 : for (PRUint32 i = 0; i < aLength; ++i) {
3071 0 : if (mTextRun->CanHyphenateBefore(aStart + i)) {
3072 0 : aBreakBefore[i] = true;
3073 : }
3074 : }
3075 : }
3076 : }
3077 :
3078 : void
3079 0 : PropertyProvider::InitializeForDisplay(bool aTrimAfter)
3080 : {
3081 : nsTextFrame::TrimmedOffsets trimmed =
3082 0 : mFrame->GetTrimmedOffsets(mFrag, aTrimAfter);
3083 0 : mStart.SetOriginalOffset(trimmed.mStart);
3084 0 : mLength = trimmed.mLength;
3085 0 : SetupJustificationSpacing();
3086 0 : }
3087 :
3088 0 : static PRUint32 GetSkippedDistance(const gfxSkipCharsIterator& aStart,
3089 : const gfxSkipCharsIterator& aEnd)
3090 : {
3091 0 : return aEnd.GetSkippedOffset() - aStart.GetSkippedOffset();
3092 : }
3093 :
3094 : void
3095 0 : PropertyProvider::FindJustificationRange(gfxSkipCharsIterator* aStart,
3096 : gfxSkipCharsIterator* aEnd)
3097 : {
3098 0 : NS_PRECONDITION(mLength != PR_INT32_MAX, "Can't call this with undefined length");
3099 0 : NS_ASSERTION(aStart && aEnd, "aStart or/and aEnd is null");
3100 :
3101 0 : aStart->SetOriginalOffset(mStart.GetOriginalOffset());
3102 0 : aEnd->SetOriginalOffset(mStart.GetOriginalOffset() + mLength);
3103 :
3104 : // Ignore first cluster at start of line for justification purposes
3105 0 : if (mFrame->GetStateBits() & TEXT_START_OF_LINE) {
3106 0 : while (aStart->GetOriginalOffset() < aEnd->GetOriginalOffset()) {
3107 0 : aStart->AdvanceOriginal(1);
3108 0 : if (!aStart->IsOriginalCharSkipped() &&
3109 0 : mTextRun->IsClusterStart(aStart->GetSkippedOffset()))
3110 0 : break;
3111 : }
3112 : }
3113 :
3114 : // Ignore trailing cluster at end of line for justification purposes
3115 0 : if (mFrame->GetStateBits() & TEXT_END_OF_LINE) {
3116 0 : while (aEnd->GetOriginalOffset() > aStart->GetOriginalOffset()) {
3117 0 : aEnd->AdvanceOriginal(-1);
3118 0 : if (!aEnd->IsOriginalCharSkipped() &&
3119 0 : mTextRun->IsClusterStart(aEnd->GetSkippedOffset()))
3120 0 : break;
3121 : }
3122 : }
3123 0 : }
3124 :
3125 : void
3126 0 : PropertyProvider::SetupJustificationSpacing()
3127 : {
3128 0 : NS_PRECONDITION(mLength != PR_INT32_MAX, "Can't call this with undefined length");
3129 :
3130 0 : if (!(mFrame->GetStateBits() & TEXT_JUSTIFICATION_ENABLED))
3131 0 : return;
3132 :
3133 0 : gfxSkipCharsIterator start(mStart), end(mStart);
3134 : // We can't just use our mLength here; when InitializeForDisplay is
3135 : // called with false for aTrimAfter, we still shouldn't be assigning
3136 : // justification space to any trailing whitespace.
3137 : nsTextFrame::TrimmedOffsets trimmed =
3138 0 : mFrame->GetTrimmedOffsets(mFrag, true);
3139 0 : end.AdvanceOriginal(trimmed.mLength);
3140 0 : gfxSkipCharsIterator realEnd(end);
3141 0 : FindJustificationRange(&start, &end);
3142 :
3143 : PRInt32 justifiableCharacters =
3144 : ComputeJustifiableCharacters(start.GetOriginalOffset(),
3145 0 : end.GetOriginalOffset() - start.GetOriginalOffset());
3146 0 : if (justifiableCharacters == 0) {
3147 : // Nothing to do, nothing is justifiable and we shouldn't have any
3148 : // justification space assigned
3149 0 : return;
3150 : }
3151 :
3152 : gfxFloat naturalWidth =
3153 : mTextRun->GetAdvanceWidth(mStart.GetSkippedOffset(),
3154 0 : GetSkippedDistance(mStart, realEnd), this);
3155 0 : if (mFrame->GetStateBits() & TEXT_HYPHEN_BREAK) {
3156 0 : nsAutoPtr<gfxTextRun> hyphenTextRun(GetHyphenTextRun(mTextRun, nsnull, mFrame));
3157 0 : if (hyphenTextRun.get()) {
3158 : naturalWidth +=
3159 0 : hyphenTextRun->GetAdvanceWidth(0, hyphenTextRun->GetLength(), nsnull);
3160 : }
3161 : }
3162 0 : gfxFloat totalJustificationSpace = mFrame->GetSize().width - naturalWidth;
3163 0 : if (totalJustificationSpace <= 0) {
3164 : // No space available
3165 0 : return;
3166 : }
3167 :
3168 0 : mJustificationSpacing = totalJustificationSpace/justifiableCharacters;
3169 : }
3170 :
3171 : //----------------------------------------------------------------------
3172 :
3173 : // Helper class for managing blinking text
3174 :
3175 : class nsBlinkTimer : public nsITimerCallback
3176 : {
3177 : public:
3178 : nsBlinkTimer();
3179 : virtual ~nsBlinkTimer();
3180 :
3181 : NS_DECL_ISUPPORTS
3182 :
3183 : void AddFrame(nsPresContext* aPresContext, nsIFrame* aFrame);
3184 :
3185 : bool RemoveFrame(nsIFrame* aFrame);
3186 :
3187 : PRInt32 FrameCount();
3188 :
3189 : void Start();
3190 :
3191 : void Stop();
3192 :
3193 : NS_DECL_NSITIMERCALLBACK
3194 :
3195 : static void AddBlinkFrame(nsPresContext* aPresContext, nsIFrame* aFrame);
3196 : static void RemoveBlinkFrame(nsIFrame* aFrame);
3197 :
3198 0 : static bool GetBlinkIsOff() { return sState == 3; }
3199 :
3200 : protected:
3201 :
3202 0 : struct FrameData {
3203 : nsPresContext* mPresContext; // pres context associated with the frame
3204 : nsIFrame* mFrame;
3205 :
3206 :
3207 0 : FrameData(nsPresContext* aPresContext,
3208 : nsIFrame* aFrame)
3209 0 : : mPresContext(aPresContext), mFrame(aFrame) {}
3210 : };
3211 :
3212 : class FrameDataComparator {
3213 : public:
3214 0 : bool Equals(const FrameData& aTimer, nsIFrame* const& aFrame) const {
3215 0 : return aTimer.mFrame == aFrame;
3216 : }
3217 : };
3218 :
3219 : nsCOMPtr<nsITimer> mTimer;
3220 : nsTArray<FrameData> mFrames;
3221 : nsPresContext* mPresContext;
3222 :
3223 : protected:
3224 :
3225 : static nsBlinkTimer* sTextBlinker;
3226 : static PRUint32 sState; // 0-2 == on; 3 == off
3227 :
3228 : };
3229 :
3230 : nsBlinkTimer* nsBlinkTimer::sTextBlinker = nsnull;
3231 : PRUint32 nsBlinkTimer::sState = 0;
3232 :
3233 : #ifdef NOISY_BLINK
3234 : static PRTime gLastTick;
3235 : #endif
3236 :
3237 0 : nsBlinkTimer::nsBlinkTimer()
3238 : {
3239 0 : }
3240 :
3241 0 : nsBlinkTimer::~nsBlinkTimer()
3242 : {
3243 0 : Stop();
3244 0 : sTextBlinker = nsnull;
3245 0 : }
3246 :
3247 0 : void nsBlinkTimer::Start()
3248 : {
3249 : nsresult rv;
3250 0 : mTimer = do_CreateInstance("@mozilla.org/timer;1", &rv);
3251 0 : if (NS_OK == rv) {
3252 0 : mTimer->InitWithCallback(this, 250, nsITimer::TYPE_REPEATING_PRECISE_CAN_SKIP);
3253 : }
3254 0 : }
3255 :
3256 0 : void nsBlinkTimer::Stop()
3257 : {
3258 0 : if (nsnull != mTimer) {
3259 0 : mTimer->Cancel();
3260 0 : mTimer = nsnull;
3261 : }
3262 0 : }
3263 :
3264 0 : NS_IMPL_ISUPPORTS1(nsBlinkTimer, nsITimerCallback)
3265 :
3266 0 : void nsBlinkTimer::AddFrame(nsPresContext* aPresContext, nsIFrame* aFrame) {
3267 0 : mFrames.AppendElement(FrameData(aPresContext, aFrame));
3268 0 : if (1 == mFrames.Length()) {
3269 0 : Start();
3270 : }
3271 0 : }
3272 :
3273 0 : bool nsBlinkTimer::RemoveFrame(nsIFrame* aFrame) {
3274 0 : mFrames.RemoveElement(aFrame, FrameDataComparator());
3275 :
3276 0 : if (mFrames.IsEmpty()) {
3277 0 : Stop();
3278 : }
3279 0 : return true;
3280 : }
3281 :
3282 0 : PRInt32 nsBlinkTimer::FrameCount() {
3283 0 : return PRInt32(mFrames.Length());
3284 : }
3285 :
3286 0 : NS_IMETHODIMP nsBlinkTimer::Notify(nsITimer *timer)
3287 : {
3288 : // Toggle blink state bit so that text code knows whether or not to
3289 : // render. All text code shares the same flag so that they all blink
3290 : // in unison.
3291 0 : sState = (sState + 1) % 4;
3292 0 : if (sState == 1 || sState == 2)
3293 : // States 0, 1, and 2 are all the same.
3294 0 : return NS_OK;
3295 :
3296 : #ifdef NOISY_BLINK
3297 : PRTime now = PR_Now();
3298 : char buf[50];
3299 : PRTime delta;
3300 : LL_SUB(delta, now, gLastTick);
3301 : gLastTick = now;
3302 : PR_snprintf(buf, sizeof(buf), "%lldusec", delta);
3303 : printf("%s\n", buf);
3304 : #endif
3305 :
3306 0 : PRUint32 i, n = mFrames.Length();
3307 0 : for (i = 0; i < n; i++) {
3308 0 : FrameData& frameData = mFrames.ElementAt(i);
3309 :
3310 : // Determine damaged area and tell view manager to redraw it
3311 : // blink doesn't blink outline ... I hope
3312 0 : nsRect bounds(nsPoint(0, 0), frameData.mFrame->GetSize());
3313 0 : frameData.mFrame->Invalidate(bounds);
3314 : }
3315 0 : return NS_OK;
3316 : }
3317 :
3318 :
3319 : // static
3320 0 : void nsBlinkTimer::AddBlinkFrame(nsPresContext* aPresContext, nsIFrame* aFrame)
3321 : {
3322 0 : if (!sTextBlinker)
3323 : {
3324 0 : sTextBlinker = new nsBlinkTimer;
3325 : }
3326 :
3327 0 : NS_ADDREF(sTextBlinker);
3328 :
3329 0 : sTextBlinker->AddFrame(aPresContext, aFrame);
3330 0 : }
3331 :
3332 :
3333 : // static
3334 0 : void nsBlinkTimer::RemoveBlinkFrame(nsIFrame* aFrame)
3335 : {
3336 0 : NS_ASSERTION(sTextBlinker, "Should have blink timer here");
3337 :
3338 0 : nsBlinkTimer* blinkTimer = sTextBlinker; // copy so we can call NS_RELEASE on it
3339 :
3340 0 : blinkTimer->RemoveFrame(aFrame);
3341 0 : NS_RELEASE(blinkTimer);
3342 0 : }
3343 :
3344 : //----------------------------------------------------------------------
3345 :
3346 : static nscolor
3347 0 : EnsureDifferentColors(nscolor colorA, nscolor colorB)
3348 : {
3349 0 : if (colorA == colorB) {
3350 : nscolor res;
3351 : res = NS_RGB(NS_GET_R(colorA) ^ 0xff,
3352 : NS_GET_G(colorA) ^ 0xff,
3353 0 : NS_GET_B(colorA) ^ 0xff);
3354 0 : return res;
3355 : }
3356 0 : return colorA;
3357 : }
3358 :
3359 : //-----------------------------------------------------------------------------
3360 :
3361 0 : nsTextPaintStyle::nsTextPaintStyle(nsTextFrame* aFrame)
3362 : : mFrame(aFrame),
3363 0 : mPresContext(aFrame->PresContext()),
3364 : mInitCommonColors(false),
3365 0 : mInitSelectionColors(false)
3366 : {
3367 0 : for (PRUint32 i = 0; i < ArrayLength(mSelectionStyle); i++)
3368 0 : mSelectionStyle[i].mInit = false;
3369 0 : }
3370 :
3371 : bool
3372 0 : nsTextPaintStyle::EnsureSufficientContrast(nscolor *aForeColor, nscolor *aBackColor)
3373 : {
3374 0 : InitCommonColors();
3375 :
3376 : // If the combination of selection background color and frame background color
3377 : // is sufficient contrast, don't exchange the selection colors.
3378 : PRInt32 backLuminosityDifference =
3379 0 : NS_LUMINOSITY_DIFFERENCE(*aBackColor, mFrameBackgroundColor);
3380 0 : if (backLuminosityDifference >= mSufficientContrast)
3381 0 : return false;
3382 :
3383 : // Otherwise, we should use the higher-contrast color for the selection
3384 : // background color.
3385 : PRInt32 foreLuminosityDifference =
3386 0 : NS_LUMINOSITY_DIFFERENCE(*aForeColor, mFrameBackgroundColor);
3387 0 : if (backLuminosityDifference < foreLuminosityDifference) {
3388 0 : nscolor tmpColor = *aForeColor;
3389 0 : *aForeColor = *aBackColor;
3390 0 : *aBackColor = tmpColor;
3391 0 : return true;
3392 : }
3393 0 : return false;
3394 : }
3395 :
3396 : nscolor
3397 0 : nsTextPaintStyle::GetTextColor()
3398 : {
3399 0 : return nsLayoutUtils::GetColor(mFrame, eCSSProperty_color);
3400 : }
3401 :
3402 : bool
3403 0 : nsTextPaintStyle::GetSelectionColors(nscolor* aForeColor,
3404 : nscolor* aBackColor)
3405 : {
3406 0 : NS_ASSERTION(aForeColor, "aForeColor is null");
3407 0 : NS_ASSERTION(aBackColor, "aBackColor is null");
3408 :
3409 0 : if (!InitSelectionColors())
3410 0 : return false;
3411 :
3412 0 : *aForeColor = mSelectionTextColor;
3413 0 : *aBackColor = mSelectionBGColor;
3414 0 : return true;
3415 : }
3416 :
3417 : void
3418 0 : nsTextPaintStyle::GetHighlightColors(nscolor* aForeColor,
3419 : nscolor* aBackColor)
3420 : {
3421 0 : NS_ASSERTION(aForeColor, "aForeColor is null");
3422 0 : NS_ASSERTION(aBackColor, "aBackColor is null");
3423 :
3424 : nscolor backColor =
3425 0 : LookAndFeel::GetColor(LookAndFeel::eColorID_TextHighlightBackground);
3426 : nscolor foreColor =
3427 0 : LookAndFeel::GetColor(LookAndFeel::eColorID_TextHighlightForeground);
3428 0 : EnsureSufficientContrast(&foreColor, &backColor);
3429 0 : *aForeColor = foreColor;
3430 0 : *aBackColor = backColor;
3431 0 : }
3432 :
3433 : void
3434 0 : nsTextPaintStyle::GetURLSecondaryColor(nscolor* aForeColor)
3435 : {
3436 0 : NS_ASSERTION(aForeColor, "aForeColor is null");
3437 :
3438 0 : nscolor textColor = GetTextColor();
3439 : textColor = NS_RGBA(NS_GET_R(textColor),
3440 : NS_GET_G(textColor),
3441 : NS_GET_B(textColor),
3442 0 : (PRUint8)(255 * 0.5f));
3443 : // Don't use true alpha color for readability.
3444 0 : InitCommonColors();
3445 0 : *aForeColor = NS_ComposeColors(mFrameBackgroundColor, textColor);
3446 0 : }
3447 :
3448 : void
3449 0 : nsTextPaintStyle::GetIMESelectionColors(PRInt32 aIndex,
3450 : nscolor* aForeColor,
3451 : nscolor* aBackColor)
3452 : {
3453 0 : NS_ASSERTION(aForeColor, "aForeColor is null");
3454 0 : NS_ASSERTION(aBackColor, "aBackColor is null");
3455 0 : NS_ASSERTION(aIndex >= 0 && aIndex < 5, "Index out of range");
3456 :
3457 0 : nsSelectionStyle* selectionStyle = GetSelectionStyle(aIndex);
3458 0 : *aForeColor = selectionStyle->mTextColor;
3459 0 : *aBackColor = selectionStyle->mBGColor;
3460 0 : }
3461 :
3462 : bool
3463 0 : nsTextPaintStyle::GetSelectionUnderlineForPaint(PRInt32 aIndex,
3464 : nscolor* aLineColor,
3465 : float* aRelativeSize,
3466 : PRUint8* aStyle)
3467 : {
3468 0 : NS_ASSERTION(aLineColor, "aLineColor is null");
3469 0 : NS_ASSERTION(aRelativeSize, "aRelativeSize is null");
3470 0 : NS_ASSERTION(aIndex >= 0 && aIndex < 5, "Index out of range");
3471 :
3472 0 : nsSelectionStyle* selectionStyle = GetSelectionStyle(aIndex);
3473 0 : if (selectionStyle->mUnderlineStyle == NS_STYLE_BORDER_STYLE_NONE ||
3474 : selectionStyle->mUnderlineColor == NS_TRANSPARENT ||
3475 : selectionStyle->mUnderlineRelativeSize <= 0.0f)
3476 0 : return false;
3477 :
3478 0 : *aLineColor = selectionStyle->mUnderlineColor;
3479 0 : *aRelativeSize = selectionStyle->mUnderlineRelativeSize;
3480 0 : *aStyle = selectionStyle->mUnderlineStyle;
3481 0 : return true;
3482 : }
3483 :
3484 : void
3485 0 : nsTextPaintStyle::InitCommonColors()
3486 : {
3487 0 : if (mInitCommonColors)
3488 0 : return;
3489 :
3490 : nsIFrame* bgFrame =
3491 0 : nsCSSRendering::FindNonTransparentBackgroundFrame(mFrame);
3492 0 : NS_ASSERTION(bgFrame, "Cannot find NonTransparentBackgroundFrame.");
3493 : nscolor bgColor =
3494 0 : bgFrame->GetVisitedDependentColor(eCSSProperty_background_color);
3495 :
3496 0 : nscolor defaultBgColor = mPresContext->DefaultBackgroundColor();
3497 0 : mFrameBackgroundColor = NS_ComposeColors(defaultBgColor, bgColor);
3498 :
3499 0 : if (bgFrame->IsThemed()) {
3500 : // Assume a native widget has sufficient contrast always
3501 0 : mSufficientContrast = 0;
3502 0 : mInitCommonColors = true;
3503 0 : return;
3504 : }
3505 :
3506 0 : NS_ASSERTION(NS_GET_A(defaultBgColor) == 255,
3507 : "default background color is not opaque");
3508 :
3509 : nscolor defaultWindowBackgroundColor =
3510 0 : LookAndFeel::GetColor(LookAndFeel::eColorID_WindowBackground);
3511 : nscolor selectionTextColor =
3512 0 : LookAndFeel::GetColor(LookAndFeel::eColorID_TextSelectForeground);
3513 : nscolor selectionBGColor =
3514 0 : LookAndFeel::GetColor(LookAndFeel::eColorID_TextSelectBackground);
3515 :
3516 : mSufficientContrast =
3517 : NS_MIN(NS_MIN(NS_SUFFICIENT_LUMINOSITY_DIFFERENCE,
3518 0 : NS_LUMINOSITY_DIFFERENCE(selectionTextColor,
3519 0 : selectionBGColor)),
3520 0 : NS_LUMINOSITY_DIFFERENCE(defaultWindowBackgroundColor,
3521 0 : selectionBGColor));
3522 :
3523 0 : mInitCommonColors = true;
3524 : }
3525 :
3526 : static Element*
3527 0 : FindElementAncestorForMozSelection(nsIContent* aContent)
3528 : {
3529 0 : NS_ENSURE_TRUE(aContent, nsnull);
3530 0 : while (aContent && aContent->IsInNativeAnonymousSubtree()) {
3531 0 : aContent = aContent->GetBindingParent();
3532 : }
3533 0 : NS_ASSERTION(aContent, "aContent isn't in non-anonymous tree?");
3534 0 : while (aContent && !aContent->IsElement()) {
3535 0 : aContent = aContent->GetParent();
3536 : }
3537 0 : return aContent ? aContent->AsElement() : nsnull;
3538 : }
3539 :
3540 : bool
3541 0 : nsTextPaintStyle::InitSelectionColors()
3542 : {
3543 0 : if (mInitSelectionColors)
3544 0 : return true;
3545 :
3546 : PRInt16 selectionFlags;
3547 0 : PRInt16 selectionStatus = mFrame->GetSelectionStatus(&selectionFlags);
3548 0 : if (!(selectionFlags & nsISelectionDisplay::DISPLAY_TEXT) ||
3549 : selectionStatus < nsISelectionController::SELECTION_ON) {
3550 : // Not displaying the normal selection.
3551 : // We're not caching this fact, so every call to GetSelectionColors
3552 : // will come through here. We could avoid this, but it's not really worth it.
3553 0 : return false;
3554 : }
3555 :
3556 0 : mInitSelectionColors = true;
3557 :
3558 0 : nsIFrame* nonGeneratedAncestor = nsLayoutUtils::GetNonGeneratedAncestor(mFrame);
3559 : Element* selectionElement =
3560 0 : FindElementAncestorForMozSelection(nonGeneratedAncestor->GetContent());
3561 :
3562 0 : if (selectionElement &&
3563 : selectionStatus == nsISelectionController::SELECTION_ON) {
3564 0 : nsRefPtr<nsStyleContext> sc = nsnull;
3565 : sc = mPresContext->StyleSet()->
3566 : ProbePseudoElementStyle(selectionElement,
3567 : nsCSSPseudoElements::ePseudo_mozSelection,
3568 0 : mFrame->GetStyleContext());
3569 : // Use -moz-selection pseudo class.
3570 0 : if (sc) {
3571 : mSelectionBGColor =
3572 0 : sc->GetVisitedDependentColor(eCSSProperty_background_color);
3573 0 : mSelectionTextColor = sc->GetVisitedDependentColor(eCSSProperty_color);
3574 0 : return true;
3575 : }
3576 : }
3577 :
3578 : nscolor selectionBGColor =
3579 0 : LookAndFeel::GetColor(LookAndFeel::eColorID_TextSelectBackground);
3580 :
3581 0 : if (selectionStatus == nsISelectionController::SELECTION_ATTENTION) {
3582 : mSelectionBGColor =
3583 : LookAndFeel::GetColor(
3584 0 : LookAndFeel::eColorID_TextSelectBackgroundAttention);
3585 : mSelectionBGColor = EnsureDifferentColors(mSelectionBGColor,
3586 0 : selectionBGColor);
3587 0 : } else if (selectionStatus != nsISelectionController::SELECTION_ON) {
3588 : mSelectionBGColor =
3589 0 : LookAndFeel::GetColor(LookAndFeel::eColorID_TextSelectBackgroundDisabled);
3590 : mSelectionBGColor = EnsureDifferentColors(mSelectionBGColor,
3591 0 : selectionBGColor);
3592 : } else {
3593 0 : mSelectionBGColor = selectionBGColor;
3594 : }
3595 :
3596 : mSelectionTextColor =
3597 0 : LookAndFeel::GetColor(LookAndFeel::eColorID_TextSelectForeground);
3598 :
3599 : // On MacOS X, we don't exchange text color and BG color.
3600 0 : if (mSelectionTextColor == NS_DONT_CHANGE_COLOR) {
3601 0 : nscoord frameColor = mFrame->GetVisitedDependentColor(eCSSProperty_color);
3602 0 : mSelectionTextColor = EnsureDifferentColors(frameColor, mSelectionBGColor);
3603 : } else {
3604 0 : EnsureSufficientContrast(&mSelectionTextColor, &mSelectionBGColor);
3605 : }
3606 0 : return true;
3607 : }
3608 :
3609 : nsTextPaintStyle::nsSelectionStyle*
3610 0 : nsTextPaintStyle::GetSelectionStyle(PRInt32 aIndex)
3611 : {
3612 0 : InitSelectionStyle(aIndex);
3613 0 : return &mSelectionStyle[aIndex];
3614 : }
3615 :
3616 : struct StyleIDs {
3617 : LookAndFeel::ColorID mForeground, mBackground, mLine;
3618 : LookAndFeel::IntID mLineStyle;
3619 : LookAndFeel::FloatID mLineRelativeSize;
3620 : };
3621 : static StyleIDs SelectionStyleIDs[] = {
3622 : { LookAndFeel::eColorID_IMERawInputForeground,
3623 : LookAndFeel::eColorID_IMERawInputBackground,
3624 : LookAndFeel::eColorID_IMERawInputUnderline,
3625 : LookAndFeel::eIntID_IMERawInputUnderlineStyle,
3626 : LookAndFeel::eFloatID_IMEUnderlineRelativeSize },
3627 : { LookAndFeel::eColorID_IMESelectedRawTextForeground,
3628 : LookAndFeel::eColorID_IMESelectedRawTextBackground,
3629 : LookAndFeel::eColorID_IMESelectedRawTextUnderline,
3630 : LookAndFeel::eIntID_IMESelectedRawTextUnderlineStyle,
3631 : LookAndFeel::eFloatID_IMEUnderlineRelativeSize },
3632 : { LookAndFeel::eColorID_IMEConvertedTextForeground,
3633 : LookAndFeel::eColorID_IMEConvertedTextBackground,
3634 : LookAndFeel::eColorID_IMEConvertedTextUnderline,
3635 : LookAndFeel::eIntID_IMEConvertedTextUnderlineStyle,
3636 : LookAndFeel::eFloatID_IMEUnderlineRelativeSize },
3637 : { LookAndFeel::eColorID_IMESelectedConvertedTextForeground,
3638 : LookAndFeel::eColorID_IMESelectedConvertedTextBackground,
3639 : LookAndFeel::eColorID_IMESelectedConvertedTextUnderline,
3640 : LookAndFeel::eIntID_IMESelectedConvertedTextUnderline,
3641 : LookAndFeel::eFloatID_IMEUnderlineRelativeSize },
3642 : { LookAndFeel::eColorID_LAST_COLOR,
3643 : LookAndFeel::eColorID_LAST_COLOR,
3644 : LookAndFeel::eColorID_SpellCheckerUnderline,
3645 : LookAndFeel::eIntID_SpellCheckerUnderlineStyle,
3646 : LookAndFeel::eFloatID_SpellCheckerUnderlineRelativeSize }
3647 : };
3648 :
3649 : void
3650 0 : nsTextPaintStyle::InitSelectionStyle(PRInt32 aIndex)
3651 : {
3652 0 : NS_ASSERTION(aIndex >= 0 && aIndex < 5, "aIndex is invalid");
3653 0 : nsSelectionStyle* selectionStyle = &mSelectionStyle[aIndex];
3654 0 : if (selectionStyle->mInit)
3655 0 : return;
3656 :
3657 0 : StyleIDs* styleIDs = &SelectionStyleIDs[aIndex];
3658 :
3659 : nscolor foreColor, backColor;
3660 0 : if (styleIDs->mForeground == LookAndFeel::eColorID_LAST_COLOR) {
3661 0 : foreColor = NS_SAME_AS_FOREGROUND_COLOR;
3662 : } else {
3663 0 : foreColor = LookAndFeel::GetColor(styleIDs->mForeground);
3664 : }
3665 0 : if (styleIDs->mBackground == LookAndFeel::eColorID_LAST_COLOR) {
3666 0 : backColor = NS_TRANSPARENT;
3667 : } else {
3668 0 : backColor = LookAndFeel::GetColor(styleIDs->mBackground);
3669 : }
3670 :
3671 : // Convert special color to actual color
3672 0 : NS_ASSERTION(foreColor != NS_TRANSPARENT,
3673 : "foreColor cannot be NS_TRANSPARENT");
3674 0 : NS_ASSERTION(backColor != NS_SAME_AS_FOREGROUND_COLOR,
3675 : "backColor cannot be NS_SAME_AS_FOREGROUND_COLOR");
3676 0 : NS_ASSERTION(backColor != NS_40PERCENT_FOREGROUND_COLOR,
3677 : "backColor cannot be NS_40PERCENT_FOREGROUND_COLOR");
3678 :
3679 0 : foreColor = GetResolvedForeColor(foreColor, GetTextColor(), backColor);
3680 :
3681 0 : if (NS_GET_A(backColor) > 0)
3682 0 : EnsureSufficientContrast(&foreColor, &backColor);
3683 :
3684 : nscolor lineColor;
3685 : float relativeSize;
3686 : PRUint8 lineStyle;
3687 : GetSelectionUnderline(mPresContext, aIndex,
3688 0 : &lineColor, &relativeSize, &lineStyle);
3689 0 : lineColor = GetResolvedForeColor(lineColor, foreColor, backColor);
3690 :
3691 0 : selectionStyle->mTextColor = foreColor;
3692 0 : selectionStyle->mBGColor = backColor;
3693 0 : selectionStyle->mUnderlineColor = lineColor;
3694 0 : selectionStyle->mUnderlineStyle = lineStyle;
3695 0 : selectionStyle->mUnderlineRelativeSize = relativeSize;
3696 0 : selectionStyle->mInit = true;
3697 : }
3698 :
3699 : /* static */ bool
3700 0 : nsTextPaintStyle::GetSelectionUnderline(nsPresContext* aPresContext,
3701 : PRInt32 aIndex,
3702 : nscolor* aLineColor,
3703 : float* aRelativeSize,
3704 : PRUint8* aStyle)
3705 : {
3706 0 : NS_ASSERTION(aPresContext, "aPresContext is null");
3707 0 : NS_ASSERTION(aRelativeSize, "aRelativeSize is null");
3708 0 : NS_ASSERTION(aStyle, "aStyle is null");
3709 0 : NS_ASSERTION(aIndex >= 0 && aIndex < 5, "Index out of range");
3710 :
3711 0 : StyleIDs& styleID = SelectionStyleIDs[aIndex];
3712 :
3713 0 : nscolor color = LookAndFeel::GetColor(styleID.mLine);
3714 0 : PRInt32 style = LookAndFeel::GetInt(styleID.mLineStyle);
3715 0 : if (style > NS_STYLE_TEXT_DECORATION_STYLE_MAX) {
3716 0 : NS_ERROR("Invalid underline style value is specified");
3717 0 : style = NS_STYLE_TEXT_DECORATION_STYLE_SOLID;
3718 : }
3719 0 : float size = LookAndFeel::GetFloat(styleID.mLineRelativeSize);
3720 :
3721 0 : NS_ASSERTION(size, "selection underline relative size must be larger than 0");
3722 :
3723 0 : if (aLineColor) {
3724 0 : *aLineColor = color;
3725 : }
3726 0 : *aRelativeSize = size;
3727 0 : *aStyle = style;
3728 :
3729 : return style != NS_STYLE_TEXT_DECORATION_STYLE_NONE &&
3730 : color != NS_TRANSPARENT &&
3731 0 : size > 0.0f;
3732 : }
3733 :
3734 0 : inline nscolor Get40PercentColor(nscolor aForeColor, nscolor aBackColor)
3735 : {
3736 0 : nscolor foreColor = NS_RGBA(NS_GET_R(aForeColor),
3737 : NS_GET_G(aForeColor),
3738 : NS_GET_B(aForeColor),
3739 : (PRUint8)(255 * 0.4f));
3740 : // Don't use true alpha color for readability.
3741 0 : return NS_ComposeColors(aBackColor, foreColor);
3742 : }
3743 :
3744 : nscolor
3745 0 : nsTextPaintStyle::GetResolvedForeColor(nscolor aColor,
3746 : nscolor aDefaultForeColor,
3747 : nscolor aBackColor)
3748 : {
3749 0 : if (aColor == NS_SAME_AS_FOREGROUND_COLOR)
3750 0 : return aDefaultForeColor;
3751 :
3752 0 : if (aColor != NS_40PERCENT_FOREGROUND_COLOR)
3753 0 : return aColor;
3754 :
3755 : // Get actual background color
3756 0 : nscolor actualBGColor = aBackColor;
3757 0 : if (actualBGColor == NS_TRANSPARENT) {
3758 0 : InitCommonColors();
3759 0 : actualBGColor = mFrameBackgroundColor;
3760 : }
3761 0 : return Get40PercentColor(aDefaultForeColor, actualBGColor);
3762 : }
3763 :
3764 : //-----------------------------------------------------------------------------
3765 :
3766 : #ifdef ACCESSIBILITY
3767 : already_AddRefed<nsAccessible>
3768 0 : nsTextFrame::CreateAccessible()
3769 : {
3770 0 : if (IsEmpty()) {
3771 0 : nsAutoString renderedWhitespace;
3772 0 : GetRenderedText(&renderedWhitespace, nsnull, nsnull, 0, 1);
3773 0 : if (renderedWhitespace.IsEmpty()) {
3774 0 : return nsnull;
3775 : }
3776 : }
3777 :
3778 0 : nsAccessibilityService* accService = nsIPresShell::AccService();
3779 0 : if (accService) {
3780 : return accService->CreateHTMLTextAccessible(mContent,
3781 0 : PresContext()->PresShell());
3782 : }
3783 0 : return nsnull;
3784 : }
3785 : #endif
3786 :
3787 :
3788 : //-----------------------------------------------------------------------------
3789 : NS_IMETHODIMP
3790 0 : nsTextFrame::Init(nsIContent* aContent,
3791 : nsIFrame* aParent,
3792 : nsIFrame* aPrevInFlow)
3793 : {
3794 0 : NS_ASSERTION(!aPrevInFlow, "Can't be a continuation!");
3795 0 : NS_PRECONDITION(aContent->IsNodeOfType(nsINode::eTEXT),
3796 : "Bogus content!");
3797 :
3798 : // Remove any NewlineOffsetProperty or InFlowContentLengthProperty since they
3799 : // might be invalid if the content was modified while there was no frame
3800 0 : aContent->DeleteProperty(nsGkAtoms::newline);
3801 0 : if (PresContext()->BidiEnabled()) {
3802 0 : aContent->DeleteProperty(nsGkAtoms::flowlength);
3803 : }
3804 :
3805 : // Since our content has a frame now, this flag is no longer needed.
3806 0 : aContent->UnsetFlags(NS_CREATE_FRAME_IF_NON_WHITESPACE);
3807 :
3808 : // We're not a continuing frame.
3809 : // mContentOffset = 0; not necessary since we get zeroed out at init
3810 0 : return nsFrame::Init(aContent, aParent, aPrevInFlow);
3811 : }
3812 :
3813 : void
3814 0 : nsTextFrame::ClearFrameOffsetCache()
3815 : {
3816 : // See if we need to remove ourselves from the offset cache
3817 0 : if (GetStateBits() & TEXT_IN_OFFSET_CACHE) {
3818 0 : nsIFrame* primaryFrame = mContent->GetPrimaryFrame();
3819 0 : if (primaryFrame) {
3820 : // The primary frame might be null here. For example, nsLineBox::DeleteLineList
3821 : // just destroys the frames in order, which means that the primary frame is already
3822 : // dead if we're a continuing text frame, in which case, all of its properties are
3823 : // gone, and we don't need to worry about deleting this property here.
3824 0 : primaryFrame->Properties().Delete(OffsetToFrameProperty());
3825 : }
3826 0 : RemoveStateBits(TEXT_IN_OFFSET_CACHE);
3827 : }
3828 0 : }
3829 :
3830 : void
3831 0 : nsTextFrame::DestroyFrom(nsIFrame* aDestructRoot)
3832 : {
3833 0 : ClearFrameOffsetCache();
3834 :
3835 : // We might want to clear NS_CREATE_FRAME_IF_NON_WHITESPACE or
3836 : // NS_REFRAME_IF_WHITESPACE on mContent here, since our parent frame
3837 : // type might be changing. Not clear whether it's worth it.
3838 0 : ClearTextRuns();
3839 0 : if (mNextContinuation) {
3840 0 : mNextContinuation->SetPrevInFlow(nsnull);
3841 : }
3842 : // Let the base class destroy the frame
3843 0 : nsFrame::DestroyFrom(aDestructRoot);
3844 0 : }
3845 :
3846 0 : class nsContinuingTextFrame : public nsTextFrame {
3847 : public:
3848 : NS_DECL_FRAMEARENA_HELPERS
3849 :
3850 : friend nsIFrame* NS_NewContinuingTextFrame(nsIPresShell* aPresShell, nsStyleContext* aContext);
3851 :
3852 : NS_IMETHOD Init(nsIContent* aContent,
3853 : nsIFrame* aParent,
3854 : nsIFrame* aPrevInFlow);
3855 :
3856 : virtual void DestroyFrom(nsIFrame* aDestructRoot);
3857 :
3858 0 : virtual nsIFrame* GetPrevContinuation() const {
3859 0 : return mPrevContinuation;
3860 : }
3861 0 : NS_IMETHOD SetPrevContinuation(nsIFrame* aPrevContinuation) {
3862 0 : NS_ASSERTION (!aPrevContinuation || GetType() == aPrevContinuation->GetType(),
3863 : "setting a prev continuation with incorrect type!");
3864 0 : NS_ASSERTION (!nsSplittableFrame::IsInPrevContinuationChain(aPrevContinuation, this),
3865 : "creating a loop in continuation chain!");
3866 0 : mPrevContinuation = aPrevContinuation;
3867 0 : RemoveStateBits(NS_FRAME_IS_FLUID_CONTINUATION);
3868 0 : return NS_OK;
3869 : }
3870 0 : virtual nsIFrame* GetPrevInFlowVirtual() const { return GetPrevInFlow(); }
3871 0 : nsIFrame* GetPrevInFlow() const {
3872 0 : return (GetStateBits() & NS_FRAME_IS_FLUID_CONTINUATION) ? mPrevContinuation : nsnull;
3873 : }
3874 0 : NS_IMETHOD SetPrevInFlow(nsIFrame* aPrevInFlow) {
3875 0 : NS_ASSERTION (!aPrevInFlow || GetType() == aPrevInFlow->GetType(),
3876 : "setting a prev in flow with incorrect type!");
3877 0 : NS_ASSERTION (!nsSplittableFrame::IsInPrevContinuationChain(aPrevInFlow, this),
3878 : "creating a loop in continuation chain!");
3879 0 : mPrevContinuation = aPrevInFlow;
3880 0 : AddStateBits(NS_FRAME_IS_FLUID_CONTINUATION);
3881 0 : return NS_OK;
3882 : }
3883 : virtual nsIFrame* GetFirstInFlow() const;
3884 : virtual nsIFrame* GetFirstContinuation() const;
3885 :
3886 : virtual void AddInlineMinWidth(nsRenderingContext *aRenderingContext,
3887 : InlineMinWidthData *aData);
3888 : virtual void AddInlinePrefWidth(nsRenderingContext *aRenderingContext,
3889 : InlinePrefWidthData *aData);
3890 :
3891 0 : virtual nsresult GetRenderedText(nsAString* aString = nsnull,
3892 : gfxSkipChars* aSkipChars = nsnull,
3893 : gfxSkipCharsIterator* aSkipIter = nsnull,
3894 : PRUint32 aSkippedStartOffset = 0,
3895 : PRUint32 aSkippedMaxLength = PR_UINT32_MAX)
3896 0 : { return NS_ERROR_NOT_IMPLEMENTED; } // Call on a primary text frame only
3897 :
3898 : protected:
3899 0 : nsContinuingTextFrame(nsStyleContext* aContext) : nsTextFrame(aContext) {}
3900 : nsIFrame* mPrevContinuation;
3901 : };
3902 :
3903 : NS_IMETHODIMP
3904 0 : nsContinuingTextFrame::Init(nsIContent* aContent,
3905 : nsIFrame* aParent,
3906 : nsIFrame* aPrevInFlow)
3907 : {
3908 0 : NS_ASSERTION(aPrevInFlow, "Must be a continuation!");
3909 : // NOTE: bypassing nsTextFrame::Init!!!
3910 0 : nsresult rv = nsFrame::Init(aContent, aParent, aPrevInFlow);
3911 :
3912 : #ifdef IBMBIDI
3913 : nsTextFrame* nextContinuation =
3914 0 : static_cast<nsTextFrame*>(aPrevInFlow->GetNextContinuation());
3915 : #endif // IBMBIDI
3916 : // Hook the frame into the flow
3917 0 : SetPrevInFlow(aPrevInFlow);
3918 0 : aPrevInFlow->SetNextInFlow(this);
3919 0 : nsTextFrame* prev = static_cast<nsTextFrame*>(aPrevInFlow);
3920 0 : mContentOffset = prev->GetContentOffset() + prev->GetContentLengthHint();
3921 0 : NS_ASSERTION(mContentOffset < PRInt32(aContent->GetText()->GetLength()),
3922 : "Creating ContinuingTextFrame, but there is no more content");
3923 0 : if (prev->GetStyleContext() != GetStyleContext()) {
3924 : // We're taking part of prev's text, and its style may be different
3925 : // so clear its textrun which may no longer be valid (and don't set ours)
3926 0 : prev->ClearTextRuns();
3927 : } else {
3928 0 : float inflation = prev->GetFontSizeInflation();
3929 0 : SetFontSizeInflation(inflation);
3930 0 : mTextRun = prev->GetTextRun(nsTextFrame::eInflated);
3931 0 : if (inflation != 1.0f) {
3932 : gfxTextRun *uninflatedTextRun =
3933 0 : prev->GetTextRun(nsTextFrame::eNotInflated);
3934 0 : if (uninflatedTextRun) {
3935 0 : SetTextRun(uninflatedTextRun, nsTextFrame::eNotInflated, 1.0f);
3936 : }
3937 : }
3938 : }
3939 : #ifdef IBMBIDI
3940 0 : if (aPrevInFlow->GetStateBits() & NS_FRAME_IS_BIDI) {
3941 0 : FramePropertyTable *propTable = PresContext()->PropertyTable();
3942 : // Get all the properties from the prev-in-flow first to take
3943 : // advantage of the propTable's cache and simplify the assertion below
3944 0 : void* embeddingLevel = propTable->Get(aPrevInFlow, EmbeddingLevelProperty());
3945 0 : void* baseLevel = propTable->Get(aPrevInFlow, BaseLevelProperty());
3946 0 : propTable->Set(this, EmbeddingLevelProperty(), embeddingLevel);
3947 0 : propTable->Set(this, BaseLevelProperty(), baseLevel);
3948 :
3949 0 : if (nextContinuation) {
3950 0 : SetNextContinuation(nextContinuation);
3951 0 : nextContinuation->SetPrevContinuation(this);
3952 : // Adjust next-continuations' content offset as needed.
3953 0 : while (nextContinuation &&
3954 0 : nextContinuation->GetContentOffset() < mContentOffset) {
3955 0 : NS_ASSERTION(
3956 : embeddingLevel == propTable->Get(nextContinuation, EmbeddingLevelProperty()) &&
3957 : baseLevel == propTable->Get(nextContinuation, BaseLevelProperty()),
3958 : "stealing text from different type of BIDI continuation");
3959 0 : nextContinuation->mContentOffset = mContentOffset;
3960 0 : nextContinuation = static_cast<nsTextFrame*>(nextContinuation->GetNextContinuation());
3961 : }
3962 : }
3963 0 : mState |= NS_FRAME_IS_BIDI;
3964 : } // prev frame is bidi
3965 : #endif // IBMBIDI
3966 :
3967 0 : return rv;
3968 : }
3969 :
3970 : void
3971 0 : nsContinuingTextFrame::DestroyFrom(nsIFrame* aDestructRoot)
3972 : {
3973 0 : ClearFrameOffsetCache();
3974 :
3975 : // The text associated with this frame will become associated with our
3976 : // prev-continuation. If that means the text has changed style, then
3977 : // we need to wipe out the text run for the text.
3978 : // Note that mPrevContinuation can be null if we're destroying the whole
3979 : // frame chain from the start to the end.
3980 : // If this frame is mentioned in the userData for a textrun (say
3981 : // because there's a direction change at the start of this frame), then
3982 : // we have to clear the textrun because we're going away and the
3983 : // textrun had better not keep a dangling reference to us.
3984 0 : if ((GetStateBits() & TEXT_IN_TEXTRUN_USER_DATA) ||
3985 0 : (!mPrevContinuation &&
3986 0 : !(GetStateBits() & TEXT_STYLE_MATCHES_PREV_CONTINUATION)) ||
3987 : (mPrevContinuation &&
3988 0 : mPrevContinuation->GetStyleContext() != GetStyleContext())) {
3989 0 : ClearTextRuns();
3990 : // Clear the previous continuation's text run also, so that it can rebuild
3991 : // the text run to include our text.
3992 0 : if (mPrevContinuation) {
3993 : nsTextFrame *prevContinuationText =
3994 0 : static_cast<nsTextFrame*>(mPrevContinuation);
3995 0 : prevContinuationText->ClearTextRuns();
3996 : }
3997 : }
3998 0 : nsSplittableFrame::RemoveFromFlow(this);
3999 : // Let the base class destroy the frame
4000 0 : nsFrame::DestroyFrom(aDestructRoot);
4001 0 : }
4002 :
4003 : nsIFrame*
4004 0 : nsContinuingTextFrame::GetFirstInFlow() const
4005 : {
4006 : // Can't cast to |nsContinuingTextFrame*| because the first one isn't.
4007 : nsIFrame *firstInFlow,
4008 : *previous = const_cast<nsIFrame*>
4009 0 : (static_cast<const nsIFrame*>(this));
4010 0 : do {
4011 0 : firstInFlow = previous;
4012 0 : previous = firstInFlow->GetPrevInFlow();
4013 : } while (previous);
4014 0 : return firstInFlow;
4015 : }
4016 :
4017 : nsIFrame*
4018 0 : nsContinuingTextFrame::GetFirstContinuation() const
4019 : {
4020 : // Can't cast to |nsContinuingTextFrame*| because the first one isn't.
4021 : nsIFrame *firstContinuation,
4022 : *previous = const_cast<nsIFrame*>
4023 0 : (static_cast<const nsIFrame*>(mPrevContinuation));
4024 :
4025 0 : NS_ASSERTION(previous, "How can an nsContinuingTextFrame be the first continuation?");
4026 :
4027 0 : do {
4028 0 : firstContinuation = previous;
4029 0 : previous = firstContinuation->GetPrevContinuation();
4030 : } while (previous);
4031 0 : return firstContinuation;
4032 : }
4033 :
4034 : // XXX Do we want to do all the work for the first-in-flow or do the
4035 : // work for each part? (Be careful of first-letter / first-line, though,
4036 : // especially first-line!) Doing all the work on the first-in-flow has
4037 : // the advantage of avoiding the potential for incremental reflow bugs,
4038 : // but depends on our maintining the frame tree in reasonable ways even
4039 : // for edge cases (block-within-inline splits, nextBidi, etc.)
4040 :
4041 : // XXX We really need to make :first-letter happen during frame
4042 : // construction.
4043 :
4044 : // Needed for text frames in XUL.
4045 : /* virtual */ nscoord
4046 0 : nsTextFrame::GetMinWidth(nsRenderingContext *aRenderingContext)
4047 : {
4048 0 : return nsLayoutUtils::MinWidthFromInline(this, aRenderingContext);
4049 : }
4050 :
4051 : // Needed for text frames in XUL.
4052 : /* virtual */ nscoord
4053 0 : nsTextFrame::GetPrefWidth(nsRenderingContext *aRenderingContext)
4054 : {
4055 0 : return nsLayoutUtils::PrefWidthFromInline(this, aRenderingContext);
4056 : }
4057 :
4058 : /* virtual */ void
4059 0 : nsContinuingTextFrame::AddInlineMinWidth(nsRenderingContext *aRenderingContext,
4060 : InlineMinWidthData *aData)
4061 : {
4062 : // Do nothing, since the first-in-flow accounts for everything.
4063 : return;
4064 : }
4065 :
4066 : /* virtual */ void
4067 0 : nsContinuingTextFrame::AddInlinePrefWidth(nsRenderingContext *aRenderingContext,
4068 : InlinePrefWidthData *aData)
4069 : {
4070 : // Do nothing, since the first-in-flow accounts for everything.
4071 : return;
4072 : }
4073 :
4074 : static void
4075 0 : DestroySelectionDetails(SelectionDetails* aDetails)
4076 : {
4077 0 : while (aDetails) {
4078 0 : SelectionDetails* next = aDetails->mNext;
4079 0 : delete aDetails;
4080 0 : aDetails = next;
4081 : }
4082 0 : }
4083 :
4084 : //----------------------------------------------------------------------
4085 :
4086 : #if defined(DEBUG_rbs) || defined(DEBUG_bzbarsky)
4087 : static void
4088 : VerifyNotDirty(nsFrameState state)
4089 : {
4090 : bool isZero = state & NS_FRAME_FIRST_REFLOW;
4091 : bool isDirty = state & NS_FRAME_IS_DIRTY;
4092 : if (!isZero && isDirty)
4093 : NS_WARNING("internal offsets may be out-of-sync");
4094 : }
4095 : #define DEBUG_VERIFY_NOT_DIRTY(state) \
4096 : VerifyNotDirty(state)
4097 : #else
4098 : #define DEBUG_VERIFY_NOT_DIRTY(state)
4099 : #endif
4100 :
4101 : nsIFrame*
4102 0 : NS_NewTextFrame(nsIPresShell* aPresShell, nsStyleContext* aContext)
4103 : {
4104 0 : return new (aPresShell) nsTextFrame(aContext);
4105 : }
4106 :
4107 0 : NS_IMPL_FRAMEARENA_HELPERS(nsTextFrame)
4108 :
4109 : nsIFrame*
4110 0 : NS_NewContinuingTextFrame(nsIPresShell* aPresShell, nsStyleContext* aContext)
4111 : {
4112 0 : return new (aPresShell) nsContinuingTextFrame(aContext);
4113 : }
4114 :
4115 0 : NS_IMPL_FRAMEARENA_HELPERS(nsContinuingTextFrame)
4116 :
4117 0 : nsTextFrame::~nsTextFrame()
4118 : {
4119 0 : if (0 != (mState & TEXT_BLINK_ON))
4120 : {
4121 0 : nsBlinkTimer::RemoveBlinkFrame(this);
4122 : }
4123 0 : }
4124 :
4125 : NS_IMETHODIMP
4126 0 : nsTextFrame::GetCursor(const nsPoint& aPoint,
4127 : nsIFrame::Cursor& aCursor)
4128 : {
4129 0 : FillCursorInformationFromStyle(GetStyleUserInterface(), aCursor);
4130 0 : if (NS_STYLE_CURSOR_AUTO == aCursor.mCursor) {
4131 0 : aCursor.mCursor = NS_STYLE_CURSOR_TEXT;
4132 :
4133 : // If tabindex >= 0, use default cursor to indicate it's not selectable
4134 0 : nsIFrame *ancestorFrame = this;
4135 0 : while ((ancestorFrame = ancestorFrame->GetParent()) != nsnull) {
4136 0 : nsIContent *ancestorContent = ancestorFrame->GetContent();
4137 0 : if (ancestorContent && ancestorContent->HasAttr(kNameSpaceID_None, nsGkAtoms::tabindex)) {
4138 0 : nsAutoString tabIndexStr;
4139 0 : ancestorContent->GetAttr(kNameSpaceID_None, nsGkAtoms::tabindex, tabIndexStr);
4140 0 : if (!tabIndexStr.IsEmpty()) {
4141 0 : PRInt32 rv, tabIndexVal = tabIndexStr.ToInteger(&rv);
4142 0 : if (NS_SUCCEEDED(rv) && tabIndexVal >= 0) {
4143 0 : aCursor.mCursor = NS_STYLE_CURSOR_DEFAULT;
4144 : break;
4145 : }
4146 : }
4147 : }
4148 : }
4149 : }
4150 :
4151 0 : return NS_OK;
4152 : }
4153 :
4154 : nsIFrame*
4155 0 : nsTextFrame::GetLastInFlow() const
4156 : {
4157 0 : nsTextFrame* lastInFlow = const_cast<nsTextFrame*>(this);
4158 0 : while (lastInFlow->GetNextInFlow()) {
4159 0 : lastInFlow = static_cast<nsTextFrame*>(lastInFlow->GetNextInFlow());
4160 : }
4161 0 : NS_POSTCONDITION(lastInFlow, "illegal state in flow chain.");
4162 0 : return lastInFlow;
4163 : }
4164 : nsIFrame*
4165 0 : nsTextFrame::GetLastContinuation() const
4166 : {
4167 0 : nsTextFrame* lastInFlow = const_cast<nsTextFrame*>(this);
4168 0 : while (lastInFlow->mNextContinuation) {
4169 0 : lastInFlow = static_cast<nsTextFrame*>(lastInFlow->mNextContinuation);
4170 : }
4171 0 : NS_POSTCONDITION(lastInFlow, "illegal state in continuation chain.");
4172 0 : return lastInFlow;
4173 : }
4174 :
4175 : gfxTextRun*
4176 0 : nsTextFrame::GetUninflatedTextRun()
4177 : {
4178 : return static_cast<gfxTextRun*>(
4179 0 : Properties().Get(UninflatedTextRunProperty()));
4180 : }
4181 :
4182 : void
4183 0 : nsTextFrame::SetTextRun(gfxTextRun* aTextRun, TextRunType aWhichTextRun,
4184 : float aInflation)
4185 : {
4186 0 : NS_ASSERTION(aTextRun, "must have text run");
4187 :
4188 : // Our inflated text run is always stored in mTextRun. In the cases
4189 : // where our current inflation is not 1.0, however, we store two text
4190 : // runs, and the uninflated one goes in a frame property. We never
4191 : // store a single text run in both.
4192 0 : if (aWhichTextRun == eInflated) {
4193 0 : if (HasFontSizeInflation() && aInflation == 1.0f) {
4194 : // FIXME: Probably shouldn't do this within each SetTextRun
4195 : // method, but it doesn't hurt.
4196 0 : ClearTextRun(nsnull, nsTextFrame::eNotInflated);
4197 : }
4198 0 : SetFontSizeInflation(aInflation);
4199 : } else {
4200 0 : NS_ABORT_IF_FALSE(aInflation == 1.0f, "unexpected inflation");
4201 0 : if (HasFontSizeInflation()) {
4202 0 : Properties().Set(UninflatedTextRunProperty(), aTextRun);
4203 0 : return;
4204 : }
4205 : // fall through to setting mTextRun
4206 : }
4207 :
4208 0 : mTextRun = aTextRun;
4209 :
4210 : // FIXME: Add assertions testing the relationship between
4211 : // GetFontSizeInflation() and whether we have an uninflated text run
4212 : // (but be aware that text runs can go away).
4213 : }
4214 :
4215 : bool
4216 0 : nsTextFrame::RemoveTextRun(gfxTextRun* aTextRun)
4217 : {
4218 0 : if (aTextRun == mTextRun) {
4219 0 : mTextRun = nsnull;
4220 0 : return true;
4221 : }
4222 0 : FrameProperties props = Properties();
4223 0 : if ((GetStateBits() & TEXT_HAS_FONT_INFLATION) &&
4224 0 : props.Get(UninflatedTextRunProperty()) == aTextRun) {
4225 0 : props.Delete(UninflatedTextRunProperty());
4226 0 : return true;
4227 : }
4228 0 : return false;
4229 : }
4230 :
4231 : void
4232 0 : nsTextFrame::ClearTextRun(nsTextFrame* aStartContinuation,
4233 : TextRunType aWhichTextRun)
4234 : {
4235 : // save textrun because ClearAllTextRunReferences may clear ours
4236 0 : gfxTextRun* textRun = GetTextRun(aWhichTextRun);
4237 :
4238 0 : if (!textRun)
4239 0 : return;
4240 :
4241 0 : UnhookTextRunFromFrames(textRun, aStartContinuation);
4242 : // see comments in BuildTextRunForFrames...
4243 : // if (textRun->GetFlags() & gfxFontGroup::TEXT_IS_PERSISTENT) {
4244 : // NS_ERROR("Shouldn't reach here for now...");
4245 : // // the textrun's text may be referencing a DOM node that has changed,
4246 : // // so we'd better kill this textrun now.
4247 : // if (textRun->GetExpirationState()->IsTracked()) {
4248 : // gTextRuns->RemoveFromCache(textRun);
4249 : // }
4250 : // delete textRun;
4251 : // return;
4252 : // }
4253 :
4254 0 : if (!textRun->GetUserData()) {
4255 : // Remove it now because it's not doing anything useful
4256 0 : gTextRuns->RemoveFromCache(textRun);
4257 0 : delete textRun;
4258 : }
4259 : }
4260 :
4261 : NS_IMETHODIMP
4262 0 : nsTextFrame::CharacterDataChanged(CharacterDataChangeInfo* aInfo)
4263 : {
4264 0 : mContent->DeleteProperty(nsGkAtoms::newline);
4265 0 : if (PresContext()->BidiEnabled()) {
4266 0 : mContent->DeleteProperty(nsGkAtoms::flowlength);
4267 : }
4268 :
4269 : // Find the first frame whose text has changed. Frames that are entirely
4270 : // before the text change are completely unaffected.
4271 : nsTextFrame* next;
4272 0 : nsTextFrame* textFrame = this;
4273 0 : while (true) {
4274 0 : next = static_cast<nsTextFrame*>(textFrame->GetNextContinuation());
4275 0 : if (!next || next->GetContentOffset() > PRInt32(aInfo->mChangeStart))
4276 : break;
4277 0 : textFrame = next;
4278 : }
4279 :
4280 0 : PRInt32 endOfChangedText = aInfo->mChangeStart + aInfo->mReplaceLength;
4281 0 : nsTextFrame* lastDirtiedFrame = nsnull;
4282 :
4283 0 : nsIPresShell* shell = PresContext()->GetPresShell();
4284 0 : do {
4285 : // textFrame contained deleted text (or the insertion point,
4286 : // if this was a pure insertion).
4287 0 : textFrame->mState &= ~TEXT_WHITESPACE_FLAGS;
4288 0 : textFrame->ClearTextRuns();
4289 0 : if (!lastDirtiedFrame ||
4290 0 : lastDirtiedFrame->GetParent() != textFrame->GetParent()) {
4291 : // Ask the parent frame to reflow me.
4292 : shell->FrameNeedsReflow(textFrame, nsIPresShell::eStyleChange,
4293 0 : NS_FRAME_IS_DIRTY);
4294 0 : lastDirtiedFrame = textFrame;
4295 : } else {
4296 : // if the parent is a block, we're cheating here because we should
4297 : // be marking our line dirty, but we're not. nsTextFrame::SetLength
4298 : // will do that when it gets called during reflow.
4299 0 : textFrame->AddStateBits(NS_FRAME_IS_DIRTY);
4300 : }
4301 :
4302 : // Below, frames that start after the deleted text will be adjusted so that
4303 : // their offsets move with the trailing unchanged text. If this change
4304 : // deletes more text than it inserts, those frame offsets will decrease.
4305 : // We need to maintain the invariant that mContentOffset is non-decreasing
4306 : // along the continuation chain. So we need to ensure that frames that
4307 : // started in the deleted text are all still starting before the
4308 : // unchanged text.
4309 0 : if (textFrame->mContentOffset > endOfChangedText) {
4310 0 : textFrame->mContentOffset = endOfChangedText;
4311 : }
4312 :
4313 0 : textFrame = static_cast<nsTextFrame*>(textFrame->GetNextContinuation());
4314 0 : } while (textFrame && textFrame->GetContentOffset() < PRInt32(aInfo->mChangeEnd));
4315 :
4316 : // This is how much the length of the string changed by --- i.e.,
4317 : // how much the trailing unchanged text moved.
4318 : PRInt32 sizeChange =
4319 0 : aInfo->mChangeStart + aInfo->mReplaceLength - aInfo->mChangeEnd;
4320 :
4321 0 : if (sizeChange) {
4322 : // Fix the offsets of the text frames that start in the trailing
4323 : // unchanged text.
4324 0 : while (textFrame) {
4325 0 : textFrame->mContentOffset += sizeChange;
4326 : // XXX we could rescue some text runs by adjusting their user data
4327 : // to reflect the change in DOM offsets
4328 0 : textFrame->ClearTextRuns();
4329 0 : textFrame = static_cast<nsTextFrame*>(textFrame->GetNextContinuation());
4330 : }
4331 : }
4332 :
4333 0 : return NS_OK;
4334 : }
4335 :
4336 : /* virtual */ void
4337 0 : nsTextFrame::DidSetStyleContext(nsStyleContext* aOldStyleContext)
4338 : {
4339 0 : nsFrame::DidSetStyleContext(aOldStyleContext);
4340 0 : ClearTextRuns();
4341 0 : }
4342 :
4343 : class nsDisplayText : public nsCharClipDisplayItem {
4344 : public:
4345 0 : nsDisplayText(nsDisplayListBuilder* aBuilder, nsTextFrame* aFrame) :
4346 : nsCharClipDisplayItem(aBuilder, aFrame),
4347 0 : mDisableSubpixelAA(false) {
4348 0 : MOZ_COUNT_CTOR(nsDisplayText);
4349 0 : }
4350 : #ifdef NS_BUILD_REFCNT_LOGGING
4351 0 : virtual ~nsDisplayText() {
4352 0 : MOZ_COUNT_DTOR(nsDisplayText);
4353 0 : }
4354 : #endif
4355 :
4356 0 : virtual nsRect GetBounds(nsDisplayListBuilder* aBuilder) {
4357 0 : return mFrame->GetVisualOverflowRectRelativeToSelf() + ToReferenceFrame();
4358 : }
4359 0 : virtual void HitTest(nsDisplayListBuilder* aBuilder, const nsRect& aRect,
4360 : HitTestState* aState, nsTArray<nsIFrame*> *aOutFrames) {
4361 0 : if (nsRect(ToReferenceFrame(), mFrame->GetSize()).Intersects(aRect)) {
4362 0 : aOutFrames->AppendElement(mFrame);
4363 : }
4364 0 : }
4365 : virtual void Paint(nsDisplayListBuilder* aBuilder,
4366 : nsRenderingContext* aCtx);
4367 0 : NS_DISPLAY_DECL_NAME("Text", TYPE_TEXT)
4368 :
4369 0 : virtual nsRect GetComponentAlphaBounds(nsDisplayListBuilder* aBuilder)
4370 : {
4371 0 : return GetBounds(aBuilder);
4372 : }
4373 :
4374 0 : virtual void DisableComponentAlpha() { mDisableSubpixelAA = true; }
4375 :
4376 : bool mDisableSubpixelAA;
4377 : };
4378 :
4379 : void
4380 0 : nsDisplayText::Paint(nsDisplayListBuilder* aBuilder,
4381 : nsRenderingContext* aCtx) {
4382 : // Add 1 pixel of dirty area around mVisibleRect to allow us to paint
4383 : // antialiased pixels beyond the measured text extents.
4384 : // This is temporary until we do this in the actual calculation of text extents.
4385 0 : nsRect extraVisible = mVisibleRect;
4386 0 : nscoord appUnitsPerDevPixel = mFrame->PresContext()->AppUnitsPerDevPixel();
4387 0 : extraVisible.Inflate(appUnitsPerDevPixel, appUnitsPerDevPixel);
4388 0 : nsTextFrame* f = static_cast<nsTextFrame*>(mFrame);
4389 :
4390 : gfxContextAutoDisableSubpixelAntialiasing disable(aCtx->ThebesContext(),
4391 0 : mDisableSubpixelAA);
4392 0 : NS_ASSERTION(mLeftEdge >= 0, "illegal left edge");
4393 0 : NS_ASSERTION(mRightEdge >= 0, "illegal right edge");
4394 0 : f->PaintText(aCtx, ToReferenceFrame(), extraVisible, *this);
4395 0 : }
4396 :
4397 : NS_IMETHODIMP
4398 0 : nsTextFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
4399 : const nsRect& aDirtyRect,
4400 : const nsDisplayListSet& aLists)
4401 : {
4402 0 : if (!IsVisibleForPainting(aBuilder))
4403 0 : return NS_OK;
4404 :
4405 0 : DO_GLOBAL_REFLOW_COUNT_DSP("nsTextFrame");
4406 :
4407 0 : if ((0 != (mState & TEXT_BLINK_ON)) && nsBlinkTimer::GetBlinkIsOff() &&
4408 0 : PresContext()->IsDynamic() && !aBuilder->IsForEventDelivery())
4409 0 : return NS_OK;
4410 :
4411 : return aLists.Content()->AppendNewToTop(
4412 0 : new (aBuilder) nsDisplayText(aBuilder, this));
4413 : }
4414 :
4415 : static nsIFrame*
4416 0 : GetGeneratedContentOwner(nsIFrame* aFrame, bool* aIsBefore)
4417 : {
4418 0 : *aIsBefore = false;
4419 0 : while (aFrame && (aFrame->GetStateBits() & NS_FRAME_GENERATED_CONTENT)) {
4420 0 : if (aFrame->GetStyleContext()->GetPseudo() == nsCSSPseudoElements::before) {
4421 0 : *aIsBefore = true;
4422 : }
4423 0 : aFrame = aFrame->GetParent();
4424 : }
4425 0 : return aFrame;
4426 : }
4427 :
4428 : SelectionDetails*
4429 0 : nsTextFrame::GetSelectionDetails()
4430 : {
4431 0 : const nsFrameSelection* frameSelection = GetConstFrameSelection();
4432 0 : if (frameSelection->GetTableCellSelection()) {
4433 0 : return nsnull;
4434 : }
4435 0 : if (!(GetStateBits() & NS_FRAME_GENERATED_CONTENT)) {
4436 : SelectionDetails* details =
4437 : frameSelection->LookUpSelection(mContent, GetContentOffset(),
4438 0 : GetContentLength(), false);
4439 : SelectionDetails* sd;
4440 0 : for (sd = details; sd; sd = sd->mNext) {
4441 0 : sd->mStart += mContentOffset;
4442 0 : sd->mEnd += mContentOffset;
4443 : }
4444 0 : return details;
4445 : }
4446 :
4447 : // Check if the beginning or end of the element is selected, depending on
4448 : // whether we're :before content or :after content.
4449 : bool isBefore;
4450 0 : nsIFrame* owner = GetGeneratedContentOwner(this, &isBefore);
4451 0 : if (!owner || !owner->GetContent())
4452 0 : return nsnull;
4453 :
4454 : SelectionDetails* details =
4455 : frameSelection->LookUpSelection(owner->GetContent(),
4456 0 : isBefore ? 0 : owner->GetContent()->GetChildCount(), 0, false);
4457 : SelectionDetails* sd;
4458 0 : for (sd = details; sd; sd = sd->mNext) {
4459 : // The entire text is selected!
4460 0 : sd->mStart = GetContentOffset();
4461 0 : sd->mEnd = GetContentEnd();
4462 : }
4463 0 : return details;
4464 : }
4465 :
4466 : static void
4467 0 : FillClippedRect(gfxContext* aCtx, nsPresContext* aPresContext,
4468 : nscolor aColor, const gfxRect& aDirtyRect, const gfxRect& aRect)
4469 : {
4470 0 : gfxRect r = aRect.Intersect(aDirtyRect);
4471 : // For now, we need to put this in pixel coordinates
4472 0 : PRInt32 app = aPresContext->AppUnitsPerDevPixel();
4473 0 : aCtx->NewPath();
4474 : // pixel-snap
4475 0 : aCtx->Rectangle(gfxRect(r.X() / app, r.Y() / app,
4476 0 : r.Width() / app, r.Height() / app), true);
4477 0 : aCtx->SetColor(gfxRGBA(aColor));
4478 0 : aCtx->Fill();
4479 0 : }
4480 :
4481 : void
4482 0 : nsTextFrame::GetTextDecorations(nsPresContext* aPresContext,
4483 : nsTextFrame::TextDecorations& aDecorations)
4484 : {
4485 0 : const nsCompatibility compatMode = aPresContext->CompatibilityMode();
4486 :
4487 0 : bool useOverride = false;
4488 : nscolor overrideColor;
4489 :
4490 : // frameTopOffset represents the offset to f's top from our baseline in our
4491 : // coordinate space
4492 : // baselineOffset represents the offset from our baseline to f's baseline or
4493 : // the nearest block's baseline, in our coordinate space, whichever is closest
4494 : // during the particular iteration
4495 0 : nscoord frameTopOffset = mAscent,
4496 0 : baselineOffset = 0;
4497 :
4498 0 : bool nearestBlockFound = false;
4499 :
4500 0 : for (nsIFrame* f = this, *fChild = nsnull;
4501 : f;
4502 : fChild = f,
4503 : f = nsLayoutUtils::GetParentOrPlaceholderFor(
4504 0 : aPresContext->FrameManager(), f))
4505 : {
4506 0 : nsStyleContext *const context = f->GetStyleContext();
4507 0 : if (!context->HasTextDecorationLines()) {
4508 0 : break;
4509 : }
4510 :
4511 0 : const nsStyleTextReset *const styleText = context->GetStyleTextReset();
4512 0 : const PRUint8 textDecorations = styleText->mTextDecorationLine;
4513 :
4514 0 : if (!useOverride &&
4515 : (NS_STYLE_TEXT_DECORATION_LINE_OVERRIDE_ALL & textDecorations)) {
4516 : // This handles the <a href="blah.html"><font color="green">La
4517 : // la la</font></a> case. The link underline should be green.
4518 0 : useOverride = true;
4519 : overrideColor =
4520 0 : nsLayoutUtils::GetColor(f, eCSSProperty_text_decoration_color);
4521 : }
4522 :
4523 0 : const bool firstBlock = !nearestBlockFound && nsLayoutUtils::GetAsBlock(f);
4524 :
4525 : // Not updating positions once we hit a parent block is equivalent to
4526 : // the CSS 2.1 spec that blocks should propagate decorations down to their
4527 : // children (albeit the style should be preserved)
4528 : // However, if we're vertically aligned within a block, then we need to
4529 : // recover the right baseline from the line by querying the FrameProperty
4530 : // that should be set (see nsLineLayout::VerticalAlignLine).
4531 0 : if (firstBlock) {
4532 : // At this point, fChild can't be null since TextFrames can't be blocks
4533 : const nsStyleCoord& vAlign =
4534 0 : fChild->GetStyleContext()->GetStyleTextReset()->mVerticalAlign;
4535 0 : if (vAlign.GetUnit() != eStyleUnit_Enumerated ||
4536 0 : vAlign.GetIntValue() != NS_STYLE_VERTICAL_ALIGN_BASELINE)
4537 : {
4538 : // Since offset is the offset in the child's coordinate space, we have
4539 : // to undo the accumulation to bring the transform out of the block's
4540 : // coordinate space
4541 : baselineOffset =
4542 0 : frameTopOffset - (fChild->GetRect().y - fChild->GetRelativeOffset().y)
4543 0 : - NS_PTR_TO_INT32(
4544 0 : fChild->Properties().Get(nsIFrame::LineBaselineOffset()));
4545 : }
4546 : }
4547 0 : else if (!nearestBlockFound) {
4548 0 : baselineOffset = frameTopOffset - f->GetBaseline();
4549 : }
4550 :
4551 0 : nearestBlockFound = nearestBlockFound || firstBlock;
4552 0 : frameTopOffset += f->GetRect().y - f->GetRelativeOffset().y;
4553 :
4554 0 : const PRUint8 style = styleText->GetDecorationStyle();
4555 : // Accumulate only elements that have decorations with a genuine style
4556 0 : if (textDecorations && style != NS_STYLE_TEXT_DECORATION_STYLE_NONE) {
4557 : const nscolor color = useOverride ? overrideColor
4558 0 : : nsLayoutUtils::GetColor(f, eCSSProperty_text_decoration_color);
4559 :
4560 0 : if (textDecorations & NS_STYLE_TEXT_DECORATION_LINE_UNDERLINE) {
4561 : aDecorations.mUnderlines.AppendElement(
4562 0 : nsTextFrame::LineDecoration(f, baselineOffset, color, style));
4563 : }
4564 0 : if (textDecorations & NS_STYLE_TEXT_DECORATION_LINE_OVERLINE) {
4565 : aDecorations.mOverlines.AppendElement(
4566 0 : nsTextFrame::LineDecoration(f, baselineOffset, color, style));
4567 : }
4568 0 : if (textDecorations & NS_STYLE_TEXT_DECORATION_LINE_LINE_THROUGH) {
4569 : aDecorations.mStrikes.AppendElement(
4570 0 : nsTextFrame::LineDecoration(f, baselineOffset, color, style));
4571 : }
4572 : }
4573 :
4574 : // In all modes, if we're on an inline-block or inline-table (or
4575 : // inline-stack, inline-box, inline-grid), we're done.
4576 0 : const nsStyleDisplay *disp = context->GetStyleDisplay();
4577 0 : if (disp->mDisplay != NS_STYLE_DISPLAY_INLINE &&
4578 0 : disp->IsInlineOutside()) {
4579 0 : break;
4580 : }
4581 :
4582 0 : if (compatMode == eCompatibility_NavQuirks) {
4583 : // In quirks mode, if we're on an HTML table element, we're done.
4584 0 : if (f->GetContent()->IsHTML(nsGkAtoms::table)) {
4585 0 : break;
4586 : }
4587 : } else {
4588 : // In standards/almost-standards mode, if we're on an
4589 : // absolutely-positioned element or a floating element, we're done.
4590 0 : if (disp->IsFloating() || disp->IsAbsolutelyPositioned()) {
4591 0 : break;
4592 : }
4593 : }
4594 : }
4595 0 : }
4596 :
4597 : void
4598 0 : nsTextFrame::UnionAdditionalOverflow(nsPresContext* aPresContext,
4599 : const nsHTMLReflowState& aBlockReflowState,
4600 : PropertyProvider& aProvider,
4601 : nsRect* aVisualOverflowRect,
4602 : bool aIncludeTextDecorations)
4603 : {
4604 : // Text-shadow overflows
4605 : nsRect shadowRect =
4606 0 : nsLayoutUtils::GetTextShadowRectsUnion(*aVisualOverflowRect, this);
4607 0 : aVisualOverflowRect->UnionRect(*aVisualOverflowRect, shadowRect);
4608 :
4609 0 : if (IsFloatingFirstLetterChild()) {
4610 : // The underline/overline drawable area must be contained in the overflow
4611 : // rect when this is in floating first letter frame at *both* modes.
4612 0 : nsFontMetrics* fm = aProvider.GetFontMetrics();
4613 0 : nscoord fontAscent = fm->MaxAscent();
4614 0 : nscoord fontHeight = fm->MaxHeight();
4615 0 : nsRect fontRect(0, mAscent - fontAscent, GetSize().width, fontHeight);
4616 0 : aVisualOverflowRect->UnionRect(*aVisualOverflowRect, fontRect);
4617 : }
4618 0 : if (aIncludeTextDecorations) {
4619 : // Since CSS 2.1 requires that text-decoration defined on ancestors maintain
4620 : // style and position, they can be drawn at virtually any y-offset, so
4621 : // maxima and minima are required to reliably generate the rectangle for
4622 : // them
4623 0 : TextDecorations textDecs;
4624 0 : GetTextDecorations(aPresContext, textDecs);
4625 0 : if (textDecs.HasDecorationLines()) {
4626 : nscoord inflationMinFontSize =
4627 : nsLayoutUtils::InflationMinFontSizeFor(aBlockReflowState.frame,
4628 0 : nsLayoutUtils::eInReflow);
4629 :
4630 0 : const nscoord width = GetSize().width;
4631 0 : const gfxFloat appUnitsPerDevUnit = aPresContext->AppUnitsPerDevPixel(),
4632 0 : gfxWidth = width / appUnitsPerDevUnit,
4633 0 : ascent = gfxFloat(mAscent) / appUnitsPerDevUnit;
4634 0 : nscoord top(nscoord_MAX), bottom(nscoord_MIN);
4635 : // Below we loop through all text decorations and compute the rectangle
4636 : // containing all of them, in this frame's coordinate space
4637 0 : for (PRUint32 i = 0; i < textDecs.mUnderlines.Length(); ++i) {
4638 0 : const LineDecoration& dec = textDecs.mUnderlines[i];
4639 :
4640 : float inflation = nsLayoutUtils::FontSizeInflationInner(dec.mFrame,
4641 0 : inflationMinFontSize);
4642 : const gfxFont::Metrics metrics =
4643 0 : GetFirstFontMetrics(GetFontGroupForFrame(dec.mFrame, inflation));
4644 :
4645 : const nsRect decorationRect =
4646 : nsCSSRendering::GetTextDecorationRect(aPresContext,
4647 : gfxSize(gfxWidth, metrics.underlineSize),
4648 : ascent, metrics.underlineOffset,
4649 0 : NS_STYLE_TEXT_DECORATION_LINE_UNDERLINE, dec.mStyle) +
4650 0 : nsPoint(0, -dec.mBaselineOffset);
4651 :
4652 0 : top = NS_MIN(decorationRect.y, top);
4653 0 : bottom = NS_MAX(decorationRect.YMost(), bottom);
4654 : }
4655 0 : for (PRUint32 i = 0; i < textDecs.mOverlines.Length(); ++i) {
4656 0 : const LineDecoration& dec = textDecs.mOverlines[i];
4657 :
4658 : float inflation = nsLayoutUtils::FontSizeInflationInner(dec.mFrame,
4659 0 : inflationMinFontSize);
4660 : const gfxFont::Metrics metrics =
4661 0 : GetFirstFontMetrics(GetFontGroupForFrame(dec.mFrame, inflation));
4662 :
4663 : const nsRect decorationRect =
4664 : nsCSSRendering::GetTextDecorationRect(aPresContext,
4665 : gfxSize(gfxWidth, metrics.underlineSize),
4666 : ascent, metrics.maxAscent,
4667 0 : NS_STYLE_TEXT_DECORATION_LINE_OVERLINE, dec.mStyle) +
4668 0 : nsPoint(0, -dec.mBaselineOffset);
4669 :
4670 0 : top = NS_MIN(decorationRect.y, top);
4671 0 : bottom = NS_MAX(decorationRect.YMost(), bottom);
4672 : }
4673 0 : for (PRUint32 i = 0; i < textDecs.mStrikes.Length(); ++i) {
4674 0 : const LineDecoration& dec = textDecs.mStrikes[i];
4675 :
4676 : float inflation = nsLayoutUtils::FontSizeInflationInner(dec.mFrame,
4677 0 : inflationMinFontSize);
4678 : const gfxFont::Metrics metrics =
4679 0 : GetFirstFontMetrics(GetFontGroupForFrame(dec.mFrame, inflation));
4680 :
4681 : const nsRect decorationRect =
4682 : nsCSSRendering::GetTextDecorationRect(aPresContext,
4683 : gfxSize(gfxWidth, metrics.strikeoutSize),
4684 : ascent, metrics.strikeoutOffset,
4685 0 : NS_STYLE_TEXT_DECORATION_LINE_LINE_THROUGH, dec.mStyle) +
4686 0 : nsPoint(0, -dec.mBaselineOffset);
4687 0 : top = NS_MIN(decorationRect.y, top);
4688 0 : bottom = NS_MAX(decorationRect.YMost(), bottom);
4689 : }
4690 :
4691 : aVisualOverflowRect->UnionRect(*aVisualOverflowRect,
4692 0 : nsRect(0, top, width, bottom - top));
4693 : }
4694 : }
4695 : // When this frame is not selected, the text-decoration area must be in
4696 : // frame bounds.
4697 0 : if (!IsSelected() ||
4698 0 : !CombineSelectionUnderlineRect(aPresContext, *aVisualOverflowRect))
4699 : return;
4700 0 : AddStateBits(TEXT_SELECTION_UNDERLINE_OVERFLOWED);
4701 : }
4702 :
4703 : static gfxFloat
4704 0 : ComputeDescentLimitForSelectionUnderline(nsPresContext* aPresContext,
4705 : nsTextFrame* aFrame,
4706 : const gfxFont::Metrics& aFontMetrics)
4707 : {
4708 0 : gfxFloat app = aPresContext->AppUnitsPerDevPixel();
4709 : nscoord lineHeightApp =
4710 : nsHTMLReflowState::CalcLineHeight(aFrame->GetStyleContext(), NS_AUTOHEIGHT,
4711 0 : aFrame->GetFontSizeInflation());
4712 0 : gfxFloat lineHeight = gfxFloat(lineHeightApp) / app;
4713 0 : if (lineHeight <= aFontMetrics.maxHeight) {
4714 0 : return aFontMetrics.maxDescent;
4715 : }
4716 0 : return aFontMetrics.maxDescent + (lineHeight - aFontMetrics.maxHeight) / 2;
4717 : }
4718 :
4719 :
4720 : // Make sure this stays in sync with DrawSelectionDecorations below
4721 : static const SelectionType SelectionTypesWithDecorations =
4722 : nsISelectionController::SELECTION_SPELLCHECK |
4723 : nsISelectionController::SELECTION_IME_RAWINPUT |
4724 : nsISelectionController::SELECTION_IME_SELECTEDRAWTEXT |
4725 : nsISelectionController::SELECTION_IME_CONVERTEDTEXT |
4726 : nsISelectionController::SELECTION_IME_SELECTEDCONVERTEDTEXT;
4727 :
4728 : static gfxFloat
4729 0 : ComputeSelectionUnderlineHeight(nsPresContext* aPresContext,
4730 : const gfxFont::Metrics& aFontMetrics,
4731 : SelectionType aSelectionType)
4732 : {
4733 0 : switch (aSelectionType) {
4734 : case nsISelectionController::SELECTION_IME_RAWINPUT:
4735 : case nsISelectionController::SELECTION_IME_SELECTEDRAWTEXT:
4736 : case nsISelectionController::SELECTION_IME_CONVERTEDTEXT:
4737 : case nsISelectionController::SELECTION_IME_SELECTEDCONVERTEDTEXT:
4738 0 : return aFontMetrics.underlineSize;
4739 : case nsISelectionController::SELECTION_SPELLCHECK: {
4740 : // The thickness of the spellchecker underline shouldn't honor the font
4741 : // metrics. It should be constant pixels value which is decided from the
4742 : // default font size. Note that if the actual font size is smaller than
4743 : // the default font size, we should use the actual font size because the
4744 : // computed value from the default font size can be too thick for the
4745 : // current font size.
4746 : PRInt32 defaultFontSize =
4747 0 : aPresContext->AppUnitsToDevPixels(nsStyleFont(aPresContext).mFont.size);
4748 : gfxFloat fontSize = NS_MIN(gfxFloat(defaultFontSize),
4749 0 : aFontMetrics.emHeight);
4750 0 : fontSize = NS_MAX(fontSize, 1.0);
4751 0 : return ceil(fontSize / 20);
4752 : }
4753 : default:
4754 0 : NS_WARNING("Requested underline style is not valid");
4755 0 : return aFontMetrics.underlineSize;
4756 : }
4757 : }
4758 :
4759 : /**
4760 : * This, plus SelectionTypesWithDecorations, encapsulates all knowledge about
4761 : * drawing text decoration for selections.
4762 : */
4763 0 : static void DrawSelectionDecorations(gfxContext* aContext,
4764 : const gfxRect& aDirtyRect,
4765 : SelectionType aType,
4766 : nsTextFrame* aFrame,
4767 : nsTextPaintStyle& aTextPaintStyle,
4768 : const nsTextRangeStyle &aRangeStyle,
4769 : const gfxPoint& aPt, gfxFloat aWidth,
4770 : gfxFloat aAscent, const gfxFont::Metrics& aFontMetrics)
4771 : {
4772 0 : gfxPoint pt(aPt);
4773 : gfxSize size(aWidth,
4774 : ComputeSelectionUnderlineHeight(aTextPaintStyle.PresContext(),
4775 0 : aFontMetrics, aType));
4776 : gfxFloat descentLimit =
4777 : ComputeDescentLimitForSelectionUnderline(aTextPaintStyle.PresContext(),
4778 0 : aFrame, aFontMetrics);
4779 :
4780 : float relativeSize;
4781 : PRUint8 style;
4782 : nscolor color;
4783 : PRInt32 index =
4784 0 : nsTextPaintStyle::GetUnderlineStyleIndexForSelectionType(aType);
4785 : bool weDefineSelectionUnderline =
4786 : aTextPaintStyle.GetSelectionUnderlineForPaint(index, &color,
4787 0 : &relativeSize, &style);
4788 :
4789 0 : switch (aType) {
4790 : case nsISelectionController::SELECTION_IME_RAWINPUT:
4791 : case nsISelectionController::SELECTION_IME_SELECTEDRAWTEXT:
4792 : case nsISelectionController::SELECTION_IME_CONVERTEDTEXT:
4793 : case nsISelectionController::SELECTION_IME_SELECTEDCONVERTEDTEXT: {
4794 : // IME decoration lines should not be drawn on the both ends, i.e., we
4795 : // need to cut both edges of the decoration lines. Because same style
4796 : // IME selections can adjoin, but the users need to be able to know
4797 : // where are the boundaries of the selections.
4798 : //
4799 : // X: underline
4800 : //
4801 : // IME selection #1 IME selection #2 IME selection #3
4802 : // | | |
4803 : // | XXXXXXXXXXXXXXXXXXX | XXXXXXXXXXXXXXXXXXXX | XXXXXXXXXXXXXXXXXXX
4804 : // +---------------------+----------------------+--------------------
4805 : // ^ ^ ^ ^ ^
4806 : // gap gap gap
4807 0 : pt.x += 1.0;
4808 0 : size.width -= 2.0;
4809 0 : if (aRangeStyle.IsDefined()) {
4810 : // If IME defines the style, that should override our definition.
4811 0 : if (aRangeStyle.IsLineStyleDefined()) {
4812 0 : if (aRangeStyle.mLineStyle == nsTextRangeStyle::LINESTYLE_NONE) {
4813 0 : return;
4814 : }
4815 0 : style = aRangeStyle.mLineStyle;
4816 0 : relativeSize = aRangeStyle.mIsBoldLine ? 2.0f : 1.0f;
4817 0 : } else if (!weDefineSelectionUnderline) {
4818 : // There is no underline style definition.
4819 0 : return;
4820 : }
4821 0 : if (aRangeStyle.IsUnderlineColorDefined()) {
4822 0 : color = aRangeStyle.mUnderlineColor;
4823 0 : } else if (aRangeStyle.IsForegroundColorDefined()) {
4824 0 : color = aRangeStyle.mForegroundColor;
4825 : } else {
4826 0 : NS_ASSERTION(!aRangeStyle.IsBackgroundColorDefined(),
4827 : "Only the background color is defined");
4828 0 : color = aTextPaintStyle.GetTextColor();
4829 : }
4830 0 : } else if (!weDefineSelectionUnderline) {
4831 : // IME doesn't specify the selection style and we don't define selection
4832 : // underline.
4833 0 : return;
4834 : }
4835 0 : break;
4836 : }
4837 : case nsISelectionController::SELECTION_SPELLCHECK:
4838 0 : if (!weDefineSelectionUnderline)
4839 0 : return;
4840 0 : break;
4841 : default:
4842 0 : NS_WARNING("Requested selection decorations when there aren't any");
4843 0 : return;
4844 : }
4845 0 : size.height *= relativeSize;
4846 : nsCSSRendering::PaintDecorationLine(
4847 : aContext, aDirtyRect, color, pt, size, aAscent, aFontMetrics.underlineOffset,
4848 0 : NS_STYLE_TEXT_DECORATION_LINE_UNDERLINE, style, descentLimit);
4849 : }
4850 :
4851 : /**
4852 : * This function encapsulates all knowledge of how selections affect foreground
4853 : * and background colors.
4854 : * @return true if the selection affects colors, false otherwise
4855 : * @param aForeground the foreground color to use
4856 : * @param aBackground the background color to use, or RGBA(0,0,0,0) if no
4857 : * background should be painted
4858 : */
4859 0 : static bool GetSelectionTextColors(SelectionType aType,
4860 : nsTextPaintStyle& aTextPaintStyle,
4861 : const nsTextRangeStyle &aRangeStyle,
4862 : nscolor* aForeground, nscolor* aBackground)
4863 : {
4864 0 : switch (aType) {
4865 : case nsISelectionController::SELECTION_NORMAL:
4866 0 : return aTextPaintStyle.GetSelectionColors(aForeground, aBackground);
4867 : case nsISelectionController::SELECTION_FIND:
4868 0 : aTextPaintStyle.GetHighlightColors(aForeground, aBackground);
4869 0 : return true;
4870 : case nsISelectionController::SELECTION_URLSECONDARY:
4871 0 : aTextPaintStyle.GetURLSecondaryColor(aForeground);
4872 0 : *aBackground = NS_RGBA(0,0,0,0);
4873 0 : return true;
4874 : case nsISelectionController::SELECTION_IME_RAWINPUT:
4875 : case nsISelectionController::SELECTION_IME_SELECTEDRAWTEXT:
4876 : case nsISelectionController::SELECTION_IME_CONVERTEDTEXT:
4877 : case nsISelectionController::SELECTION_IME_SELECTEDCONVERTEDTEXT:
4878 0 : if (aRangeStyle.IsDefined()) {
4879 0 : *aForeground = aTextPaintStyle.GetTextColor();
4880 0 : *aBackground = NS_RGBA(0,0,0,0);
4881 0 : if (!aRangeStyle.IsForegroundColorDefined() &&
4882 0 : !aRangeStyle.IsBackgroundColorDefined()) {
4883 0 : return false;
4884 : }
4885 0 : if (aRangeStyle.IsForegroundColorDefined()) {
4886 0 : *aForeground = aRangeStyle.mForegroundColor;
4887 : }
4888 0 : if (aRangeStyle.IsBackgroundColorDefined()) {
4889 0 : *aBackground = aRangeStyle.mBackgroundColor;
4890 : }
4891 0 : return true;
4892 : }
4893 : aTextPaintStyle.GetIMESelectionColors(
4894 : nsTextPaintStyle::GetUnderlineStyleIndexForSelectionType(aType),
4895 0 : aForeground, aBackground);
4896 0 : return true;
4897 : default:
4898 0 : *aForeground = aTextPaintStyle.GetTextColor();
4899 0 : *aBackground = NS_RGBA(0,0,0,0);
4900 0 : return false;
4901 : }
4902 : }
4903 :
4904 : /**
4905 : * This class lets us iterate over chunks of text in a uniform selection state,
4906 : * observing cluster boundaries, in content order, maintaining the current
4907 : * x-offset as we go, and telling whether the text chunk has a hyphen after
4908 : * it or not. The caller is responsible for actually computing the advance
4909 : * width of each chunk.
4910 : */
4911 : class SelectionIterator {
4912 : public:
4913 : /**
4914 : * aStart and aLength are in the original string. aSelectionDetails is
4915 : * according to the original string.
4916 : * @param aXOffset the offset from the origin of the frame to the start
4917 : * of the text (the left baseline origin for LTR, the right baseline origin
4918 : * for RTL)
4919 : */
4920 : SelectionIterator(SelectionDetails** aSelectionDetails,
4921 : PRInt32 aStart, PRInt32 aLength,
4922 : PropertyProvider& aProvider, gfxTextRun* aTextRun,
4923 : gfxFloat aXOffset);
4924 :
4925 : /**
4926 : * Returns the next segment of uniformly selected (or not) text.
4927 : * @param aXOffset the offset from the origin of the frame to the start
4928 : * of the text (the left baseline origin for LTR, the right baseline origin
4929 : * for RTL)
4930 : * @param aOffset the transformed string offset of the text for this segment
4931 : * @param aLength the transformed string length of the text for this segment
4932 : * @param aHyphenWidth if a hyphen is to be rendered after the text, the
4933 : * width of the hyphen, otherwise zero
4934 : * @param aType the selection type for this segment
4935 : * @param aStyle the selection style for this segment
4936 : * @return false if there are no more segments
4937 : */
4938 : bool GetNextSegment(gfxFloat* aXOffset, PRUint32* aOffset, PRUint32* aLength,
4939 : gfxFloat* aHyphenWidth, SelectionType* aType,
4940 : nsTextRangeStyle* aStyle);
4941 0 : void UpdateWithAdvance(gfxFloat aAdvance) {
4942 0 : mXOffset += aAdvance*mTextRun->GetDirection();
4943 0 : }
4944 :
4945 : private:
4946 : SelectionDetails** mSelectionDetails;
4947 : PropertyProvider& mProvider;
4948 : gfxTextRun* mTextRun;
4949 : gfxSkipCharsIterator mIterator;
4950 : PRInt32 mOriginalStart;
4951 : PRInt32 mOriginalEnd;
4952 : gfxFloat mXOffset;
4953 : };
4954 :
4955 0 : SelectionIterator::SelectionIterator(SelectionDetails** aSelectionDetails,
4956 : PRInt32 aStart, PRInt32 aLength, PropertyProvider& aProvider,
4957 : gfxTextRun* aTextRun, gfxFloat aXOffset)
4958 : : mSelectionDetails(aSelectionDetails), mProvider(aProvider),
4959 0 : mTextRun(aTextRun), mIterator(aProvider.GetStart()),
4960 : mOriginalStart(aStart), mOriginalEnd(aStart + aLength),
4961 0 : mXOffset(aXOffset)
4962 : {
4963 0 : mIterator.SetOriginalOffset(aStart);
4964 0 : }
4965 :
4966 0 : bool SelectionIterator::GetNextSegment(gfxFloat* aXOffset,
4967 : PRUint32* aOffset, PRUint32* aLength, gfxFloat* aHyphenWidth,
4968 : SelectionType* aType, nsTextRangeStyle* aStyle)
4969 : {
4970 0 : if (mIterator.GetOriginalOffset() >= mOriginalEnd)
4971 0 : return false;
4972 :
4973 : // save offset into transformed string now
4974 0 : PRUint32 runOffset = mIterator.GetSkippedOffset();
4975 :
4976 0 : PRInt32 index = mIterator.GetOriginalOffset() - mOriginalStart;
4977 0 : SelectionDetails* sdptr = mSelectionDetails[index];
4978 : SelectionType type =
4979 0 : sdptr ? sdptr->mType : nsISelectionController::SELECTION_NONE;
4980 0 : nsTextRangeStyle style;
4981 0 : if (sdptr) {
4982 0 : style = sdptr->mTextRangeStyle;
4983 : }
4984 0 : for (++index; mOriginalStart + index < mOriginalEnd; ++index) {
4985 0 : if (sdptr != mSelectionDetails[index])
4986 0 : break;
4987 : }
4988 0 : mIterator.SetOriginalOffset(index + mOriginalStart);
4989 :
4990 : // Advance to the next cluster boundary
4991 0 : while (mIterator.GetOriginalOffset() < mOriginalEnd &&
4992 0 : !mIterator.IsOriginalCharSkipped() &&
4993 0 : !mTextRun->IsClusterStart(mIterator.GetSkippedOffset())) {
4994 0 : mIterator.AdvanceOriginal(1);
4995 : }
4996 :
4997 : bool haveHyphenBreak =
4998 0 : (mProvider.GetFrame()->GetStateBits() & TEXT_HYPHEN_BREAK) != 0;
4999 0 : *aOffset = runOffset;
5000 0 : *aLength = mIterator.GetSkippedOffset() - runOffset;
5001 0 : *aXOffset = mXOffset;
5002 0 : *aHyphenWidth = 0;
5003 0 : if (mIterator.GetOriginalOffset() == mOriginalEnd && haveHyphenBreak) {
5004 0 : *aHyphenWidth = mProvider.GetHyphenWidth();
5005 : }
5006 0 : *aType = type;
5007 0 : *aStyle = style;
5008 0 : return true;
5009 : }
5010 :
5011 : static void
5012 0 : AddHyphenToMetrics(nsTextFrame* aTextFrame, gfxTextRun* aBaseTextRun,
5013 : gfxTextRun::Metrics* aMetrics,
5014 : gfxFont::BoundingBoxType aBoundingBoxType,
5015 : gfxContext* aContext)
5016 : {
5017 : // Fix up metrics to include hyphen
5018 : nsAutoPtr<gfxTextRun> hyphenTextRun(
5019 0 : GetHyphenTextRun(aBaseTextRun, aContext, aTextFrame));
5020 0 : if (!hyphenTextRun.get())
5021 : return;
5022 :
5023 : gfxTextRun::Metrics hyphenMetrics =
5024 : hyphenTextRun->MeasureText(0, hyphenTextRun->GetLength(),
5025 0 : aBoundingBoxType, aContext, nsnull);
5026 0 : aMetrics->CombineWith(hyphenMetrics, aBaseTextRun->IsRightToLeft());
5027 : }
5028 :
5029 : void
5030 0 : nsTextFrame::PaintOneShadow(PRUint32 aOffset, PRUint32 aLength,
5031 : nsCSSShadowItem* aShadowDetails,
5032 : PropertyProvider* aProvider, const nsRect& aDirtyRect,
5033 : const gfxPoint& aFramePt, const gfxPoint& aTextBaselinePt,
5034 : gfxContext* aCtx, const nscolor& aForegroundColor,
5035 : const nsCharClipDisplayItem::ClipEdges& aClipEdges,
5036 : nscoord aLeftSideOffset)
5037 : {
5038 0 : gfxPoint shadowOffset(aShadowDetails->mXOffset, aShadowDetails->mYOffset);
5039 0 : nscoord blurRadius = NS_MAX(aShadowDetails->mRadius, 0);
5040 :
5041 : gfxTextRun::Metrics shadowMetrics =
5042 : mTextRun->MeasureText(aOffset, aLength, gfxFont::LOOSE_INK_EXTENTS,
5043 0 : nsnull, aProvider);
5044 0 : if (GetStateBits() & TEXT_HYPHEN_BREAK) {
5045 0 : AddHyphenToMetrics(this, mTextRun, &shadowMetrics, gfxFont::LOOSE_INK_EXTENTS, aCtx);
5046 : }
5047 :
5048 : // This rect is the box which is equivalent to where the shadow will be painted.
5049 : // The origin of mBoundingBox is the text baseline left, so we must translate it by
5050 : // that much in order to make the origin the top-left corner of the text bounding box.
5051 : gfxRect shadowGfxRect = shadowMetrics.mBoundingBox +
5052 0 : gfxPoint(aFramePt.x + aLeftSideOffset, aTextBaselinePt.y) + shadowOffset;
5053 : nsRect shadowRect(NSToCoordRound(shadowGfxRect.X()),
5054 : NSToCoordRound(shadowGfxRect.Y()),
5055 : NSToCoordRound(shadowGfxRect.Width()),
5056 0 : NSToCoordRound(shadowGfxRect.Height()));
5057 :
5058 0 : nsContextBoxBlur contextBoxBlur;
5059 : gfxContext* shadowContext = contextBoxBlur.Init(shadowRect, 0, blurRadius,
5060 0 : PresContext()->AppUnitsPerDevPixel(),
5061 0 : aCtx, aDirtyRect, nsnull);
5062 0 : if (!shadowContext)
5063 : return;
5064 :
5065 : nscolor shadowColor;
5066 : const nscolor* decorationOverrideColor;
5067 0 : if (aShadowDetails->mHasColor) {
5068 0 : shadowColor = aShadowDetails->mColor;
5069 0 : decorationOverrideColor = &shadowColor;
5070 : } else {
5071 0 : shadowColor = aForegroundColor;
5072 0 : decorationOverrideColor = nsnull;
5073 : }
5074 :
5075 0 : aCtx->Save();
5076 0 : aCtx->NewPath();
5077 0 : aCtx->SetColor(gfxRGBA(shadowColor));
5078 :
5079 : // Draw the text onto our alpha-only surface to capture the alpha values.
5080 : // Remember that the box blur context has a device offset on it, so we don't need to
5081 : // translate any coordinates to fit on the surface.
5082 : gfxFloat advanceWidth;
5083 : gfxRect dirtyRect(aDirtyRect.x, aDirtyRect.y,
5084 0 : aDirtyRect.width, aDirtyRect.height);
5085 0 : DrawText(shadowContext, dirtyRect, aFramePt + shadowOffset,
5086 0 : aTextBaselinePt + shadowOffset, aOffset, aLength, *aProvider,
5087 : nsTextPaintStyle(this), aClipEdges, advanceWidth,
5088 0 : (GetStateBits() & TEXT_HYPHEN_BREAK) != 0, decorationOverrideColor);
5089 :
5090 0 : contextBoxBlur.DoPaint();
5091 0 : aCtx->Restore();
5092 : }
5093 :
5094 : // Paints selection backgrounds and text in the correct colors. Also computes
5095 : // aAllTypes, the union of all selection types that are applying to this text.
5096 : bool
5097 0 : nsTextFrame::PaintTextWithSelectionColors(gfxContext* aCtx,
5098 : const gfxPoint& aFramePt, const gfxPoint& aTextBaselinePt,
5099 : const gfxRect& aDirtyRect,
5100 : PropertyProvider& aProvider,
5101 : PRUint32 aContentOffset, PRUint32 aContentLength,
5102 : nsTextPaintStyle& aTextPaintStyle, SelectionDetails* aDetails,
5103 : SelectionType* aAllTypes,
5104 : const nsCharClipDisplayItem::ClipEdges& aClipEdges)
5105 : {
5106 : // Figure out which selections control the colors to use for each character.
5107 0 : AutoFallibleTArray<SelectionDetails*,BIG_TEXT_NODE_SIZE> prevailingSelectionsBuffer;
5108 : SelectionDetails** prevailingSelections =
5109 0 : prevailingSelectionsBuffer.AppendElements(aContentLength);
5110 0 : if (!prevailingSelections) {
5111 0 : return false;
5112 : }
5113 :
5114 0 : SelectionType allTypes = 0;
5115 0 : for (PRUint32 i = 0; i < aContentLength; ++i) {
5116 0 : prevailingSelections[i] = nsnull;
5117 : }
5118 :
5119 0 : SelectionDetails *sdptr = aDetails;
5120 0 : bool anyBackgrounds = false;
5121 0 : while (sdptr) {
5122 0 : PRInt32 start = NS_MAX(0, sdptr->mStart - PRInt32(aContentOffset));
5123 : PRInt32 end = NS_MIN(PRInt32(aContentLength),
5124 0 : sdptr->mEnd - PRInt32(aContentOffset));
5125 0 : SelectionType type = sdptr->mType;
5126 0 : if (start < end) {
5127 0 : allTypes |= type;
5128 : // Ignore selections that don't set colors
5129 : nscolor foreground, background;
5130 0 : if (GetSelectionTextColors(type, aTextPaintStyle, sdptr->mTextRangeStyle,
5131 0 : &foreground, &background)) {
5132 0 : if (NS_GET_A(background) > 0) {
5133 0 : anyBackgrounds = true;
5134 : }
5135 0 : for (PRInt32 i = start; i < end; ++i) {
5136 : // Favour normal selection over IME selections
5137 0 : if (!prevailingSelections[i] ||
5138 0 : type < prevailingSelections[i]->mType) {
5139 0 : prevailingSelections[i] = sdptr;
5140 : }
5141 : }
5142 : }
5143 : }
5144 0 : sdptr = sdptr->mNext;
5145 : }
5146 0 : *aAllTypes = allTypes;
5147 :
5148 0 : if (!allTypes) {
5149 : // Nothing is selected in the given text range. XXX can this still occur?
5150 0 : return false;
5151 : }
5152 :
5153 0 : const gfxFloat startXOffset = aTextBaselinePt.x - aFramePt.x;
5154 : gfxFloat xOffset, hyphenWidth;
5155 : PRUint32 offset, length; // in transformed string
5156 : SelectionType type;
5157 0 : nsTextRangeStyle rangeStyle;
5158 : // Draw background colors
5159 0 : if (anyBackgrounds) {
5160 : SelectionIterator iterator(prevailingSelections, aContentOffset, aContentLength,
5161 0 : aProvider, mTextRun, startXOffset);
5162 0 : while (iterator.GetNextSegment(&xOffset, &offset, &length, &hyphenWidth,
5163 : &type, &rangeStyle)) {
5164 : nscolor foreground, background;
5165 : GetSelectionTextColors(type, aTextPaintStyle, rangeStyle,
5166 0 : &foreground, &background);
5167 : // Draw background color
5168 : gfxFloat advance = hyphenWidth +
5169 0 : mTextRun->GetAdvanceWidth(offset, length, &aProvider);
5170 0 : if (NS_GET_A(background) > 0) {
5171 0 : gfxFloat x = xOffset - (mTextRun->IsRightToLeft() ? advance : 0);
5172 : FillClippedRect(aCtx, aTextPaintStyle.PresContext(),
5173 : background, aDirtyRect,
5174 0 : gfxRect(aFramePt.x + x, aFramePt.y, advance, GetSize().height));
5175 : }
5176 0 : iterator.UpdateWithAdvance(advance);
5177 : }
5178 : }
5179 :
5180 : // Draw text
5181 0 : const nsStyleText* textStyle = GetStyleText();
5182 : nsRect dirtyRect(aDirtyRect.x, aDirtyRect.y,
5183 0 : aDirtyRect.width, aDirtyRect.height);
5184 : SelectionIterator iterator(prevailingSelections, aContentOffset, aContentLength,
5185 0 : aProvider, mTextRun, startXOffset);
5186 0 : while (iterator.GetNextSegment(&xOffset, &offset, &length, &hyphenWidth,
5187 : &type, &rangeStyle)) {
5188 : nscolor foreground, background;
5189 : GetSelectionTextColors(type, aTextPaintStyle, rangeStyle,
5190 0 : &foreground, &background);
5191 0 : gfxPoint textBaselinePt(aFramePt.x + xOffset, aTextBaselinePt.y);
5192 :
5193 : // Draw shadows, if any
5194 0 : if (textStyle->mTextShadow) {
5195 0 : for (PRUint32 i = textStyle->mTextShadow->Length(); i > 0; --i) {
5196 : PaintOneShadow(offset, length,
5197 : textStyle->mTextShadow->ShadowAt(i - 1), &aProvider,
5198 : dirtyRect, aFramePt, textBaselinePt, aCtx,
5199 0 : foreground, aClipEdges, xOffset);
5200 : }
5201 : }
5202 :
5203 : // Draw text segment
5204 0 : aCtx->SetColor(gfxRGBA(foreground));
5205 : gfxFloat advance;
5206 :
5207 : DrawText(aCtx, aDirtyRect, aFramePt, textBaselinePt,
5208 : offset, length, aProvider, aTextPaintStyle, aClipEdges, advance,
5209 0 : hyphenWidth > 0);
5210 0 : if (hyphenWidth) {
5211 0 : advance += hyphenWidth;
5212 : }
5213 0 : iterator.UpdateWithAdvance(advance);
5214 : }
5215 0 : return true;
5216 : }
5217 :
5218 : void
5219 0 : nsTextFrame::PaintTextSelectionDecorations(gfxContext* aCtx,
5220 : const gfxPoint& aFramePt,
5221 : const gfxPoint& aTextBaselinePt, const gfxRect& aDirtyRect,
5222 : PropertyProvider& aProvider,
5223 : PRUint32 aContentOffset, PRUint32 aContentLength,
5224 : nsTextPaintStyle& aTextPaintStyle, SelectionDetails* aDetails,
5225 : SelectionType aSelectionType)
5226 : {
5227 : // Hide text decorations if we're currently hiding @font-face fallback text
5228 0 : if (aProvider.GetFontGroup()->ShouldSkipDrawing())
5229 0 : return;
5230 :
5231 : // Figure out which characters will be decorated for this selection.
5232 0 : AutoFallibleTArray<SelectionDetails*, BIG_TEXT_NODE_SIZE> selectedCharsBuffer;
5233 : SelectionDetails** selectedChars =
5234 0 : selectedCharsBuffer.AppendElements(aContentLength);
5235 0 : if (!selectedChars) {
5236 : return;
5237 : }
5238 0 : for (PRUint32 i = 0; i < aContentLength; ++i) {
5239 0 : selectedChars[i] = nsnull;
5240 : }
5241 :
5242 0 : SelectionDetails *sdptr = aDetails;
5243 0 : while (sdptr) {
5244 0 : if (sdptr->mType == aSelectionType) {
5245 0 : PRInt32 start = NS_MAX(0, sdptr->mStart - PRInt32(aContentOffset));
5246 : PRInt32 end = NS_MIN(PRInt32(aContentLength),
5247 0 : sdptr->mEnd - PRInt32(aContentOffset));
5248 0 : for (PRInt32 i = start; i < end; ++i) {
5249 0 : selectedChars[i] = sdptr;
5250 : }
5251 : }
5252 0 : sdptr = sdptr->mNext;
5253 : }
5254 :
5255 0 : gfxFont* firstFont = aProvider.GetFontGroup()->GetFontAt(0);
5256 0 : if (!firstFont)
5257 : return; // OOM
5258 0 : gfxFont::Metrics decorationMetrics(firstFont->GetMetrics());
5259 : decorationMetrics.underlineOffset =
5260 0 : aProvider.GetFontGroup()->GetUnderlineOffset();
5261 :
5262 0 : gfxFloat startXOffset = aTextBaselinePt.x - aFramePt.x;
5263 : SelectionIterator iterator(selectedChars, aContentOffset, aContentLength,
5264 0 : aProvider, mTextRun, startXOffset);
5265 : gfxFloat xOffset, hyphenWidth;
5266 : PRUint32 offset, length;
5267 0 : PRInt32 app = aTextPaintStyle.PresContext()->AppUnitsPerDevPixel();
5268 : // XXX aTextBaselinePt is in AppUnits, shouldn't it be nsFloatPoint?
5269 0 : gfxPoint pt(0.0, (aTextBaselinePt.y - mAscent) / app);
5270 : gfxRect dirtyRect(aDirtyRect.x / app, aDirtyRect.y / app,
5271 0 : aDirtyRect.width / app, aDirtyRect.height / app);
5272 : SelectionType type;
5273 0 : nsTextRangeStyle selectedStyle;
5274 0 : while (iterator.GetNextSegment(&xOffset, &offset, &length, &hyphenWidth,
5275 : &type, &selectedStyle)) {
5276 : gfxFloat advance = hyphenWidth +
5277 0 : mTextRun->GetAdvanceWidth(offset, length, &aProvider);
5278 0 : if (type == aSelectionType) {
5279 : pt.x = (aFramePt.x + xOffset -
5280 0 : (mTextRun->IsRightToLeft() ? advance : 0)) / app;
5281 0 : gfxFloat width = NS_ABS(advance) / app;
5282 : DrawSelectionDecorations(aCtx, dirtyRect, aSelectionType, this, aTextPaintStyle,
5283 : selectedStyle,
5284 0 : pt, width, mAscent / app, decorationMetrics);
5285 : }
5286 0 : iterator.UpdateWithAdvance(advance);
5287 : }
5288 : }
5289 :
5290 : bool
5291 0 : nsTextFrame::PaintTextWithSelection(gfxContext* aCtx,
5292 : const gfxPoint& aFramePt,
5293 : const gfxPoint& aTextBaselinePt, const gfxRect& aDirtyRect,
5294 : PropertyProvider& aProvider,
5295 : PRUint32 aContentOffset, PRUint32 aContentLength,
5296 : nsTextPaintStyle& aTextPaintStyle,
5297 : const nsCharClipDisplayItem::ClipEdges& aClipEdges)
5298 : {
5299 0 : NS_ASSERTION(GetContent()->IsSelectionDescendant(), "wrong paint path");
5300 :
5301 0 : SelectionDetails* details = GetSelectionDetails();
5302 0 : if (!details) {
5303 0 : return false;
5304 : }
5305 :
5306 : SelectionType allTypes;
5307 0 : if (!PaintTextWithSelectionColors(aCtx, aFramePt, aTextBaselinePt, aDirtyRect,
5308 : aProvider, aContentOffset, aContentLength,
5309 : aTextPaintStyle, details, &allTypes,
5310 0 : aClipEdges)) {
5311 0 : DestroySelectionDetails(details);
5312 0 : return false;
5313 : }
5314 : PRInt32 i;
5315 : // Iterate through just the selection types that paint decorations and
5316 : // paint decorations for any that actually occur in this frame. Paint
5317 : // higher-numbered selection types below lower-numered ones on the
5318 : // general principal that lower-numbered selections are higher priority.
5319 0 : allTypes &= SelectionTypesWithDecorations;
5320 0 : for (i = nsISelectionController::NUM_SELECTIONTYPES - 1; i >= 1; --i) {
5321 0 : SelectionType type = 1 << (i - 1);
5322 0 : if (allTypes & type) {
5323 : // There is some selection of this type. Try to paint its decorations
5324 : // (there might not be any for this type but that's OK,
5325 : // PaintTextSelectionDecorations will exit early).
5326 : PaintTextSelectionDecorations(aCtx, aFramePt, aTextBaselinePt, aDirtyRect,
5327 : aProvider, aContentOffset, aContentLength,
5328 0 : aTextPaintStyle, details, type);
5329 : }
5330 : }
5331 :
5332 0 : DestroySelectionDetails(details);
5333 0 : return true;
5334 : }
5335 :
5336 : nscolor
5337 0 : nsTextFrame::GetCaretColorAt(PRInt32 aOffset)
5338 : {
5339 0 : NS_PRECONDITION(aOffset >= 0, "aOffset must be positive");
5340 :
5341 0 : gfxSkipCharsIterator iter = EnsureTextRun(nsTextFrame::eInflated);
5342 0 : PropertyProvider provider(this, iter, nsTextFrame::eInflated);
5343 0 : PRInt32 contentOffset = provider.GetStart().GetOriginalOffset();
5344 0 : PRInt32 contentLength = provider.GetOriginalLength();
5345 0 : NS_PRECONDITION(aOffset >= contentOffset &&
5346 : aOffset <= contentOffset + contentLength,
5347 : "aOffset must be in the frame's range");
5348 0 : PRInt32 offsetInFrame = aOffset - contentOffset;
5349 0 : if (offsetInFrame < 0 || offsetInFrame >= contentLength) {
5350 0 : return nsFrame::GetCaretColorAt(aOffset);
5351 : }
5352 :
5353 0 : nsTextPaintStyle textPaintStyle(this);
5354 0 : SelectionDetails* details = GetSelectionDetails();
5355 0 : SelectionDetails* sdptr = details;
5356 0 : nscolor result = nsFrame::GetCaretColorAt(aOffset);
5357 0 : SelectionType type = 0;
5358 0 : while (sdptr) {
5359 0 : PRInt32 start = NS_MAX(0, sdptr->mStart - contentOffset);
5360 0 : PRInt32 end = NS_MIN(contentLength, sdptr->mEnd - contentOffset);
5361 0 : if (start <= offsetInFrame && offsetInFrame < end &&
5362 : (type == 0 || sdptr->mType < type)) {
5363 : nscolor foreground, background;
5364 0 : if (GetSelectionTextColors(sdptr->mType, textPaintStyle,
5365 : sdptr->mTextRangeStyle,
5366 0 : &foreground, &background)) {
5367 0 : result = foreground;
5368 0 : type = sdptr->mType;
5369 : }
5370 : }
5371 0 : sdptr = sdptr->mNext;
5372 : }
5373 :
5374 0 : DestroySelectionDetails(details);
5375 0 : return result;
5376 : }
5377 :
5378 : static PRUint32
5379 0 : ComputeTransformedLength(PropertyProvider& aProvider)
5380 : {
5381 0 : gfxSkipCharsIterator iter(aProvider.GetStart());
5382 0 : PRUint32 start = iter.GetSkippedOffset();
5383 0 : iter.AdvanceOriginal(aProvider.GetOriginalLength());
5384 0 : return iter.GetSkippedOffset() - start;
5385 : }
5386 :
5387 : bool
5388 0 : nsTextFrame::MeasureCharClippedText(nscoord aLeftEdge, nscoord aRightEdge,
5389 : nscoord* aSnappedLeftEdge,
5390 : nscoord* aSnappedRightEdge)
5391 : {
5392 : // We need a *reference* rendering context (not one that might have a
5393 : // transform), so we don't have a rendering context argument.
5394 : // XXX get the block and line passed to us somehow! This is slow!
5395 0 : gfxSkipCharsIterator iter = EnsureTextRun(nsTextFrame::eInflated);
5396 0 : if (!mTextRun)
5397 0 : return false;
5398 :
5399 0 : PropertyProvider provider(this, iter, nsTextFrame::eInflated);
5400 : // Trim trailing whitespace
5401 0 : provider.InitializeForDisplay(true);
5402 :
5403 0 : PRUint32 startOffset = provider.GetStart().GetSkippedOffset();
5404 0 : PRUint32 maxLength = ComputeTransformedLength(provider);
5405 : return MeasureCharClippedText(provider, aLeftEdge, aRightEdge,
5406 : &startOffset, &maxLength,
5407 0 : aSnappedLeftEdge, aSnappedRightEdge);
5408 : }
5409 :
5410 0 : static PRUint32 GetClusterLength(gfxTextRun* aTextRun,
5411 : PRUint32 aStartOffset,
5412 : PRUint32 aMaxLength,
5413 : bool aIsRTL)
5414 : {
5415 0 : PRUint32 clusterLength = aIsRTL ? 0 : 1;
5416 0 : while (clusterLength < aMaxLength) {
5417 0 : if (aTextRun->IsClusterStart(aStartOffset + clusterLength)) {
5418 0 : if (aIsRTL) {
5419 0 : ++clusterLength;
5420 : }
5421 0 : break;
5422 : }
5423 0 : ++clusterLength;
5424 : }
5425 0 : return clusterLength;
5426 : }
5427 :
5428 : bool
5429 0 : nsTextFrame::MeasureCharClippedText(PropertyProvider& aProvider,
5430 : nscoord aLeftEdge, nscoord aRightEdge,
5431 : PRUint32* aStartOffset,
5432 : PRUint32* aMaxLength,
5433 : nscoord* aSnappedLeftEdge,
5434 : nscoord* aSnappedRightEdge)
5435 : {
5436 0 : *aSnappedLeftEdge = 0;
5437 0 : *aSnappedRightEdge = 0;
5438 0 : if (aLeftEdge <= 0 && aRightEdge <= 0) {
5439 0 : return true;
5440 : }
5441 :
5442 0 : PRUint32 offset = *aStartOffset;
5443 0 : PRUint32 maxLength = *aMaxLength;
5444 0 : const nscoord frameWidth = GetSize().width;
5445 0 : const bool rtl = mTextRun->IsRightToLeft();
5446 0 : gfxFloat advanceWidth = 0;
5447 0 : const nscoord startEdge = rtl ? aRightEdge : aLeftEdge;
5448 0 : if (startEdge > 0) {
5449 0 : const gfxFloat maxAdvance = gfxFloat(startEdge);
5450 0 : while (maxLength > 0) {
5451 : PRUint32 clusterLength =
5452 0 : GetClusterLength(mTextRun, offset, maxLength, rtl);
5453 : advanceWidth +=
5454 0 : mTextRun->GetAdvanceWidth(offset, clusterLength, &aProvider);
5455 0 : maxLength -= clusterLength;
5456 0 : offset += clusterLength;
5457 0 : if (advanceWidth >= maxAdvance) {
5458 0 : break;
5459 : }
5460 : }
5461 0 : nscoord* snappedStartEdge = rtl ? aSnappedRightEdge : aSnappedLeftEdge;
5462 0 : *snappedStartEdge = NSToCoordFloor(advanceWidth);
5463 0 : *aStartOffset = offset;
5464 : }
5465 :
5466 0 : const nscoord endEdge = rtl ? aLeftEdge : aRightEdge;
5467 0 : if (endEdge > 0) {
5468 0 : const gfxFloat maxAdvance = gfxFloat(frameWidth - endEdge);
5469 0 : while (maxLength > 0) {
5470 : PRUint32 clusterLength =
5471 0 : GetClusterLength(mTextRun, offset, maxLength, rtl);
5472 : gfxFloat nextAdvance = advanceWidth +
5473 0 : mTextRun->GetAdvanceWidth(offset, clusterLength, &aProvider);
5474 0 : if (nextAdvance > maxAdvance) {
5475 0 : break;
5476 : }
5477 : // This cluster fits, include it.
5478 0 : advanceWidth = nextAdvance;
5479 0 : maxLength -= clusterLength;
5480 0 : offset += clusterLength;
5481 : }
5482 0 : maxLength = offset - *aStartOffset;
5483 0 : nscoord* snappedEndEdge = rtl ? aSnappedLeftEdge : aSnappedRightEdge;
5484 0 : *snappedEndEdge = NSToCoordFloor(gfxFloat(frameWidth) - advanceWidth);
5485 : }
5486 0 : *aMaxLength = maxLength;
5487 0 : return maxLength != 0;
5488 : }
5489 :
5490 : void
5491 0 : nsTextFrame::PaintText(nsRenderingContext* aRenderingContext, nsPoint aPt,
5492 : const nsRect& aDirtyRect,
5493 : const nsCharClipDisplayItem& aItem)
5494 : {
5495 : // Don't pass in aRenderingContext here, because we need a *reference*
5496 : // context and aRenderingContext might have some transform in it
5497 : // XXX get the block and line passed to us somehow! This is slow!
5498 0 : gfxSkipCharsIterator iter = EnsureTextRun(nsTextFrame::eInflated);
5499 0 : if (!mTextRun)
5500 0 : return;
5501 :
5502 0 : PropertyProvider provider(this, iter, nsTextFrame::eInflated);
5503 : // Trim trailing whitespace
5504 0 : provider.InitializeForDisplay(true);
5505 :
5506 0 : gfxContext* ctx = aRenderingContext->ThebesContext();
5507 0 : const bool rtl = mTextRun->IsRightToLeft();
5508 0 : const nscoord frameWidth = GetSize().width;
5509 0 : gfxPoint framePt(aPt.x, aPt.y);
5510 : gfxPoint textBaselinePt(rtl ? gfxFloat(aPt.x + frameWidth) : framePt.x,
5511 0 : nsLayoutUtils::GetSnappedBaselineY(this, ctx, aPt.y, mAscent));
5512 0 : PRUint32 startOffset = provider.GetStart().GetSkippedOffset();
5513 0 : PRUint32 maxLength = ComputeTransformedLength(provider);
5514 : nscoord snappedLeftEdge, snappedRightEdge;
5515 0 : if (!MeasureCharClippedText(provider, aItem.mLeftEdge, aItem.mRightEdge,
5516 0 : &startOffset, &maxLength, &snappedLeftEdge, &snappedRightEdge)) {
5517 : return;
5518 : }
5519 0 : textBaselinePt.x += rtl ? -snappedRightEdge : snappedLeftEdge;
5520 : nsCharClipDisplayItem::ClipEdges clipEdges(aItem, snappedLeftEdge,
5521 0 : snappedRightEdge);
5522 0 : nsTextPaintStyle textPaintStyle(this);
5523 :
5524 : gfxRect dirtyRect(aDirtyRect.x, aDirtyRect.y,
5525 0 : aDirtyRect.width, aDirtyRect.height);
5526 : // Fork off to the (slower) paint-with-selection path if necessary.
5527 0 : if (IsSelected()) {
5528 0 : gfxSkipCharsIterator tmp(provider.GetStart());
5529 0 : PRInt32 contentOffset = tmp.ConvertSkippedToOriginal(startOffset);
5530 : PRInt32 contentLength =
5531 0 : tmp.ConvertSkippedToOriginal(startOffset + maxLength) - contentOffset;
5532 0 : if (PaintTextWithSelection(ctx, framePt, textBaselinePt, dirtyRect,
5533 : provider, contentOffset, contentLength,
5534 0 : textPaintStyle, clipEdges)) {
5535 : return;
5536 : }
5537 : }
5538 :
5539 0 : nscolor foregroundColor = textPaintStyle.GetTextColor();
5540 0 : const nsStyleText* textStyle = GetStyleText();
5541 0 : if (textStyle->mTextShadow) {
5542 : // Text shadow happens with the last value being painted at the back,
5543 : // ie. it is painted first.
5544 0 : for (PRUint32 i = textStyle->mTextShadow->Length(); i > 0; --i) {
5545 : PaintOneShadow(startOffset, maxLength,
5546 : textStyle->mTextShadow->ShadowAt(i - 1), &provider,
5547 : aDirtyRect, framePt, textBaselinePt, ctx,
5548 0 : foregroundColor, clipEdges, snappedLeftEdge);
5549 : }
5550 : }
5551 :
5552 0 : ctx->SetColor(gfxRGBA(foregroundColor));
5553 :
5554 : gfxFloat advanceWidth;
5555 : DrawText(ctx, dirtyRect, framePt, textBaselinePt, startOffset, maxLength, provider,
5556 : textPaintStyle, clipEdges, advanceWidth,
5557 0 : (GetStateBits() & TEXT_HYPHEN_BREAK) != 0);
5558 : }
5559 :
5560 : void
5561 0 : nsTextFrame::DrawTextRun(gfxContext* const aCtx,
5562 : const gfxPoint& aTextBaselinePt,
5563 : PRUint32 aOffset, PRUint32 aLength,
5564 : PropertyProvider& aProvider,
5565 : gfxFloat& aAdvanceWidth,
5566 : bool aDrawSoftHyphen)
5567 : {
5568 : mTextRun->Draw(aCtx, aTextBaselinePt, gfxFont::GLYPH_FILL, aOffset, aLength,
5569 0 : &aProvider, &aAdvanceWidth, nsnull);
5570 :
5571 0 : if (aDrawSoftHyphen) {
5572 : // Don't use ctx as the context, because we need a reference context here,
5573 : // ctx may be transformed.
5574 0 : nsAutoPtr<gfxTextRun> hyphenTextRun(GetHyphenTextRun(mTextRun, nsnull, this));
5575 0 : if (hyphenTextRun.get()) {
5576 : // For right-to-left text runs, the soft-hyphen is positioned at the left
5577 : // of the text, minus its own width
5578 0 : gfxFloat hyphenBaselineX = aTextBaselinePt.x + mTextRun->GetDirection() * aAdvanceWidth -
5579 0 : (mTextRun->IsRightToLeft() ? hyphenTextRun->GetAdvanceWidth(0, hyphenTextRun->GetLength(), nsnull) : 0);
5580 : hyphenTextRun->Draw(aCtx, gfxPoint(hyphenBaselineX, aTextBaselinePt.y),
5581 : gfxFont::GLYPH_FILL, 0, hyphenTextRun->GetLength(),
5582 0 : nsnull, nsnull, nsnull);
5583 : }
5584 : }
5585 0 : }
5586 :
5587 : void
5588 0 : nsTextFrame::DrawTextRunAndDecorations(
5589 : gfxContext* const aCtx, const gfxRect& aDirtyRect,
5590 : const gfxPoint& aFramePt, const gfxPoint& aTextBaselinePt,
5591 : PRUint32 aOffset, PRUint32 aLength,
5592 : PropertyProvider& aProvider,
5593 : const nsTextPaintStyle& aTextStyle,
5594 : const nsCharClipDisplayItem::ClipEdges& aClipEdges,
5595 : gfxFloat& aAdvanceWidth,
5596 : bool aDrawSoftHyphen,
5597 : const TextDecorations& aDecorations,
5598 : const nscolor* const aDecorationOverrideColor)
5599 : {
5600 0 : const gfxFloat app = aTextStyle.PresContext()->AppUnitsPerDevPixel();
5601 :
5602 : // XXX aFramePt is in AppUnits, shouldn't it be nsFloatPoint?
5603 0 : nscoord x = NSToCoordRound(aFramePt.x);
5604 0 : nscoord width = GetRect().width;
5605 0 : aClipEdges.Intersect(&x, &width);
5606 :
5607 0 : gfxPoint decPt(x / app, 0);
5608 0 : gfxSize decSize(width / app, 0);
5609 0 : const gfxFloat ascent = gfxFloat(mAscent) / app;
5610 0 : const gfxFloat frameTop = aFramePt.y;
5611 :
5612 : gfxRect dirtyRect(aDirtyRect.x / app, aDirtyRect.y / app,
5613 0 : aDirtyRect.Width() / app, aDirtyRect.Height() / app);
5614 :
5615 : nscoord inflationMinFontSize =
5616 0 : nsLayoutUtils::InflationMinFontSizeFor(this, nsLayoutUtils::eNotInReflow);
5617 :
5618 : // Underlines
5619 0 : for (PRUint32 i = aDecorations.mUnderlines.Length(); i-- > 0; ) {
5620 0 : const LineDecoration& dec = aDecorations.mUnderlines[i];
5621 :
5622 : float inflation = nsLayoutUtils::FontSizeInflationInner(dec.mFrame,
5623 0 : inflationMinFontSize);
5624 : const gfxFont::Metrics metrics =
5625 0 : GetFirstFontMetrics(GetFontGroupForFrame(dec.mFrame, inflation));
5626 :
5627 0 : decSize.height = metrics.underlineSize;
5628 0 : decPt.y = (frameTop - dec.mBaselineOffset) / app;
5629 :
5630 0 : const nscolor lineColor = aDecorationOverrideColor ? *aDecorationOverrideColor : dec.mColor;
5631 : nsCSSRendering::PaintDecorationLine(aCtx, dirtyRect, lineColor, decPt, decSize, ascent,
5632 : metrics.underlineOffset, NS_STYLE_TEXT_DECORATION_LINE_UNDERLINE,
5633 0 : dec.mStyle);
5634 : }
5635 : // Overlines
5636 0 : for (PRUint32 i = aDecorations.mOverlines.Length(); i-- > 0; ) {
5637 0 : const LineDecoration& dec = aDecorations.mOverlines[i];
5638 :
5639 : float inflation = nsLayoutUtils::FontSizeInflationInner(dec.mFrame,
5640 0 : inflationMinFontSize);
5641 : const gfxFont::Metrics metrics =
5642 0 : GetFirstFontMetrics(GetFontGroupForFrame(dec.mFrame, inflation));
5643 :
5644 0 : decSize.height = metrics.underlineSize;
5645 0 : decPt.y = (frameTop - dec.mBaselineOffset) / app;
5646 :
5647 0 : const nscolor lineColor = aDecorationOverrideColor ? *aDecorationOverrideColor : dec.mColor;
5648 : nsCSSRendering::PaintDecorationLine(aCtx, dirtyRect, lineColor, decPt, decSize, ascent,
5649 0 : metrics.maxAscent, NS_STYLE_TEXT_DECORATION_LINE_OVERLINE, dec.mStyle);
5650 : }
5651 :
5652 : // CSS 2.1 mandates that text be painted after over/underlines, and *then*
5653 : // line-throughs
5654 : DrawTextRun(aCtx, aTextBaselinePt, aOffset, aLength, aProvider, aAdvanceWidth,
5655 0 : aDrawSoftHyphen);
5656 :
5657 : // Line-throughs
5658 0 : for (PRUint32 i = aDecorations.mStrikes.Length(); i-- > 0; ) {
5659 0 : const LineDecoration& dec = aDecorations.mStrikes[i];
5660 :
5661 : float inflation = nsLayoutUtils::FontSizeInflationInner(dec.mFrame,
5662 0 : inflationMinFontSize);
5663 : const gfxFont::Metrics metrics =
5664 0 : GetFirstFontMetrics(GetFontGroupForFrame(dec.mFrame, inflation));
5665 :
5666 0 : decSize.height = metrics.strikeoutSize;
5667 0 : decPt.y = (frameTop - dec.mBaselineOffset) / app;
5668 :
5669 0 : const nscolor lineColor = aDecorationOverrideColor ? *aDecorationOverrideColor : dec.mColor;
5670 : nsCSSRendering::PaintDecorationLine(aCtx, dirtyRect, lineColor, decPt, decSize, ascent,
5671 : metrics.strikeoutOffset, NS_STYLE_TEXT_DECORATION_LINE_LINE_THROUGH,
5672 0 : dec.mStyle);
5673 : }
5674 0 : }
5675 :
5676 : void
5677 0 : nsTextFrame::DrawText(
5678 : gfxContext* const aCtx, const gfxRect& aDirtyRect,
5679 : const gfxPoint& aFramePt, const gfxPoint& aTextBaselinePt,
5680 : PRUint32 aOffset, PRUint32 aLength,
5681 : PropertyProvider& aProvider,
5682 : const nsTextPaintStyle& aTextStyle,
5683 : const nsCharClipDisplayItem::ClipEdges& aClipEdges,
5684 : gfxFloat& aAdvanceWidth,
5685 : bool aDrawSoftHyphen,
5686 : const nscolor* const aDecorationOverrideColor)
5687 : {
5688 0 : TextDecorations decorations;
5689 0 : GetTextDecorations(aTextStyle.PresContext(), decorations);
5690 :
5691 : // Hide text decorations if we're currently hiding @font-face fallback text
5692 0 : const bool drawDecorations = !aProvider.GetFontGroup()->ShouldSkipDrawing() &&
5693 0 : decorations.HasDecorationLines();
5694 0 : if (drawDecorations) {
5695 : DrawTextRunAndDecorations(aCtx, aDirtyRect, aFramePt, aTextBaselinePt, aOffset, aLength,
5696 : aProvider, aTextStyle, aClipEdges, aAdvanceWidth,
5697 : aDrawSoftHyphen, decorations,
5698 0 : aDecorationOverrideColor);
5699 : } else {
5700 : DrawTextRun(aCtx, aTextBaselinePt, aOffset, aLength, aProvider,
5701 0 : aAdvanceWidth, aDrawSoftHyphen);
5702 : }
5703 0 : }
5704 :
5705 : PRInt16
5706 0 : nsTextFrame::GetSelectionStatus(PRInt16* aSelectionFlags)
5707 : {
5708 : // get the selection controller
5709 0 : nsCOMPtr<nsISelectionController> selectionController;
5710 : nsresult rv = GetSelectionController(PresContext(),
5711 0 : getter_AddRefs(selectionController));
5712 0 : if (NS_FAILED(rv) || !selectionController)
5713 0 : return nsISelectionController::SELECTION_OFF;
5714 :
5715 0 : selectionController->GetSelectionFlags(aSelectionFlags);
5716 :
5717 : PRInt16 selectionValue;
5718 0 : selectionController->GetDisplaySelection(&selectionValue);
5719 :
5720 0 : return selectionValue;
5721 : }
5722 :
5723 : bool
5724 0 : nsTextFrame::IsVisibleInSelection(nsISelection* aSelection)
5725 : {
5726 : // Check the quick way first
5727 0 : if (!GetContent()->IsSelectionDescendant())
5728 0 : return false;
5729 :
5730 0 : SelectionDetails* details = GetSelectionDetails();
5731 0 : bool found = false;
5732 :
5733 : // where are the selection points "really"
5734 0 : SelectionDetails *sdptr = details;
5735 0 : while (sdptr) {
5736 0 : if (sdptr->mEnd > GetContentOffset() &&
5737 0 : sdptr->mStart < GetContentEnd() &&
5738 : sdptr->mType == nsISelectionController::SELECTION_NORMAL) {
5739 0 : found = true;
5740 0 : break;
5741 : }
5742 0 : sdptr = sdptr->mNext;
5743 : }
5744 0 : DestroySelectionDetails(details);
5745 :
5746 0 : return found;
5747 : }
5748 :
5749 : /**
5750 : * Compute the longest prefix of text whose width is <= aWidth. Return
5751 : * the length of the prefix. Also returns the width of the prefix in aFitWidth.
5752 : */
5753 : static PRUint32
5754 0 : CountCharsFit(gfxTextRun* aTextRun, PRUint32 aStart, PRUint32 aLength,
5755 : gfxFloat aWidth, PropertyProvider* aProvider,
5756 : gfxFloat* aFitWidth)
5757 : {
5758 0 : PRUint32 last = 0;
5759 0 : gfxFloat width = 0;
5760 : PRUint32 i;
5761 0 : for (i = 1; i <= aLength; ++i) {
5762 0 : if (i == aLength || aTextRun->IsClusterStart(aStart + i)) {
5763 : gfxFloat nextWidth = width +
5764 0 : aTextRun->GetAdvanceWidth(aStart + last, i - last, aProvider);
5765 0 : if (nextWidth > aWidth)
5766 0 : break;
5767 0 : last = i;
5768 0 : width = nextWidth;
5769 : }
5770 : }
5771 0 : *aFitWidth = width;
5772 0 : return last;
5773 : }
5774 :
5775 : nsIFrame::ContentOffsets
5776 0 : nsTextFrame::CalcContentOffsetsFromFramePoint(nsPoint aPoint)
5777 : {
5778 0 : return GetCharacterOffsetAtFramePointInternal(aPoint, true);
5779 : }
5780 :
5781 : nsIFrame::ContentOffsets
5782 0 : nsTextFrame::GetCharacterOffsetAtFramePoint(const nsPoint &aPoint)
5783 : {
5784 0 : return GetCharacterOffsetAtFramePointInternal(aPoint, false);
5785 : }
5786 :
5787 : nsIFrame::ContentOffsets
5788 0 : nsTextFrame::GetCharacterOffsetAtFramePointInternal(const nsPoint &aPoint,
5789 : bool aForInsertionPoint)
5790 : {
5791 0 : ContentOffsets offsets;
5792 :
5793 0 : gfxSkipCharsIterator iter = EnsureTextRun(nsTextFrame::eInflated);
5794 0 : if (!mTextRun)
5795 0 : return offsets;
5796 :
5797 0 : PropertyProvider provider(this, iter, nsTextFrame::eInflated);
5798 : // Trim leading but not trailing whitespace if possible
5799 0 : provider.InitializeForDisplay(false);
5800 0 : gfxFloat width = mTextRun->IsRightToLeft() ? mRect.width - aPoint.x : aPoint.x;
5801 : gfxFloat fitWidth;
5802 0 : PRUint32 skippedLength = ComputeTransformedLength(provider);
5803 :
5804 : PRUint32 charsFit = CountCharsFit(mTextRun,
5805 0 : provider.GetStart().GetSkippedOffset(), skippedLength, width, &provider, &fitWidth);
5806 :
5807 : PRInt32 selectedOffset;
5808 0 : if (charsFit < skippedLength) {
5809 : // charsFit characters fitted, but no more could fit. See if we're
5810 : // more than halfway through the cluster.. If we are, choose the next
5811 : // cluster.
5812 0 : gfxSkipCharsIterator extraCluster(provider.GetStart());
5813 0 : extraCluster.AdvanceSkipped(charsFit);
5814 0 : gfxSkipCharsIterator extraClusterLastChar(extraCluster);
5815 : FindClusterEnd(mTextRun,
5816 0 : provider.GetStart().GetOriginalOffset() + provider.GetOriginalLength(),
5817 0 : &extraClusterLastChar);
5818 : gfxFloat charWidth =
5819 : mTextRun->GetAdvanceWidth(extraCluster.GetSkippedOffset(),
5820 0 : GetSkippedDistance(extraCluster, extraClusterLastChar) + 1,
5821 0 : &provider);
5822 0 : selectedOffset = !aForInsertionPoint || width <= fitWidth + charWidth/2
5823 : ? extraCluster.GetOriginalOffset()
5824 0 : : extraClusterLastChar.GetOriginalOffset() + 1;
5825 : } else {
5826 : // All characters fitted, we're at (or beyond) the end of the text.
5827 : // XXX This could be some pathological situation where negative spacing
5828 : // caused characters to move backwards. We can't really handle that
5829 : // in the current frame system because frames can't have negative
5830 : // intrinsic widths.
5831 : selectedOffset =
5832 0 : provider.GetStart().GetOriginalOffset() + provider.GetOriginalLength();
5833 : // If we're at the end of a preformatted line which has a terminating
5834 : // linefeed, we want to reduce the offset by one to make sure that the
5835 : // selection is placed before the linefeed character.
5836 0 : if (GetStyleText()->NewlineIsSignificant() &&
5837 0 : HasTerminalNewline()) {
5838 0 : --selectedOffset;
5839 : }
5840 : }
5841 :
5842 0 : offsets.content = GetContent();
5843 0 : offsets.offset = offsets.secondaryOffset = selectedOffset;
5844 0 : offsets.associateWithNext = mContentOffset == offsets.offset;
5845 : return offsets;
5846 : }
5847 :
5848 : bool
5849 0 : nsTextFrame::CombineSelectionUnderlineRect(nsPresContext* aPresContext,
5850 : nsRect& aRect)
5851 : {
5852 0 : if (aRect.IsEmpty())
5853 0 : return false;
5854 :
5855 0 : nsRect givenRect = aRect;
5856 :
5857 0 : nsRefPtr<nsFontMetrics> fm;
5858 : nsLayoutUtils::GetFontMetricsForFrame(this, getter_AddRefs(fm),
5859 0 : GetFontSizeInflation());
5860 0 : gfxFontGroup* fontGroup = fm->GetThebesFontGroup();
5861 0 : gfxFont* firstFont = fontGroup->GetFontAt(0);
5862 0 : if (!firstFont)
5863 0 : return false; // OOM
5864 0 : const gfxFont::Metrics& metrics = firstFont->GetMetrics();
5865 0 : gfxFloat underlineOffset = fontGroup->GetUnderlineOffset();
5866 0 : gfxFloat ascent = aPresContext->AppUnitsToGfxUnits(mAscent);
5867 : gfxFloat descentLimit =
5868 0 : ComputeDescentLimitForSelectionUnderline(aPresContext, this, metrics);
5869 :
5870 0 : SelectionDetails *details = GetSelectionDetails();
5871 0 : for (SelectionDetails *sd = details; sd; sd = sd->mNext) {
5872 0 : if (sd->mStart == sd->mEnd || !(sd->mType & SelectionTypesWithDecorations))
5873 0 : continue;
5874 :
5875 : PRUint8 style;
5876 : float relativeSize;
5877 : PRInt32 index =
5878 0 : nsTextPaintStyle::GetUnderlineStyleIndexForSelectionType(sd->mType);
5879 0 : if (sd->mType == nsISelectionController::SELECTION_SPELLCHECK) {
5880 0 : if (!nsTextPaintStyle::GetSelectionUnderline(aPresContext, index, nsnull,
5881 0 : &relativeSize, &style)) {
5882 0 : continue;
5883 : }
5884 : } else {
5885 : // IME selections
5886 0 : nsTextRangeStyle& rangeStyle = sd->mTextRangeStyle;
5887 0 : if (rangeStyle.IsDefined()) {
5888 0 : if (!rangeStyle.IsLineStyleDefined() ||
5889 : rangeStyle.mLineStyle == nsTextRangeStyle::LINESTYLE_NONE) {
5890 0 : continue;
5891 : }
5892 0 : style = rangeStyle.mLineStyle;
5893 0 : relativeSize = rangeStyle.mIsBoldLine ? 2.0f : 1.0f;
5894 0 : } else if (!nsTextPaintStyle::GetSelectionUnderline(aPresContext, index,
5895 : nsnull, &relativeSize,
5896 0 : &style)) {
5897 0 : continue;
5898 : }
5899 : }
5900 0 : nsRect decorationArea;
5901 : gfxSize size(aPresContext->AppUnitsToGfxUnits(aRect.width),
5902 : ComputeSelectionUnderlineHeight(aPresContext,
5903 0 : metrics, sd->mType));
5904 0 : relativeSize = NS_MAX(relativeSize, 1.0f);
5905 0 : size.height *= relativeSize;
5906 : decorationArea =
5907 : nsCSSRendering::GetTextDecorationRect(aPresContext, size,
5908 : ascent, underlineOffset, NS_STYLE_TEXT_DECORATION_LINE_UNDERLINE,
5909 0 : style, descentLimit);
5910 0 : aRect.UnionRect(aRect, decorationArea);
5911 : }
5912 0 : DestroySelectionDetails(details);
5913 :
5914 0 : return !aRect.IsEmpty() && !givenRect.Contains(aRect);
5915 : }
5916 :
5917 : bool
5918 0 : nsTextFrame::IsFrameSelected() const
5919 : {
5920 0 : NS_ASSERTION(!GetContent() || GetContent()->IsSelectionDescendant(),
5921 : "use the public IsSelected() instead");
5922 0 : return nsRange::IsNodeSelected(GetContent(), GetContentOffset(),
5923 0 : GetContentEnd());
5924 : }
5925 :
5926 : void
5927 0 : nsTextFrame::SetSelectedRange(PRUint32 aStart, PRUint32 aEnd, bool aSelected,
5928 : SelectionType aType)
5929 : {
5930 0 : NS_ASSERTION(!GetPrevContinuation(), "Should only be called for primary frame");
5931 : DEBUG_VERIFY_NOT_DIRTY(mState);
5932 :
5933 : // Selection is collapsed, which can't affect text frame rendering
5934 0 : if (aStart == aEnd)
5935 0 : return;
5936 :
5937 0 : nsTextFrame* f = this;
5938 0 : while (f && f->GetContentEnd() <= PRInt32(aStart)) {
5939 0 : f = static_cast<nsTextFrame*>(f->GetNextContinuation());
5940 : }
5941 :
5942 0 : nsPresContext* presContext = PresContext();
5943 0 : while (f && f->GetContentOffset() < PRInt32(aEnd)) {
5944 : // We may need to reflow to recompute the overflow area for
5945 : // spellchecking or IME underline if their underline is thicker than
5946 : // the normal decoration line.
5947 0 : if (aType & SelectionTypesWithDecorations) {
5948 : bool didHaveOverflowingSelection =
5949 0 : (f->GetStateBits() & TEXT_SELECTION_UNDERLINE_OVERFLOWED) != 0;
5950 0 : nsRect r(nsPoint(0, 0), GetSize());
5951 : bool willHaveOverflowingSelection =
5952 0 : aSelected && f->CombineSelectionUnderlineRect(presContext, r);
5953 0 : if (didHaveOverflowingSelection || willHaveOverflowingSelection) {
5954 0 : presContext->PresShell()->FrameNeedsReflow(f,
5955 : nsIPresShell::eStyleChange,
5956 0 : NS_FRAME_IS_DIRTY);
5957 : }
5958 : }
5959 : // Selection might change anything. Invalidate the overflow area.
5960 0 : f->InvalidateOverflowRect();
5961 :
5962 0 : f = static_cast<nsTextFrame*>(f->GetNextContinuation());
5963 : }
5964 : }
5965 :
5966 : NS_IMETHODIMP
5967 0 : nsTextFrame::GetPointFromOffset(PRInt32 inOffset,
5968 : nsPoint* outPoint)
5969 : {
5970 0 : if (!outPoint)
5971 0 : return NS_ERROR_NULL_POINTER;
5972 :
5973 0 : outPoint->x = 0;
5974 0 : outPoint->y = 0;
5975 :
5976 : DEBUG_VERIFY_NOT_DIRTY(mState);
5977 0 : if (mState & NS_FRAME_IS_DIRTY)
5978 0 : return NS_ERROR_UNEXPECTED;
5979 :
5980 0 : if (GetContentLength() <= 0) {
5981 0 : return NS_OK;
5982 : }
5983 :
5984 0 : gfxSkipCharsIterator iter = EnsureTextRun(nsTextFrame::eInflated);
5985 0 : if (!mTextRun)
5986 0 : return NS_ERROR_FAILURE;
5987 :
5988 0 : PropertyProvider properties(this, iter, nsTextFrame::eInflated);
5989 : // Don't trim trailing whitespace, we want the caret to appear in the right
5990 : // place if it's positioned there
5991 0 : properties.InitializeForDisplay(false);
5992 :
5993 0 : if (inOffset < GetContentOffset()){
5994 0 : NS_WARNING("offset before this frame's content");
5995 0 : inOffset = GetContentOffset();
5996 0 : } else if (inOffset > GetContentEnd()) {
5997 0 : NS_WARNING("offset after this frame's content");
5998 0 : inOffset = GetContentEnd();
5999 : }
6000 0 : PRInt32 trimmedOffset = properties.GetStart().GetOriginalOffset();
6001 0 : PRInt32 trimmedEnd = trimmedOffset + properties.GetOriginalLength();
6002 0 : inOffset = NS_MAX(inOffset, trimmedOffset);
6003 0 : inOffset = NS_MIN(inOffset, trimmedEnd);
6004 :
6005 0 : iter.SetOriginalOffset(inOffset);
6006 :
6007 0 : if (inOffset < trimmedEnd &&
6008 0 : !iter.IsOriginalCharSkipped() &&
6009 0 : !mTextRun->IsClusterStart(iter.GetSkippedOffset())) {
6010 0 : NS_WARNING("GetPointFromOffset called for non-cluster boundary");
6011 0 : FindClusterStart(mTextRun, trimmedOffset, &iter);
6012 : }
6013 :
6014 : gfxFloat advanceWidth =
6015 0 : mTextRun->GetAdvanceWidth(properties.GetStart().GetSkippedOffset(),
6016 0 : GetSkippedDistance(properties.GetStart(), iter),
6017 0 : &properties);
6018 0 : nscoord width = NSToCoordCeilClamped(advanceWidth);
6019 :
6020 0 : if (mTextRun->IsRightToLeft()) {
6021 0 : outPoint->x = mRect.width - width;
6022 : } else {
6023 0 : outPoint->x = width;
6024 : }
6025 0 : outPoint->y = 0;
6026 :
6027 0 : return NS_OK;
6028 : }
6029 :
6030 : NS_IMETHODIMP
6031 0 : nsTextFrame::GetChildFrameContainingOffset(PRInt32 aContentOffset,
6032 : bool aHint,
6033 : PRInt32* aOutOffset,
6034 : nsIFrame**aOutFrame)
6035 : {
6036 : DEBUG_VERIFY_NOT_DIRTY(mState);
6037 : #if 0 //XXXrbs disable due to bug 310227
6038 : if (mState & NS_FRAME_IS_DIRTY)
6039 : return NS_ERROR_UNEXPECTED;
6040 : #endif
6041 :
6042 0 : NS_ASSERTION(aOutOffset && aOutFrame, "Bad out parameters");
6043 0 : NS_ASSERTION(aContentOffset >= 0, "Negative content offset, existing code was very broken!");
6044 0 : nsIFrame* primaryFrame = mContent->GetPrimaryFrame();
6045 0 : if (this != primaryFrame) {
6046 : // This call needs to happen on the primary frame
6047 : return primaryFrame->GetChildFrameContainingOffset(aContentOffset, aHint,
6048 0 : aOutOffset, aOutFrame);
6049 : }
6050 :
6051 0 : nsTextFrame* f = this;
6052 0 : PRInt32 offset = mContentOffset;
6053 :
6054 : // Try to look up the offset to frame property
6055 : nsTextFrame* cachedFrame = static_cast<nsTextFrame*>
6056 0 : (Properties().Get(OffsetToFrameProperty()));
6057 :
6058 0 : if (cachedFrame) {
6059 0 : f = cachedFrame;
6060 0 : offset = f->GetContentOffset();
6061 :
6062 0 : f->RemoveStateBits(TEXT_IN_OFFSET_CACHE);
6063 : }
6064 :
6065 0 : if ((aContentOffset >= offset) &&
6066 : (aHint || aContentOffset != offset)) {
6067 0 : while (true) {
6068 0 : nsTextFrame* next = static_cast<nsTextFrame*>(f->GetNextContinuation());
6069 0 : if (!next || aContentOffset < next->GetContentOffset())
6070 0 : break;
6071 0 : if (aContentOffset == next->GetContentOffset()) {
6072 0 : if (aHint) {
6073 0 : f = next;
6074 : }
6075 0 : break;
6076 : }
6077 0 : f = next;
6078 0 : }
6079 : } else {
6080 0 : while (true) {
6081 0 : nsTextFrame* prev = static_cast<nsTextFrame*>(f->GetPrevContinuation());
6082 0 : if (!prev || aContentOffset > f->GetContentOffset())
6083 0 : break;
6084 0 : if (aContentOffset == f->GetContentOffset()) {
6085 0 : if (!aHint) {
6086 0 : f = prev;
6087 : }
6088 0 : break;
6089 : }
6090 0 : f = prev;
6091 : }
6092 : }
6093 :
6094 0 : *aOutOffset = aContentOffset - f->GetContentOffset();
6095 0 : *aOutFrame = f;
6096 :
6097 : // cache the frame we found
6098 0 : Properties().Set(OffsetToFrameProperty(), f);
6099 0 : f->AddStateBits(TEXT_IN_OFFSET_CACHE);
6100 :
6101 0 : return NS_OK;
6102 : }
6103 :
6104 : bool
6105 0 : nsTextFrame::PeekOffsetNoAmount(bool aForward, PRInt32* aOffset)
6106 : {
6107 0 : NS_ASSERTION(aOffset && *aOffset <= GetContentLength(), "aOffset out of range");
6108 :
6109 0 : gfxSkipCharsIterator iter = EnsureTextRun(nsTextFrame::eInflated);
6110 0 : if (!mTextRun)
6111 0 : return false;
6112 :
6113 0 : TrimmedOffsets trimmed = GetTrimmedOffsets(mContent->GetText(), true);
6114 : // Check whether there are nonskipped characters in the trimmmed range
6115 0 : return iter.ConvertOriginalToSkipped(trimmed.GetEnd()) >
6116 0 : iter.ConvertOriginalToSkipped(trimmed.mStart);
6117 : }
6118 :
6119 : /**
6120 : * This class iterates through the clusters before or after the given
6121 : * aPosition (which is a content offset). You can test each cluster
6122 : * to see if it's whitespace (as far as selection/caret movement is concerned),
6123 : * or punctuation, or if there is a word break before the cluster. ("Before"
6124 : * is interpreted according to aDirection, so if aDirection is -1, "before"
6125 : * means actually *after* the cluster content.)
6126 : */
6127 0 : class NS_STACK_CLASS ClusterIterator {
6128 : public:
6129 : ClusterIterator(nsTextFrame* aTextFrame, PRInt32 aPosition, PRInt32 aDirection,
6130 : nsString& aContext);
6131 :
6132 : bool NextCluster();
6133 : bool IsWhitespace();
6134 : bool IsPunctuation();
6135 0 : bool HaveWordBreakBefore() { return mHaveWordBreak; }
6136 : PRInt32 GetAfterOffset();
6137 : PRInt32 GetBeforeOffset();
6138 :
6139 : private:
6140 : gfxSkipCharsIterator mIterator;
6141 : const nsTextFragment* mFrag;
6142 : nsTextFrame* mTextFrame;
6143 : PRInt32 mDirection;
6144 : PRInt32 mCharIndex;
6145 : nsTextFrame::TrimmedOffsets mTrimmed;
6146 : nsTArray<bool> mWordBreaks;
6147 : bool mHaveWordBreak;
6148 : };
6149 :
6150 : static bool
6151 0 : IsAcceptableCaretPosition(const gfxSkipCharsIterator& aIter,
6152 : bool aRespectClusters,
6153 : gfxTextRun* aTextRun,
6154 : nsIFrame* aFrame)
6155 : {
6156 0 : if (aIter.IsOriginalCharSkipped())
6157 0 : return false;
6158 0 : PRUint32 index = aIter.GetSkippedOffset();
6159 0 : if (aRespectClusters && !aTextRun->IsClusterStart(index))
6160 0 : return false;
6161 0 : if (index > 0) {
6162 : // Check whether the proposed position is in between the two halves of a
6163 : // surrogate pair; if so, this is not a valid character boundary.
6164 : // (In the case where we are respecting clusters, we won't actually get
6165 : // this far because the low surrogate is also marked as non-clusterStart
6166 : // so we'll return FALSE above.)
6167 0 : if (aTextRun->CharIsLowSurrogate(index)) {
6168 0 : return false;
6169 : }
6170 : }
6171 0 : return true;
6172 : }
6173 :
6174 : bool
6175 0 : nsTextFrame::PeekOffsetCharacter(bool aForward, PRInt32* aOffset,
6176 : bool aRespectClusters)
6177 : {
6178 0 : PRInt32 contentLength = GetContentLength();
6179 0 : NS_ASSERTION(aOffset && *aOffset <= contentLength, "aOffset out of range");
6180 :
6181 : bool selectable;
6182 : PRUint8 selectStyle;
6183 0 : IsSelectable(&selectable, &selectStyle);
6184 0 : if (selectStyle == NS_STYLE_USER_SELECT_ALL)
6185 0 : return false;
6186 :
6187 0 : gfxSkipCharsIterator iter = EnsureTextRun(nsTextFrame::eInflated);
6188 0 : if (!mTextRun)
6189 0 : return false;
6190 :
6191 0 : TrimmedOffsets trimmed = GetTrimmedOffsets(mContent->GetText(), false);
6192 :
6193 : // A negative offset means "end of frame".
6194 0 : PRInt32 startOffset = GetContentOffset() + (*aOffset < 0 ? contentLength : *aOffset);
6195 :
6196 0 : if (!aForward) {
6197 : // If at the beginning of the line, look at the previous continuation
6198 0 : for (PRInt32 i = NS_MIN(trimmed.GetEnd(), startOffset) - 1;
6199 : i >= trimmed.mStart; --i) {
6200 0 : iter.SetOriginalOffset(i);
6201 0 : if (IsAcceptableCaretPosition(iter, aRespectClusters, mTextRun, this)) {
6202 0 : *aOffset = i - mContentOffset;
6203 0 : return true;
6204 : }
6205 : }
6206 0 : *aOffset = 0;
6207 : } else {
6208 : // If we're at the end of a line, look at the next continuation
6209 0 : iter.SetOriginalOffset(startOffset);
6210 0 : if (startOffset <= trimmed.GetEnd() &&
6211 0 : !(startOffset < trimmed.GetEnd() &&
6212 0 : GetStyleText()->NewlineIsSignificant() &&
6213 0 : iter.GetSkippedOffset() < mTextRun->GetLength() &&
6214 0 : mTextRun->CharIsNewline(iter.GetSkippedOffset()))) {
6215 0 : for (PRInt32 i = startOffset + 1; i <= trimmed.GetEnd(); ++i) {
6216 0 : iter.SetOriginalOffset(i);
6217 0 : if (i == trimmed.GetEnd() ||
6218 0 : IsAcceptableCaretPosition(iter, aRespectClusters, mTextRun, this)) {
6219 0 : *aOffset = i - mContentOffset;
6220 0 : return true;
6221 : }
6222 : }
6223 : }
6224 0 : *aOffset = contentLength;
6225 : }
6226 :
6227 0 : return false;
6228 : }
6229 :
6230 : bool
6231 0 : ClusterIterator::IsWhitespace()
6232 : {
6233 0 : NS_ASSERTION(mCharIndex >= 0, "No cluster selected");
6234 0 : return IsSelectionSpace(mFrag, mCharIndex);
6235 : }
6236 :
6237 : bool
6238 0 : ClusterIterator::IsPunctuation()
6239 : {
6240 0 : NS_ASSERTION(mCharIndex >= 0, "No cluster selected");
6241 : nsIUGenCategory::nsUGenCategory c =
6242 0 : mozilla::unicode::GetGenCategory(mFrag->CharAt(mCharIndex));
6243 0 : return c == nsIUGenCategory::kPunctuation || c == nsIUGenCategory::kSymbol;
6244 : }
6245 :
6246 : PRInt32
6247 0 : ClusterIterator::GetBeforeOffset()
6248 : {
6249 0 : NS_ASSERTION(mCharIndex >= 0, "No cluster selected");
6250 0 : return mCharIndex + (mDirection > 0 ? 0 : 1);
6251 : }
6252 :
6253 : PRInt32
6254 0 : ClusterIterator::GetAfterOffset()
6255 : {
6256 0 : NS_ASSERTION(mCharIndex >= 0, "No cluster selected");
6257 0 : return mCharIndex + (mDirection > 0 ? 1 : 0);
6258 : }
6259 :
6260 : bool
6261 0 : ClusterIterator::NextCluster()
6262 : {
6263 0 : if (!mDirection)
6264 0 : return false;
6265 0 : gfxTextRun* textRun = mTextFrame->GetTextRun(nsTextFrame::eInflated);
6266 :
6267 0 : mHaveWordBreak = false;
6268 0 : while (true) {
6269 0 : bool keepGoing = false;
6270 0 : if (mDirection > 0) {
6271 0 : if (mIterator.GetOriginalOffset() >= mTrimmed.GetEnd())
6272 0 : return false;
6273 0 : keepGoing = mIterator.IsOriginalCharSkipped() ||
6274 0 : mIterator.GetOriginalOffset() < mTrimmed.mStart ||
6275 0 : !textRun->IsClusterStart(mIterator.GetSkippedOffset());
6276 0 : mCharIndex = mIterator.GetOriginalOffset();
6277 0 : mIterator.AdvanceOriginal(1);
6278 : } else {
6279 0 : if (mIterator.GetOriginalOffset() <= mTrimmed.mStart)
6280 0 : return false;
6281 0 : mIterator.AdvanceOriginal(-1);
6282 0 : keepGoing = mIterator.IsOriginalCharSkipped() ||
6283 0 : mIterator.GetOriginalOffset() >= mTrimmed.GetEnd() ||
6284 0 : !textRun->IsClusterStart(mIterator.GetSkippedOffset());
6285 0 : mCharIndex = mIterator.GetOriginalOffset();
6286 : }
6287 :
6288 0 : if (mWordBreaks[GetBeforeOffset() - mTextFrame->GetContentOffset()]) {
6289 0 : mHaveWordBreak = true;
6290 : }
6291 0 : if (!keepGoing)
6292 0 : return true;
6293 : }
6294 : }
6295 :
6296 0 : ClusterIterator::ClusterIterator(nsTextFrame* aTextFrame, PRInt32 aPosition,
6297 : PRInt32 aDirection, nsString& aContext)
6298 0 : : mTextFrame(aTextFrame), mDirection(aDirection), mCharIndex(-1)
6299 : {
6300 0 : mIterator = aTextFrame->EnsureTextRun(nsTextFrame::eInflated);
6301 0 : if (!aTextFrame->GetTextRun(nsTextFrame::eInflated)) {
6302 0 : mDirection = 0; // signal failure
6303 0 : return;
6304 : }
6305 0 : mIterator.SetOriginalOffset(aPosition);
6306 :
6307 0 : mFrag = aTextFrame->GetContent()->GetText();
6308 0 : mTrimmed = aTextFrame->GetTrimmedOffsets(mFrag, true);
6309 :
6310 0 : PRInt32 textOffset = aTextFrame->GetContentOffset();
6311 0 : PRInt32 textLen = aTextFrame->GetContentLength();
6312 0 : if (!mWordBreaks.AppendElements(textLen + 1)) {
6313 0 : mDirection = 0; // signal failure
6314 0 : return;
6315 : }
6316 0 : memset(mWordBreaks.Elements(), false, (textLen + 1)*sizeof(bool));
6317 : PRInt32 textStart;
6318 0 : if (aDirection > 0) {
6319 0 : if (aContext.IsEmpty()) {
6320 : // No previous context, so it must be the start of a line or text run
6321 0 : mWordBreaks[0] = true;
6322 : }
6323 0 : textStart = aContext.Length();
6324 0 : mFrag->AppendTo(aContext, textOffset, textLen);
6325 : } else {
6326 0 : if (aContext.IsEmpty()) {
6327 : // No following context, so it must be the end of a line or text run
6328 0 : mWordBreaks[textLen] = true;
6329 : }
6330 0 : textStart = 0;
6331 0 : nsAutoString str;
6332 0 : mFrag->AppendTo(str, textOffset, textLen);
6333 0 : aContext.Insert(str, 0);
6334 : }
6335 0 : nsIWordBreaker* wordBreaker = nsContentUtils::WordBreaker();
6336 : PRInt32 i;
6337 0 : for (i = 0; i <= textLen; ++i) {
6338 0 : PRInt32 indexInText = i + textStart;
6339 0 : mWordBreaks[i] |=
6340 : wordBreaker->BreakInBetween(aContext.get(), indexInText,
6341 0 : aContext.get() + indexInText,
6342 0 : aContext.Length() - indexInText);
6343 : }
6344 : }
6345 :
6346 : bool
6347 0 : nsTextFrame::PeekOffsetWord(bool aForward, bool aWordSelectEatSpace, bool aIsKeyboardSelect,
6348 : PRInt32* aOffset, PeekWordState* aState)
6349 : {
6350 0 : PRInt32 contentLength = GetContentLength();
6351 0 : NS_ASSERTION (aOffset && *aOffset <= contentLength, "aOffset out of range");
6352 :
6353 : bool selectable;
6354 : PRUint8 selectStyle;
6355 0 : IsSelectable(&selectable, &selectStyle);
6356 0 : if (selectStyle == NS_STYLE_USER_SELECT_ALL)
6357 0 : return false;
6358 :
6359 0 : PRInt32 offset = GetContentOffset() + (*aOffset < 0 ? contentLength : *aOffset);
6360 0 : ClusterIterator cIter(this, offset, aForward ? 1 : -1, aState->mContext);
6361 :
6362 0 : if (!cIter.NextCluster())
6363 0 : return false;
6364 :
6365 0 : do {
6366 0 : bool isPunctuation = cIter.IsPunctuation();
6367 0 : bool isWhitespace = cIter.IsWhitespace();
6368 0 : bool isWordBreakBefore = cIter.HaveWordBreakBefore();
6369 0 : if (aWordSelectEatSpace == isWhitespace && !aState->mSawBeforeType) {
6370 0 : aState->SetSawBeforeType();
6371 0 : aState->Update(isPunctuation, isWhitespace);
6372 0 : continue;
6373 : }
6374 : // See if we can break before the current cluster
6375 0 : if (!aState->mAtStart) {
6376 : bool canBreak;
6377 0 : if (isPunctuation != aState->mLastCharWasPunctuation) {
6378 : canBreak = BreakWordBetweenPunctuation(aState, aForward,
6379 0 : isPunctuation, isWhitespace, aIsKeyboardSelect);
6380 0 : } else if (!aState->mLastCharWasWhitespace &&
6381 0 : !isWhitespace && !isPunctuation && isWordBreakBefore) {
6382 : // if both the previous and the current character are not white
6383 : // space but this can be word break before, we don't need to eat
6384 : // a white space in this case. This case happens in some languages
6385 : // that their words are not separated by white spaces. E.g.,
6386 : // Japanese and Chinese.
6387 0 : canBreak = true;
6388 : } else {
6389 0 : canBreak = isWordBreakBefore && aState->mSawBeforeType;
6390 : }
6391 0 : if (canBreak) {
6392 0 : *aOffset = cIter.GetBeforeOffset() - mContentOffset;
6393 0 : return true;
6394 : }
6395 : }
6396 0 : aState->Update(isPunctuation, isWhitespace);
6397 : } while (cIter.NextCluster());
6398 :
6399 0 : *aOffset = cIter.GetAfterOffset() - mContentOffset;
6400 0 : return false;
6401 : }
6402 :
6403 : // TODO this needs to be deCOMtaminated with the interface fixed in
6404 : // nsIFrame.h, but we won't do that until the old textframe is gone.
6405 : NS_IMETHODIMP
6406 0 : nsTextFrame::CheckVisibility(nsPresContext* aContext, PRInt32 aStartIndex,
6407 : PRInt32 aEndIndex, bool aRecurse, bool *aFinished, bool *aRetval)
6408 : {
6409 0 : if (!aRetval)
6410 0 : return NS_ERROR_NULL_POINTER;
6411 :
6412 : // Text in the range is visible if there is at least one character in the range
6413 : // that is not skipped and is mapped by this frame (which is the primary frame)
6414 : // or one of its continuations.
6415 0 : for (nsTextFrame* f = this; f;
6416 0 : f = static_cast<nsTextFrame*>(GetNextContinuation())) {
6417 0 : PRInt32 dummyOffset = 0;
6418 0 : if (f->PeekOffsetNoAmount(true, &dummyOffset)) {
6419 0 : *aRetval = true;
6420 0 : return NS_OK;
6421 : }
6422 : }
6423 :
6424 0 : *aRetval = false;
6425 0 : return NS_OK;
6426 : }
6427 :
6428 : NS_IMETHODIMP
6429 0 : nsTextFrame::GetOffsets(PRInt32 &start, PRInt32 &end) const
6430 : {
6431 0 : start = GetContentOffset();
6432 0 : end = GetContentEnd();
6433 0 : return NS_OK;
6434 : }
6435 :
6436 : static PRInt32
6437 0 : FindEndOfPunctuationRun(const nsTextFragment* aFrag,
6438 : gfxTextRun* aTextRun,
6439 : gfxSkipCharsIterator* aIter,
6440 : PRInt32 aOffset,
6441 : PRInt32 aStart,
6442 : PRInt32 aEnd)
6443 : {
6444 : PRInt32 i;
6445 :
6446 0 : for (i = aStart; i < aEnd - aOffset; ++i) {
6447 0 : if (nsContentUtils::IsFirstLetterPunctuationAt(aFrag, aOffset + i)) {
6448 0 : aIter->SetOriginalOffset(aOffset + i);
6449 0 : FindClusterEnd(aTextRun, aEnd, aIter);
6450 0 : i = aIter->GetOriginalOffset() - aOffset;
6451 : } else {
6452 0 : break;
6453 : }
6454 : }
6455 0 : return i;
6456 : }
6457 :
6458 : /**
6459 : * Returns true if this text frame completes the first-letter, false
6460 : * if it does not contain a true "letter".
6461 : * If returns true, then it also updates aLength to cover just the first-letter
6462 : * text.
6463 : *
6464 : * XXX :first-letter should be handled during frame construction
6465 : * (and it has a good bit in common with nextBidi)
6466 : *
6467 : * @param aLength an in/out parameter: on entry contains the maximum length to
6468 : * return, on exit returns length of the first-letter fragment (which may
6469 : * include leading and trailing punctuation, for example)
6470 : */
6471 : static bool
6472 0 : FindFirstLetterRange(const nsTextFragment* aFrag,
6473 : gfxTextRun* aTextRun,
6474 : PRInt32 aOffset, const gfxSkipCharsIterator& aIter,
6475 : PRInt32* aLength)
6476 : {
6477 : PRInt32 i;
6478 0 : PRInt32 length = *aLength;
6479 0 : PRInt32 endOffset = aOffset + length;
6480 0 : gfxSkipCharsIterator iter(aIter);
6481 :
6482 : // skip leading whitespace, then consume clusters that start with punctuation
6483 : i = FindEndOfPunctuationRun(aFrag, aTextRun, &iter, aOffset,
6484 0 : GetTrimmableWhitespaceCount(aFrag, aOffset, length, 1),
6485 0 : endOffset);
6486 0 : if (i == length)
6487 0 : return false;
6488 :
6489 : // If the next character is not a letter or number, there is no first-letter.
6490 : // Return true so that we don't go on looking, but set aLength to 0.
6491 0 : if (!nsContentUtils::IsAlphanumericAt(aFrag, aOffset + i)) {
6492 0 : *aLength = 0;
6493 0 : return true;
6494 : }
6495 :
6496 : // consume another cluster (the actual first letter)
6497 0 : iter.SetOriginalOffset(aOffset + i);
6498 0 : FindClusterEnd(aTextRun, endOffset, &iter);
6499 0 : i = iter.GetOriginalOffset() - aOffset;
6500 0 : if (i + 1 == length)
6501 0 : return true;
6502 :
6503 : // consume clusters that start with punctuation
6504 0 : i = FindEndOfPunctuationRun(aFrag, aTextRun, &iter, aOffset, i + 1, endOffset);
6505 0 : if (i < length)
6506 0 : *aLength = i;
6507 0 : return true;
6508 : }
6509 :
6510 : static PRUint32
6511 0 : FindStartAfterSkippingWhitespace(PropertyProvider* aProvider,
6512 : nsIFrame::InlineIntrinsicWidthData* aData,
6513 : const nsStyleText* aTextStyle,
6514 : gfxSkipCharsIterator* aIterator,
6515 : PRUint32 aFlowEndInTextRun)
6516 : {
6517 0 : if (aData->skipWhitespace) {
6518 0 : while (aIterator->GetSkippedOffset() < aFlowEndInTextRun &&
6519 0 : IsTrimmableSpace(aProvider->GetFragment(), aIterator->GetOriginalOffset(), aTextStyle)) {
6520 0 : aIterator->AdvanceOriginal(1);
6521 : }
6522 : }
6523 0 : return aIterator->GetSkippedOffset();
6524 : }
6525 :
6526 : union VoidPtrOrFloat {
6527 0 : VoidPtrOrFloat() : p(nsnull) {}
6528 :
6529 : void *p;
6530 : float f;
6531 : };
6532 :
6533 : float
6534 0 : nsTextFrame::GetFontSizeInflation() const
6535 : {
6536 0 : if (!HasFontSizeInflation()) {
6537 0 : return 1.0f;
6538 : }
6539 0 : VoidPtrOrFloat u;
6540 0 : u.p = Properties().Get(FontSizeInflationProperty());
6541 0 : return u.f;
6542 : }
6543 :
6544 : void
6545 0 : nsTextFrame::SetFontSizeInflation(float aInflation)
6546 : {
6547 0 : if (aInflation == 1.0f) {
6548 0 : if (HasFontSizeInflation()) {
6549 0 : RemoveStateBits(TEXT_HAS_FONT_INFLATION);
6550 0 : Properties().Delete(FontSizeInflationProperty());
6551 : }
6552 0 : return;
6553 : }
6554 :
6555 0 : AddStateBits(TEXT_HAS_FONT_INFLATION);
6556 0 : VoidPtrOrFloat u;
6557 0 : u.f = aInflation;
6558 0 : Properties().Set(FontSizeInflationProperty(), u.p);
6559 : }
6560 :
6561 : /* virtual */
6562 0 : void nsTextFrame::MarkIntrinsicWidthsDirty()
6563 : {
6564 0 : ClearTextRuns();
6565 0 : nsFrame::MarkIntrinsicWidthsDirty();
6566 0 : }
6567 :
6568 : // XXX this doesn't handle characters shaped by line endings. We need to
6569 : // temporarily override the "current line ending" settings.
6570 : void
6571 0 : nsTextFrame::AddInlineMinWidthForFlow(nsRenderingContext *aRenderingContext,
6572 : nsIFrame::InlineMinWidthData *aData,
6573 : float aInflation,
6574 : TextRunType aTextRunType)
6575 : {
6576 : PRUint32 flowEndInTextRun;
6577 0 : gfxContext* ctx = aRenderingContext->ThebesContext();
6578 : gfxSkipCharsIterator iter =
6579 : EnsureTextRun(aTextRunType, aInflation, ctx, aData->lineContainer,
6580 0 : aData->line, &flowEndInTextRun);
6581 0 : gfxTextRun *textRun = GetTextRun(aTextRunType);
6582 0 : if (!textRun)
6583 0 : return;
6584 :
6585 : // Pass null for the line container. This will disable tab spacing, but that's
6586 : // OK since we can't really handle tabs for intrinsic sizing anyway.
6587 0 : const nsStyleText* textStyle = GetStyleText();
6588 0 : const nsTextFragment* frag = mContent->GetText();
6589 :
6590 : // If we're hyphenating, the PropertyProvider needs the actual length;
6591 : // otherwise we can just pass PR_INT32_MAX to mean "all the text"
6592 0 : PRInt32 len = PR_INT32_MAX;
6593 0 : bool hyphenating = frag->GetLength() > 0 &&
6594 : (textStyle->mHyphens == NS_STYLE_HYPHENS_AUTO ||
6595 : (textStyle->mHyphens == NS_STYLE_HYPHENS_MANUAL &&
6596 0 : (textRun->GetFlags() & gfxTextRunFactory::TEXT_ENABLE_HYPHEN_BREAKS) != 0));
6597 0 : if (hyphenating) {
6598 0 : gfxSkipCharsIterator tmp(iter);
6599 0 : len = NS_MIN<PRInt32>(GetContentOffset() + GetInFlowContentLength(),
6600 0 : tmp.ConvertSkippedToOriginal(flowEndInTextRun)) - iter.GetOriginalOffset();
6601 : }
6602 : PropertyProvider provider(textRun, textStyle, frag, this,
6603 0 : iter, len, nsnull, 0, aTextRunType);
6604 :
6605 0 : bool collapseWhitespace = !textStyle->WhiteSpaceIsSignificant();
6606 0 : bool preformatNewlines = textStyle->NewlineIsSignificant();
6607 0 : bool preformatTabs = textStyle->WhiteSpaceIsSignificant();
6608 0 : gfxFloat tabWidth = -1;
6609 : PRUint32 start =
6610 0 : FindStartAfterSkippingWhitespace(&provider, aData, textStyle, &iter, flowEndInTextRun);
6611 :
6612 0 : AutoFallibleTArray<bool,BIG_TEXT_NODE_SIZE> hyphBuffer;
6613 0 : bool *hyphBreakBefore = nsnull;
6614 0 : if (hyphenating) {
6615 0 : hyphBreakBefore = hyphBuffer.AppendElements(flowEndInTextRun - start);
6616 0 : if (hyphBreakBefore) {
6617 : provider.GetHyphenationBreaks(start, flowEndInTextRun - start,
6618 0 : hyphBreakBefore);
6619 : }
6620 : }
6621 :
6622 0 : for (PRUint32 i = start, wordStart = start; i <= flowEndInTextRun; ++i) {
6623 0 : bool preformattedNewline = false;
6624 0 : bool preformattedTab = false;
6625 0 : if (i < flowEndInTextRun) {
6626 : // XXXldb Shouldn't we be including the newline as part of the
6627 : // segment that it ends rather than part of the segment that it
6628 : // starts?
6629 0 : preformattedNewline = preformatNewlines && textRun->CharIsNewline(i);
6630 0 : preformattedTab = preformatTabs && textRun->CharIsTab(i);
6631 0 : if (!textRun->CanBreakLineBefore(i) &&
6632 0 : !preformattedNewline &&
6633 0 : !preformattedTab &&
6634 0 : (!hyphBreakBefore || !hyphBreakBefore[i - start]))
6635 : {
6636 : // we can't break here (and it's not the end of the flow)
6637 0 : continue;
6638 : }
6639 : }
6640 :
6641 0 : if (i > wordStart) {
6642 : nscoord width =
6643 0 : NSToCoordCeilClamped(textRun->GetAdvanceWidth(wordStart, i - wordStart, &provider));
6644 0 : aData->currentLine = NSCoordSaturatingAdd(aData->currentLine, width);
6645 0 : aData->atStartOfLine = false;
6646 :
6647 0 : if (collapseWhitespace) {
6648 0 : PRUint32 trimStart = GetEndOfTrimmedText(frag, textStyle, wordStart, i, &iter);
6649 0 : if (trimStart == start) {
6650 : // This is *all* trimmable whitespace, so whatever trailingWhitespace
6651 : // we saw previously is still trailing...
6652 0 : aData->trailingWhitespace += width;
6653 : } else {
6654 : // Some non-whitespace so the old trailingWhitespace is no longer trailing
6655 : aData->trailingWhitespace =
6656 0 : NSToCoordCeilClamped(textRun->GetAdvanceWidth(trimStart, i - trimStart, &provider));
6657 : }
6658 : } else {
6659 0 : aData->trailingWhitespace = 0;
6660 : }
6661 : }
6662 :
6663 0 : if (preformattedTab) {
6664 : PropertyProvider::Spacing spacing;
6665 0 : provider.GetSpacing(i, 1, &spacing);
6666 0 : aData->currentLine += nscoord(spacing.mBefore);
6667 : gfxFloat afterTab =
6668 : AdvanceToNextTab(aData->currentLine, this,
6669 0 : textRun, &tabWidth);
6670 0 : aData->currentLine = nscoord(afterTab + spacing.mAfter);
6671 0 : wordStart = i + 1;
6672 0 : } else if (i < flowEndInTextRun ||
6673 0 : (i == textRun->GetLength() &&
6674 0 : (textRun->GetFlags() & nsTextFrameUtils::TEXT_HAS_TRAILING_BREAK))) {
6675 0 : if (preformattedNewline) {
6676 0 : aData->ForceBreak(aRenderingContext);
6677 0 : } else if (i < flowEndInTextRun && hyphBreakBefore &&
6678 0 : hyphBreakBefore[i - start])
6679 : {
6680 : aData->OptionallyBreak(aRenderingContext,
6681 0 : NSToCoordRound(provider.GetHyphenWidth()));
6682 : } {
6683 0 : aData->OptionallyBreak(aRenderingContext);
6684 : }
6685 0 : wordStart = i;
6686 : }
6687 : }
6688 :
6689 0 : if (start < flowEndInTextRun) {
6690 : // Check if we have collapsible whitespace at the end
6691 : aData->skipWhitespace =
6692 : IsTrimmableSpace(provider.GetFragment(),
6693 : iter.ConvertSkippedToOriginal(flowEndInTextRun - 1),
6694 0 : textStyle);
6695 : }
6696 : }
6697 :
6698 : // XXX Need to do something here to avoid incremental reflow bugs due to
6699 : // first-line and first-letter changing min-width
6700 : /* virtual */ void
6701 0 : nsTextFrame::AddInlineMinWidth(nsRenderingContext *aRenderingContext,
6702 : nsIFrame::InlineMinWidthData *aData)
6703 : {
6704 : float inflation =
6705 0 : nsLayoutUtils::FontSizeInflationFor(this, nsLayoutUtils::eInReflow);
6706 0 : TextRunType trtype = (inflation == 1.0f) ? eNotInflated : eInflated;
6707 :
6708 : nsTextFrame* f;
6709 0 : gfxTextRun* lastTextRun = nsnull;
6710 : // nsContinuingTextFrame does nothing for AddInlineMinWidth; all text frames
6711 : // in the flow are handled right here.
6712 0 : for (f = this; f; f = static_cast<nsTextFrame*>(f->GetNextContinuation())) {
6713 : // f->GetTextRun(nsTextFrame::eNotInflated) could be null if we
6714 : // haven't set up textruns yet for f. Except in OOM situations,
6715 : // lastTextRun will only be null for the first text frame.
6716 0 : if (f == this || f->GetTextRun(trtype) != lastTextRun) {
6717 : nsIFrame* lc;
6718 0 : if (aData->lineContainer &&
6719 0 : aData->lineContainer != (lc = FindLineContainer(f))) {
6720 0 : NS_ASSERTION(f != this, "wrong InlineMinWidthData container"
6721 : " for first continuation");
6722 0 : aData->line = nsnull;
6723 0 : aData->lineContainer = lc;
6724 : }
6725 :
6726 : // This will process all the text frames that share the same textrun as f.
6727 : f->AddInlineMinWidthForFlow(aRenderingContext, aData,
6728 0 : inflation, trtype);
6729 0 : lastTextRun = f->GetTextRun(trtype);
6730 : }
6731 : }
6732 0 : }
6733 :
6734 : // XXX this doesn't handle characters shaped by line endings. We need to
6735 : // temporarily override the "current line ending" settings.
6736 : void
6737 0 : nsTextFrame::AddInlinePrefWidthForFlow(nsRenderingContext *aRenderingContext,
6738 : nsIFrame::InlinePrefWidthData *aData,
6739 : float aInflation,
6740 : TextRunType aTextRunType)
6741 : {
6742 : PRUint32 flowEndInTextRun;
6743 0 : gfxContext* ctx = aRenderingContext->ThebesContext();
6744 : gfxSkipCharsIterator iter =
6745 : EnsureTextRun(aTextRunType, aInflation, ctx, aData->lineContainer,
6746 0 : aData->line, &flowEndInTextRun);
6747 0 : gfxTextRun *textRun = GetTextRun(aTextRunType);
6748 0 : if (!textRun)
6749 0 : return;
6750 :
6751 : // Pass null for the line container. This will disable tab spacing, but that's
6752 : // OK since we can't really handle tabs for intrinsic sizing anyway.
6753 :
6754 0 : const nsStyleText* textStyle = GetStyleText();
6755 0 : const nsTextFragment* frag = mContent->GetText();
6756 : PropertyProvider provider(textRun, textStyle, frag, this,
6757 0 : iter, PR_INT32_MAX, nsnull, 0, aTextRunType);
6758 :
6759 0 : bool collapseWhitespace = !textStyle->WhiteSpaceIsSignificant();
6760 0 : bool preformatNewlines = textStyle->NewlineIsSignificant();
6761 0 : bool preformatTabs = textStyle->WhiteSpaceIsSignificant();
6762 0 : gfxFloat tabWidth = -1;
6763 : PRUint32 start =
6764 0 : FindStartAfterSkippingWhitespace(&provider, aData, textStyle, &iter, flowEndInTextRun);
6765 :
6766 : // XXX Should we consider hyphenation here?
6767 : // If newlines and tabs aren't preformatted, nothing to do inside
6768 : // the loop so make i skip to the end
6769 0 : PRUint32 loopStart = (preformatNewlines || preformatTabs) ? start : flowEndInTextRun;
6770 0 : for (PRUint32 i = loopStart, lineStart = start; i <= flowEndInTextRun; ++i) {
6771 0 : bool preformattedNewline = false;
6772 0 : bool preformattedTab = false;
6773 0 : if (i < flowEndInTextRun) {
6774 : // XXXldb Shouldn't we be including the newline as part of the
6775 : // segment that it ends rather than part of the segment that it
6776 : // starts?
6777 0 : NS_ASSERTION(preformatNewlines, "We can't be here unless newlines are hard breaks");
6778 0 : preformattedNewline = preformatNewlines && textRun->CharIsNewline(i);
6779 0 : preformattedTab = preformatTabs && textRun->CharIsTab(i);
6780 0 : if (!preformattedNewline && !preformattedTab) {
6781 : // we needn't break here (and it's not the end of the flow)
6782 0 : continue;
6783 : }
6784 : }
6785 :
6786 0 : if (i > lineStart) {
6787 : nscoord width =
6788 0 : NSToCoordCeilClamped(textRun->GetAdvanceWidth(lineStart, i - lineStart, &provider));
6789 0 : aData->currentLine = NSCoordSaturatingAdd(aData->currentLine, width);
6790 :
6791 0 : if (collapseWhitespace) {
6792 0 : PRUint32 trimStart = GetEndOfTrimmedText(frag, textStyle, lineStart, i, &iter);
6793 0 : if (trimStart == start) {
6794 : // This is *all* trimmable whitespace, so whatever trailingWhitespace
6795 : // we saw previously is still trailing...
6796 0 : aData->trailingWhitespace += width;
6797 : } else {
6798 : // Some non-whitespace so the old trailingWhitespace is no longer trailing
6799 : aData->trailingWhitespace =
6800 0 : NSToCoordCeilClamped(textRun->GetAdvanceWidth(trimStart, i - trimStart, &provider));
6801 : }
6802 : } else {
6803 0 : aData->trailingWhitespace = 0;
6804 : }
6805 : }
6806 :
6807 0 : if (preformattedTab) {
6808 : PropertyProvider::Spacing spacing;
6809 0 : provider.GetSpacing(i, 1, &spacing);
6810 0 : aData->currentLine += nscoord(spacing.mBefore);
6811 : gfxFloat afterTab =
6812 : AdvanceToNextTab(aData->currentLine, this,
6813 0 : textRun, &tabWidth);
6814 0 : aData->currentLine = nscoord(afterTab + spacing.mAfter);
6815 0 : lineStart = i + 1;
6816 0 : } else if (preformattedNewline) {
6817 0 : aData->ForceBreak(aRenderingContext);
6818 0 : lineStart = i;
6819 : }
6820 : }
6821 :
6822 : // Check if we have collapsible whitespace at the end
6823 0 : if (start < flowEndInTextRun) {
6824 : aData->skipWhitespace =
6825 : IsTrimmableSpace(provider.GetFragment(),
6826 : iter.ConvertSkippedToOriginal(flowEndInTextRun - 1),
6827 0 : textStyle);
6828 : }
6829 : }
6830 :
6831 : // XXX Need to do something here to avoid incremental reflow bugs due to
6832 : // first-line and first-letter changing pref-width
6833 : /* virtual */ void
6834 0 : nsTextFrame::AddInlinePrefWidth(nsRenderingContext *aRenderingContext,
6835 : nsIFrame::InlinePrefWidthData *aData)
6836 : {
6837 : float inflation =
6838 0 : nsLayoutUtils::FontSizeInflationFor(this, nsLayoutUtils::eInReflow);
6839 0 : TextRunType trtype = (inflation == 1.0f) ? eNotInflated : eInflated;
6840 :
6841 : nsTextFrame* f;
6842 0 : gfxTextRun* lastTextRun = nsnull;
6843 : // nsContinuingTextFrame does nothing for AddInlineMinWidth; all text frames
6844 : // in the flow are handled right here.
6845 0 : for (f = this; f; f = static_cast<nsTextFrame*>(f->GetNextContinuation())) {
6846 : // f->GetTextRun(nsTextFrame::eNotInflated) could be null if we
6847 : // haven't set up textruns yet for f. Except in OOM situations,
6848 : // lastTextRun will only be null for the first text frame.
6849 0 : if (f == this || f->GetTextRun(trtype) != lastTextRun) {
6850 : nsIFrame* lc;
6851 0 : if (aData->lineContainer &&
6852 0 : aData->lineContainer != (lc = FindLineContainer(f))) {
6853 0 : NS_ASSERTION(f != this, "wrong InlinePrefWidthData container"
6854 : " for first continuation");
6855 0 : aData->line = nsnull;
6856 0 : aData->lineContainer = lc;
6857 : }
6858 :
6859 : // This will process all the text frames that share the same textrun as f.
6860 : f->AddInlinePrefWidthForFlow(aRenderingContext, aData,
6861 0 : inflation, trtype);
6862 0 : lastTextRun = f->GetTextRun(trtype);
6863 : }
6864 : }
6865 0 : }
6866 :
6867 : /* virtual */ nsSize
6868 0 : nsTextFrame::ComputeSize(nsRenderingContext *aRenderingContext,
6869 : nsSize aCBSize, nscoord aAvailableWidth,
6870 : nsSize aMargin, nsSize aBorder, nsSize aPadding,
6871 : bool aShrinkWrap)
6872 : {
6873 : // Inlines and text don't compute size before reflow.
6874 0 : return nsSize(NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE);
6875 : }
6876 :
6877 : static nsRect
6878 0 : RoundOut(const gfxRect& aRect)
6879 : {
6880 0 : nsRect r;
6881 0 : r.x = NSToCoordFloor(aRect.X());
6882 0 : r.y = NSToCoordFloor(aRect.Y());
6883 0 : r.width = NSToCoordCeil(aRect.XMost()) - r.x;
6884 0 : r.height = NSToCoordCeil(aRect.YMost()) - r.y;
6885 : return r;
6886 : }
6887 :
6888 : nsRect
6889 0 : nsTextFrame::ComputeTightBounds(gfxContext* aContext) const
6890 : {
6891 0 : if (GetStyleContext()->HasTextDecorationLines() ||
6892 0 : (GetStateBits() & TEXT_HYPHEN_BREAK)) {
6893 : // This is conservative, but OK.
6894 0 : return GetVisualOverflowRect();
6895 : }
6896 :
6897 : gfxSkipCharsIterator iter =
6898 0 : const_cast<nsTextFrame*>(this)->EnsureTextRun(nsTextFrame::eInflated);
6899 0 : if (!mTextRun)
6900 0 : return nsRect(0, 0, 0, 0);
6901 :
6902 : PropertyProvider provider(const_cast<nsTextFrame*>(this), iter,
6903 0 : nsTextFrame::eInflated);
6904 : // Trim trailing whitespace
6905 0 : provider.InitializeForDisplay(true);
6906 :
6907 : gfxTextRun::Metrics metrics =
6908 0 : mTextRun->MeasureText(provider.GetStart().GetSkippedOffset(),
6909 : ComputeTransformedLength(provider),
6910 : gfxFont::TIGHT_HINTED_OUTLINE_EXTENTS,
6911 0 : aContext, &provider);
6912 : // mAscent should be the same as metrics.mAscent, but it's what we use to
6913 : // paint so that's the one we'll use.
6914 0 : return RoundOut(metrics.mBoundingBox) + nsPoint(0, mAscent);
6915 : }
6916 :
6917 : static bool
6918 0 : HasSoftHyphenBefore(const nsTextFragment* aFrag, gfxTextRun* aTextRun,
6919 : PRInt32 aStartOffset, const gfxSkipCharsIterator& aIter)
6920 : {
6921 0 : if (aIter.GetSkippedOffset() < aTextRun->GetLength() &&
6922 0 : aTextRun->CanHyphenateBefore(aIter.GetSkippedOffset())) {
6923 0 : return true;
6924 : }
6925 0 : if (!(aTextRun->GetFlags() & nsTextFrameUtils::TEXT_HAS_SHY))
6926 0 : return false;
6927 0 : gfxSkipCharsIterator iter = aIter;
6928 0 : while (iter.GetOriginalOffset() > aStartOffset) {
6929 0 : iter.AdvanceOriginal(-1);
6930 0 : if (!iter.IsOriginalCharSkipped())
6931 0 : break;
6932 0 : if (aFrag->CharAt(iter.GetOriginalOffset()) == CH_SHY)
6933 0 : return true;
6934 : }
6935 0 : return false;
6936 : }
6937 :
6938 : static void
6939 0 : RemoveInFlows(nsIFrame* aFrame, nsIFrame* aFirstToNotRemove)
6940 : {
6941 0 : NS_PRECONDITION(aFrame != aFirstToNotRemove, "This will go very badly");
6942 : // We have to be careful here, because some RemoveFrame implementations
6943 : // remove and destroy not only the passed-in frame but also all its following
6944 : // in-flows (and sometimes all its following continuations in general). So
6945 : // we remove |f| and everything up to but not including firstToNotRemove from
6946 : // the flow first, to make sure that only the things we want destroyed are
6947 : // destroyed.
6948 :
6949 : // This sadly duplicates some of the logic from
6950 : // nsSplittableFrame::RemoveFromFlow. We can get away with not duplicating
6951 : // all of it, because we know that the prev-continuation links of
6952 : // firstToNotRemove and f are fluid, and non-null.
6953 0 : NS_ASSERTION(aFirstToNotRemove->GetPrevContinuation() ==
6954 : aFirstToNotRemove->GetPrevInFlow() &&
6955 : aFirstToNotRemove->GetPrevInFlow() != nsnull,
6956 : "aFirstToNotRemove should have a fluid prev continuation");
6957 0 : NS_ASSERTION(aFrame->GetPrevContinuation() ==
6958 : aFrame->GetPrevInFlow() &&
6959 : aFrame->GetPrevInFlow() != nsnull,
6960 : "aFrame should have a fluid prev continuation");
6961 :
6962 0 : nsIFrame* prevContinuation = aFrame->GetPrevContinuation();
6963 0 : nsIFrame* lastRemoved = aFirstToNotRemove->GetPrevContinuation();
6964 :
6965 0 : prevContinuation->SetNextInFlow(aFirstToNotRemove);
6966 0 : aFirstToNotRemove->SetPrevInFlow(prevContinuation);
6967 :
6968 0 : aFrame->SetPrevInFlow(nsnull);
6969 0 : lastRemoved->SetNextInFlow(nsnull);
6970 :
6971 0 : nsIFrame *parent = aFrame->GetParent();
6972 0 : nsBlockFrame *parentBlock = nsLayoutUtils::GetAsBlock(parent);
6973 0 : if (parentBlock) {
6974 : // Manually call DoRemoveFrame so we can tell it that we're
6975 : // removing empty frames; this will keep it from blowing away
6976 : // text runs.
6977 0 : parentBlock->DoRemoveFrame(aFrame, nsBlockFrame::FRAMES_ARE_EMPTY);
6978 : } else {
6979 : // Just remove it normally; use the nextBidi list to avoid
6980 : // posting new reflows.
6981 0 : parent->RemoveFrame(nsIFrame::kNoReflowPrincipalList, aFrame);
6982 : }
6983 0 : }
6984 :
6985 : void
6986 0 : nsTextFrame::SetLength(PRInt32 aLength, nsLineLayout* aLineLayout,
6987 : PRUint32 aSetLengthFlags)
6988 : {
6989 0 : mContentLengthHint = aLength;
6990 0 : PRInt32 end = GetContentOffset() + aLength;
6991 0 : nsTextFrame* f = static_cast<nsTextFrame*>(GetNextInFlow());
6992 0 : if (!f)
6993 0 : return;
6994 :
6995 : // If our end offset is moving, then even if frames are not being pushed or
6996 : // pulled, content is moving to or from the next line and the next line
6997 : // must be reflowed.
6998 : // If the next-continuation is dirty, then we should dirty the next line now
6999 : // because we may have skipped doing it if we dirtied it in
7000 : // CharacterDataChanged. This is ugly but teaching FrameNeedsReflow
7001 : // and ChildIsDirty to handle a range of frames would be worse.
7002 0 : if (aLineLayout &&
7003 0 : (end != f->mContentOffset || (f->GetStateBits() & NS_FRAME_IS_DIRTY))) {
7004 0 : aLineLayout->SetDirtyNextLine();
7005 : }
7006 :
7007 0 : if (end < f->mContentOffset) {
7008 : // Our frame is shrinking. Give the text to our next in flow.
7009 0 : if (aLineLayout &&
7010 0 : GetStyleText()->WhiteSpaceIsSignificant() &&
7011 0 : HasTerminalNewline() &&
7012 0 : GetParent()->GetType() != nsGkAtoms::letterFrame &&
7013 : (aSetLengthFlags & ALLOW_FRAME_CREATION_AND_DESTRUCTION)) {
7014 : // Whatever text we hand to our next-in-flow will end up in a frame all of
7015 : // its own, since it ends in a forced linebreak. Might as well just put
7016 : // it in a separate frame now. This is important to prevent text run
7017 : // churn; if we did not do that, then we'd likely end up rebuilding
7018 : // textruns for all our following continuations.
7019 : // We skip this optimization when the parent is a first-letter frame
7020 : // because it doesn't deal well with more than one child frame.
7021 : // We also skip this optimization if we were called during bidi
7022 : // resolution, so as not to create a new frame which doesn't appear in
7023 : // the bidi resolver's list of frames
7024 0 : nsPresContext* presContext = PresContext();
7025 : nsIFrame* newFrame;
7026 : nsresult rv = presContext->PresShell()->FrameConstructor()->
7027 0 : CreateContinuingFrame(presContext, this, GetParent(), &newFrame);
7028 0 : if (NS_SUCCEEDED(rv)) {
7029 0 : nsTextFrame* next = static_cast<nsTextFrame*>(newFrame);
7030 0 : nsFrameList temp(next, next);
7031 0 : GetParent()->InsertFrames(kNoReflowPrincipalList, this, temp);
7032 0 : f = next;
7033 : }
7034 : }
7035 :
7036 0 : f->mContentOffset = end;
7037 0 : if (f->GetTextRun(nsTextFrame::eInflated) != mTextRun) {
7038 0 : ClearTextRuns();
7039 0 : f->ClearTextRuns();
7040 : }
7041 0 : return;
7042 : }
7043 : // Our frame is growing. Take text from our in-flow(s).
7044 : // We can take text from frames in lines beyond just the next line.
7045 : // We don't dirty those lines. That's OK, because when we reflow
7046 : // our empty next-in-flow, it will take text from its next-in-flow and
7047 : // dirty that line.
7048 :
7049 : // Note that in the process we may end up removing some frames from
7050 : // the flow if they end up empty.
7051 0 : nsIFrame *framesToRemove = nsnull;
7052 0 : while (f && f->mContentOffset < end) {
7053 0 : f->mContentOffset = end;
7054 0 : if (f->GetTextRun(nsTextFrame::eInflated) != mTextRun) {
7055 0 : ClearTextRuns();
7056 0 : f->ClearTextRuns();
7057 : }
7058 0 : nsTextFrame* next = static_cast<nsTextFrame*>(f->GetNextInFlow());
7059 : // Note: the "f->GetNextSibling() == next" check below is to restrict
7060 : // this optimization to the case where they are on the same child list.
7061 : // Otherwise we might remove the only child of a nsFirstLetterFrame
7062 : // for example and it can't handle that. See bug 597627 for details.
7063 0 : if (next && next->mContentOffset <= end && f->GetNextSibling() == next &&
7064 : (aSetLengthFlags & ALLOW_FRAME_CREATION_AND_DESTRUCTION)) {
7065 : // |f| is now empty. We may as well remove it, instead of copying all
7066 : // the text from |next| into it instead; the latter leads to use
7067 : // rebuilding textruns for all following continuations.
7068 : // We skip this optimization if we were called during bidi resolution,
7069 : // since the bidi resolver may try to handle the destroyed frame later
7070 : // and crash
7071 0 : if (!framesToRemove) {
7072 : // Remember that we have to remove this frame.
7073 0 : framesToRemove = f;
7074 : }
7075 :
7076 : // Important: if |f| has the same style context as its prev continuation,
7077 : // mark it accordingly so we can skip clearing textruns as needed. Note
7078 : // that at this point f always has a prev continuation.
7079 0 : if (f->GetStyleContext() == f->GetPrevContinuation()->GetStyleContext()) {
7080 0 : f->AddStateBits(TEXT_STYLE_MATCHES_PREV_CONTINUATION);
7081 : }
7082 0 : } else if (framesToRemove) {
7083 0 : RemoveInFlows(framesToRemove, f);
7084 0 : framesToRemove = nsnull;
7085 : }
7086 0 : f = next;
7087 : }
7088 0 : NS_POSTCONDITION(!framesToRemove || (f && f->mContentOffset == end),
7089 : "How did we exit the loop if we null out framesToRemove if "
7090 : "!next || next->mContentOffset > end ?");
7091 0 : if (framesToRemove) {
7092 : // We are guaranteed that we exited the loop with f not null, per the
7093 : // postcondition above
7094 0 : RemoveInFlows(framesToRemove, f);
7095 : }
7096 :
7097 : #ifdef DEBUG
7098 0 : f = this;
7099 0 : PRInt32 iterations = 0;
7100 0 : while (f && iterations < 10) {
7101 0 : f->GetContentLength(); // Assert if negative length
7102 0 : f = static_cast<nsTextFrame*>(f->GetNextContinuation());
7103 0 : ++iterations;
7104 : }
7105 0 : f = this;
7106 0 : iterations = 0;
7107 0 : while (f && iterations < 10) {
7108 0 : f->GetContentLength(); // Assert if negative length
7109 0 : f = static_cast<nsTextFrame*>(f->GetPrevContinuation());
7110 0 : ++iterations;
7111 : }
7112 : #endif
7113 : }
7114 :
7115 : bool
7116 0 : nsTextFrame::IsFloatingFirstLetterChild() const
7117 : {
7118 0 : if (!(GetStateBits() & TEXT_FIRST_LETTER))
7119 0 : return false;
7120 0 : nsIFrame* frame = GetParent();
7121 0 : if (!frame || frame->GetType() != nsGkAtoms::letterFrame)
7122 0 : return false;
7123 0 : return frame->GetStyleDisplay()->IsFloating();
7124 : }
7125 :
7126 : struct NewlineProperty {
7127 : PRInt32 mStartOffset;
7128 : // The offset of the first \n after mStartOffset, or -1 if there is none
7129 : PRInt32 mNewlineOffset;
7130 :
7131 0 : static void Destroy(void* aObject, nsIAtom* aPropertyName,
7132 : void* aPropertyValue, void* aData)
7133 : {
7134 : delete static_cast<NewlineProperty*>(aPropertyValue);
7135 0 : }
7136 : };
7137 :
7138 : NS_IMETHODIMP
7139 0 : nsTextFrame::Reflow(nsPresContext* aPresContext,
7140 : nsHTMLReflowMetrics& aMetrics,
7141 : const nsHTMLReflowState& aReflowState,
7142 : nsReflowStatus& aStatus)
7143 : {
7144 0 : DO_GLOBAL_REFLOW_COUNT("nsTextFrame");
7145 0 : DISPLAY_REFLOW(aPresContext, this, aReflowState, aMetrics, aStatus);
7146 :
7147 : // XXX If there's no line layout, we shouldn't even have created this
7148 : // frame. This may happen if, for example, this is text inside a table
7149 : // but not inside a cell. For now, just don't reflow.
7150 0 : if (!aReflowState.mLineLayout) {
7151 0 : ClearMetrics(aMetrics);
7152 0 : aStatus = NS_FRAME_COMPLETE;
7153 0 : return NS_OK;
7154 : }
7155 :
7156 : ReflowText(*aReflowState.mLineLayout, aReflowState.availableWidth,
7157 : aReflowState.rendContext, aReflowState.mFlags.mBlinks,
7158 0 : aMetrics, aStatus);
7159 :
7160 0 : NS_FRAME_SET_TRUNCATION(aStatus, aReflowState, aMetrics);
7161 0 : return NS_OK;
7162 : }
7163 :
7164 : #ifdef ACCESSIBILITY
7165 : /**
7166 : * Notifies accessibility about text reflow. Used by nsTextFrame::ReflowText.
7167 : */
7168 : class NS_STACK_CLASS ReflowTextA11yNotifier
7169 : {
7170 : public:
7171 0 : ReflowTextA11yNotifier(nsPresContext* aPresContext, nsIContent* aContent) :
7172 0 : mContent(aContent), mPresContext(aPresContext)
7173 : {
7174 0 : }
7175 0 : ~ReflowTextA11yNotifier()
7176 : {
7177 0 : nsAccessibilityService* accService = nsIPresShell::AccService();
7178 0 : if (accService) {
7179 0 : accService->UpdateText(mPresContext->PresShell(), mContent);
7180 : }
7181 0 : }
7182 : private:
7183 : ReflowTextA11yNotifier();
7184 : ReflowTextA11yNotifier(const ReflowTextA11yNotifier&);
7185 : ReflowTextA11yNotifier& operator =(const ReflowTextA11yNotifier&);
7186 :
7187 : nsIContent* mContent;
7188 : nsPresContext* mPresContext;
7189 : };
7190 : #endif
7191 :
7192 : void
7193 0 : nsTextFrame::ReflowText(nsLineLayout& aLineLayout, nscoord aAvailableWidth,
7194 : nsRenderingContext* aRenderingContext,
7195 : bool aShouldBlink,
7196 : nsHTMLReflowMetrics& aMetrics,
7197 : nsReflowStatus& aStatus)
7198 : {
7199 : #ifdef NOISY_REFLOW
7200 : ListTag(stdout);
7201 : printf(": BeginReflow: availableWidth=%d\n", aAvailableWidth);
7202 : #endif
7203 :
7204 0 : nsPresContext* presContext = PresContext();
7205 :
7206 : #ifdef ACCESSIBILITY
7207 : // Schedule the update of accessible tree since rendered text might be changed.
7208 0 : ReflowTextA11yNotifier(presContext, mContent);
7209 : #endif
7210 :
7211 : /////////////////////////////////////////////////////////////////////
7212 : // Set up flags and clear out state
7213 : /////////////////////////////////////////////////////////////////////
7214 :
7215 : // Clear out the reflow state flags in mState (without destroying
7216 : // the TEXT_BLINK_ON bit). We also clear the whitespace flags because this
7217 : // can change whether the frame maps whitespace-only text or not.
7218 0 : RemoveStateBits(TEXT_REFLOW_FLAGS | TEXT_WHITESPACE_FLAGS);
7219 :
7220 : // Temporarily map all possible content while we construct our new textrun.
7221 : // so that when doing reflow our styles prevail over any part of the
7222 : // textrun we look at. Note that next-in-flows may be mapping the same
7223 : // content; gfxTextRun construction logic will ensure that we take priority.
7224 0 : PRInt32 maxContentLength = GetInFlowContentLength();
7225 :
7226 : // We don't need to reflow if there is no content.
7227 0 : if (!maxContentLength) {
7228 0 : ClearMetrics(aMetrics);
7229 0 : aStatus = NS_FRAME_COMPLETE;
7230 0 : return;
7231 : }
7232 :
7233 0 : if (aShouldBlink) {
7234 0 : if (0 == (mState & TEXT_BLINK_ON)) {
7235 0 : mState |= TEXT_BLINK_ON;
7236 0 : nsBlinkTimer::AddBlinkFrame(presContext, this);
7237 : }
7238 : }
7239 : else {
7240 0 : if (0 != (mState & TEXT_BLINK_ON)) {
7241 0 : mState &= ~TEXT_BLINK_ON;
7242 0 : nsBlinkTimer::RemoveBlinkFrame(this);
7243 : }
7244 : }
7245 :
7246 : #ifdef NOISY_BIDI
7247 : printf("Reflowed textframe\n");
7248 : #endif
7249 :
7250 0 : const nsStyleText* textStyle = GetStyleText();
7251 :
7252 0 : bool atStartOfLine = aLineLayout.LineAtStart();
7253 0 : if (atStartOfLine) {
7254 0 : AddStateBits(TEXT_START_OF_LINE);
7255 : }
7256 :
7257 : PRUint32 flowEndInTextRun;
7258 0 : nsIFrame* lineContainer = aLineLayout.GetLineContainerFrame();
7259 0 : gfxContext* ctx = aRenderingContext->ThebesContext();
7260 0 : const nsTextFragment* frag = mContent->GetText();
7261 :
7262 : // DOM offsets of the text range we need to measure, after trimming
7263 : // whitespace, restricting to first-letter, and restricting preformatted text
7264 : // to nearest newline
7265 0 : PRInt32 length = maxContentLength;
7266 0 : PRInt32 offset = GetContentOffset();
7267 :
7268 : // Restrict preformatted text to the nearest newline
7269 0 : PRInt32 newLineOffset = -1; // this will be -1 or a content offset
7270 0 : PRInt32 contentNewLineOffset = -1;
7271 : // Pointer to the nsGkAtoms::newline set on this frame's element
7272 0 : NewlineProperty* cachedNewlineOffset = nsnull;
7273 0 : if (textStyle->NewlineIsSignificant()) {
7274 : cachedNewlineOffset =
7275 0 : static_cast<NewlineProperty*>(mContent->GetProperty(nsGkAtoms::newline));
7276 0 : if (cachedNewlineOffset && cachedNewlineOffset->mStartOffset <= offset &&
7277 : (cachedNewlineOffset->mNewlineOffset == -1 ||
7278 : cachedNewlineOffset->mNewlineOffset >= offset)) {
7279 0 : contentNewLineOffset = cachedNewlineOffset->mNewlineOffset;
7280 : } else {
7281 : contentNewLineOffset = FindChar(frag, offset,
7282 0 : mContent->TextLength() - offset, '\n');
7283 : }
7284 0 : if (contentNewLineOffset < offset + length) {
7285 : /*
7286 : The new line offset could be outside this frame if the frame has been
7287 : split by bidi resolution. In that case we won't use it in this reflow
7288 : (newLineOffset will remain -1), but we will still cache it in mContent
7289 : */
7290 0 : newLineOffset = contentNewLineOffset;
7291 : }
7292 0 : if (newLineOffset >= 0) {
7293 0 : length = newLineOffset + 1 - offset;
7294 : }
7295 : }
7296 0 : if (atStartOfLine && !textStyle->WhiteSpaceIsSignificant()) {
7297 : // Skip leading whitespace. Make sure we don't skip a 'pre-line'
7298 : // newline if there is one.
7299 0 : PRInt32 skipLength = newLineOffset >= 0 ? length - 1 : length;
7300 : PRInt32 whitespaceCount =
7301 0 : GetTrimmableWhitespaceCount(frag, offset, skipLength, 1);
7302 0 : offset += whitespaceCount;
7303 0 : length -= whitespaceCount;
7304 : }
7305 :
7306 0 : bool completedFirstLetter = false;
7307 : // Layout dependent styles are a problem because we need to reconstruct
7308 : // the gfxTextRun based on our layout.
7309 0 : if (aLineLayout.GetInFirstLetter() || aLineLayout.GetInFirstLine()) {
7310 : SetLength(maxContentLength, &aLineLayout,
7311 0 : ALLOW_FRAME_CREATION_AND_DESTRUCTION);
7312 :
7313 0 : if (aLineLayout.GetInFirstLetter()) {
7314 : // floating first-letter boundaries are significant in textrun
7315 : // construction, so clear the textrun out every time we hit a first-letter
7316 : // and have changed our length (which controls the first-letter boundary)
7317 0 : ClearTextRuns();
7318 : // Find the length of the first-letter. We need a textrun for this.
7319 : // REVIEW: maybe-bogus inflation should be ok (fixed below)
7320 : gfxSkipCharsIterator iter =
7321 : EnsureTextRun(nsTextFrame::eInflated, GetFontSizeInflation(), ctx,
7322 0 : lineContainer, aLineLayout.GetLine(),
7323 0 : &flowEndInTextRun);
7324 :
7325 0 : if (mTextRun) {
7326 0 : PRInt32 firstLetterLength = length;
7327 0 : if (aLineLayout.GetFirstLetterStyleOK()) {
7328 : completedFirstLetter =
7329 0 : FindFirstLetterRange(frag, mTextRun, offset, iter, &firstLetterLength);
7330 0 : if (newLineOffset >= 0) {
7331 : // Don't allow a preformatted newline to be part of a first-letter.
7332 0 : firstLetterLength = NS_MIN(firstLetterLength, length - 1);
7333 0 : if (length == 1) {
7334 : // There is no text to be consumed by the first-letter before the
7335 : // preformatted newline. Note that the first letter is therefore
7336 : // complete (FindFirstLetterRange will have returned false).
7337 0 : completedFirstLetter = true;
7338 : }
7339 : }
7340 : } else {
7341 : // We're in a first-letter frame's first in flow, so if there
7342 : // was a first-letter, we'd be it. However, for one reason
7343 : // or another (e.g., preformatted line break before this text),
7344 : // we're not actually supposed to have first-letter style. So
7345 : // just make a zero-length first-letter.
7346 0 : firstLetterLength = 0;
7347 0 : completedFirstLetter = true;
7348 : }
7349 0 : length = firstLetterLength;
7350 0 : if (length) {
7351 0 : AddStateBits(TEXT_FIRST_LETTER);
7352 : }
7353 : // Change this frame's length to the first-letter length right now
7354 : // so that when we rebuild the textrun it will be built with the
7355 : // right first-letter boundary
7356 0 : SetLength(offset + length - GetContentOffset(), &aLineLayout,
7357 0 : ALLOW_FRAME_CREATION_AND_DESTRUCTION);
7358 : // Ensure that the textrun will be rebuilt
7359 0 : ClearTextRuns();
7360 : }
7361 : }
7362 : }
7363 :
7364 : float fontSizeInflation = nsLayoutUtils::FontSizeInflationFor(this,
7365 0 : nsLayoutUtils::eInReflow);
7366 :
7367 0 : if (fontSizeInflation != GetFontSizeInflation()) {
7368 : // FIXME: Ideally, if we already have a text run, we'd move it to be
7369 : // the uninflated text run.
7370 0 : ClearTextRun(nsnull, nsTextFrame::eInflated);
7371 : }
7372 :
7373 : gfxSkipCharsIterator iter =
7374 : EnsureTextRun(nsTextFrame::eInflated, fontSizeInflation, ctx,
7375 0 : lineContainer, aLineLayout.GetLine(), &flowEndInTextRun);
7376 :
7377 0 : NS_ABORT_IF_FALSE(GetFontSizeInflation() == fontSizeInflation,
7378 : "EnsureTextRun should have set font size inflation");
7379 :
7380 0 : if (mTextRun && iter.GetOriginalEnd() < offset + length) {
7381 : // The textrun does not map enough text for this frame. This can happen
7382 : // when the textrun was ended in the middle of a text node because a
7383 : // preformatted newline was encountered, and prev-in-flow frames have
7384 : // consumed all the text of the textrun. We need a new textrun.
7385 0 : ClearTextRuns();
7386 : iter = EnsureTextRun(nsTextFrame::eInflated, fontSizeInflation, ctx,
7387 0 : lineContainer, aLineLayout.GetLine(),
7388 0 : &flowEndInTextRun);
7389 : }
7390 :
7391 0 : if (!mTextRun) {
7392 0 : ClearMetrics(aMetrics);
7393 0 : aStatus = NS_FRAME_COMPLETE;
7394 0 : return;
7395 : }
7396 :
7397 0 : NS_ASSERTION(gfxSkipCharsIterator(iter).ConvertOriginalToSkipped(offset + length)
7398 : <= mTextRun->GetLength(),
7399 : "Text run does not map enough text for our reflow");
7400 :
7401 : /////////////////////////////////////////////////////////////////////
7402 : // See how much text should belong to this text frame, and measure it
7403 : /////////////////////////////////////////////////////////////////////
7404 :
7405 0 : iter.SetOriginalOffset(offset);
7406 0 : nscoord xOffsetForTabs = (mTextRun->GetFlags() & nsTextFrameUtils::TEXT_HAS_TAB) ?
7407 0 : (aLineLayout.GetCurrentFrameXDistanceFromBlock() -
7408 : lineContainer->GetUsedBorderAndPadding().left)
7409 0 : : -1;
7410 : PropertyProvider provider(mTextRun, textStyle, frag, this, iter, length,
7411 0 : lineContainer, xOffsetForTabs, nsTextFrame::eInflated);
7412 :
7413 0 : PRUint32 transformedOffset = provider.GetStart().GetSkippedOffset();
7414 :
7415 : // The metrics for the text go in here
7416 0 : gfxTextRun::Metrics textMetrics;
7417 0 : gfxFont::BoundingBoxType boundingBoxType = IsFloatingFirstLetterChild() ?
7418 : gfxFont::TIGHT_HINTED_OUTLINE_EXTENTS :
7419 0 : gfxFont::LOOSE_INK_EXTENTS;
7420 0 : NS_ASSERTION(!(NS_REFLOW_CALC_BOUNDING_METRICS & aMetrics.mFlags),
7421 : "We shouldn't be passed NS_REFLOW_CALC_BOUNDING_METRICS anymore");
7422 :
7423 0 : PRInt32 limitLength = length;
7424 0 : PRInt32 forceBreak = aLineLayout.GetForcedBreakPosition(mContent);
7425 0 : bool forceBreakAfter = false;
7426 0 : if (forceBreak >= offset + length) {
7427 0 : forceBreakAfter = forceBreak == offset + length;
7428 : // The break is not within the text considered for this textframe.
7429 0 : forceBreak = -1;
7430 : }
7431 0 : if (forceBreak >= 0) {
7432 0 : limitLength = forceBreak - offset;
7433 0 : NS_ASSERTION(limitLength >= 0, "Weird break found!");
7434 : }
7435 : // This is the heart of text reflow right here! We don't know where
7436 : // to break, so we need to see how much text fits in the available width.
7437 : PRUint32 transformedLength;
7438 0 : if (offset + limitLength >= PRInt32(frag->GetLength())) {
7439 0 : NS_ASSERTION(offset + limitLength == PRInt32(frag->GetLength()),
7440 : "Content offset/length out of bounds");
7441 0 : NS_ASSERTION(flowEndInTextRun >= transformedOffset,
7442 : "Negative flow length?");
7443 0 : transformedLength = flowEndInTextRun - transformedOffset;
7444 : } else {
7445 : // we're not looking at all the content, so we need to compute the
7446 : // length of the transformed substring we're looking at
7447 0 : gfxSkipCharsIterator iter(provider.GetStart());
7448 0 : iter.SetOriginalOffset(offset + limitLength);
7449 0 : transformedLength = iter.GetSkippedOffset() - transformedOffset;
7450 : }
7451 0 : PRUint32 transformedLastBreak = 0;
7452 : bool usedHyphenation;
7453 0 : gfxFloat trimmedWidth = 0;
7454 0 : gfxFloat availWidth = aAvailableWidth;
7455 0 : bool canTrimTrailingWhitespace = !textStyle->WhiteSpaceIsSignificant();
7456 : PRInt32 unusedOffset;
7457 : gfxBreakPriority breakPriority;
7458 0 : aLineLayout.GetLastOptionalBreakPosition(&unusedOffset, &breakPriority);
7459 : PRUint32 transformedCharsFit =
7460 : mTextRun->BreakAndMeasureText(transformedOffset, transformedLength,
7461 0 : (GetStateBits() & TEXT_START_OF_LINE) != 0,
7462 : availWidth,
7463 0 : &provider, !aLineLayout.LineIsBreakable(),
7464 : canTrimTrailingWhitespace ? &trimmedWidth : nsnull,
7465 : &textMetrics, boundingBoxType, ctx,
7466 : &usedHyphenation, &transformedLastBreak,
7467 0 : textStyle->WordCanWrap(), &breakPriority);
7468 0 : if (!length && !textMetrics.mAscent && !textMetrics.mDescent) {
7469 : // If we're measuring a zero-length piece of text, update
7470 : // the height manually.
7471 0 : nsFontMetrics* fm = provider.GetFontMetrics();
7472 0 : if (fm) {
7473 0 : textMetrics.mAscent = gfxFloat(fm->MaxAscent());
7474 0 : textMetrics.mDescent = gfxFloat(fm->MaxDescent());
7475 : }
7476 : }
7477 : // The "end" iterator points to the first character after the string mapped
7478 : // by this frame. Basically, its original-string offset is offset+charsFit
7479 : // after we've computed charsFit.
7480 0 : gfxSkipCharsIterator end(provider.GetEndHint());
7481 0 : end.SetSkippedOffset(transformedOffset + transformedCharsFit);
7482 0 : PRInt32 charsFit = end.GetOriginalOffset() - offset;
7483 0 : if (offset + charsFit == newLineOffset) {
7484 : // We broke before a trailing preformatted '\n'. The newline should
7485 : // be assigned to this frame. Note that newLineOffset will be -1 if
7486 : // there was no preformatted newline, so we wouldn't get here in that
7487 : // case.
7488 0 : ++charsFit;
7489 : }
7490 : // That might have taken us beyond our assigned content range (because
7491 : // we might have advanced over some skipped chars that extend outside
7492 : // this frame), so get back in.
7493 0 : PRInt32 lastBreak = -1;
7494 0 : if (charsFit >= limitLength) {
7495 0 : charsFit = limitLength;
7496 0 : if (transformedLastBreak != PR_UINT32_MAX) {
7497 : // lastBreak is needed.
7498 : // This may set lastBreak greater than 'length', but that's OK
7499 0 : lastBreak = end.ConvertSkippedToOriginal(transformedOffset + transformedLastBreak);
7500 : }
7501 0 : end.SetOriginalOffset(offset + charsFit);
7502 : // If we were forced to fit, and the break position is after a soft hyphen,
7503 : // note that this is a hyphenation break.
7504 0 : if ((forceBreak >= 0 || forceBreakAfter) &&
7505 0 : HasSoftHyphenBefore(frag, mTextRun, offset, end)) {
7506 0 : usedHyphenation = true;
7507 : }
7508 : }
7509 0 : if (usedHyphenation) {
7510 : // Fix up metrics to include hyphen
7511 0 : AddHyphenToMetrics(this, mTextRun, &textMetrics, boundingBoxType, ctx);
7512 0 : AddStateBits(TEXT_HYPHEN_BREAK | TEXT_HAS_NONCOLLAPSED_CHARACTERS);
7513 : }
7514 :
7515 0 : gfxFloat trimmableWidth = 0;
7516 0 : bool brokeText = forceBreak >= 0 || transformedCharsFit < transformedLength;
7517 0 : if (canTrimTrailingWhitespace) {
7518 : // Optimization: if we trimmed trailing whitespace, and we can be sure
7519 : // this frame will be at the end of the line, then leave it trimmed off.
7520 : // Otherwise we have to undo the trimming, in case we're not at the end of
7521 : // the line. (If we actually do end up at the end of the line, we'll have
7522 : // to trim it off again in TrimTrailingWhiteSpace, and we'd like to avoid
7523 : // having to re-do it.)
7524 0 : if (brokeText) {
7525 : // We're definitely going to break so our trailing whitespace should
7526 : // definitely be timmed. Record that we've already done it.
7527 0 : AddStateBits(TEXT_TRIMMED_TRAILING_WHITESPACE);
7528 : } else {
7529 : // We might not be at the end of the line. (Note that even if this frame
7530 : // ends in breakable whitespace, it might not be at the end of the line
7531 : // because it might be followed by breakable, but preformatted, whitespace.)
7532 : // Undo the trimming.
7533 0 : textMetrics.mAdvanceWidth += trimmedWidth;
7534 0 : trimmableWidth = trimmedWidth;
7535 0 : if (mTextRun->IsRightToLeft()) {
7536 : // Space comes before text, so the bounding box is moved to the
7537 : // right by trimmdWidth
7538 0 : textMetrics.mBoundingBox.MoveBy(gfxPoint(trimmedWidth, 0));
7539 : }
7540 : }
7541 : }
7542 :
7543 0 : if (!brokeText && lastBreak >= 0) {
7544 : // Since everything fit and no break was forced,
7545 : // record the last break opportunity
7546 0 : NS_ASSERTION(textMetrics.mAdvanceWidth - trimmableWidth <= aAvailableWidth,
7547 : "If the text doesn't fit, and we have a break opportunity, why didn't MeasureText use it?");
7548 0 : aLineLayout.NotifyOptionalBreakPosition(mContent, lastBreak, true, breakPriority);
7549 : }
7550 :
7551 0 : PRInt32 contentLength = offset + charsFit - GetContentOffset();
7552 :
7553 : /////////////////////////////////////////////////////////////////////
7554 : // Compute output metrics
7555 : /////////////////////////////////////////////////////////////////////
7556 :
7557 : // first-letter frames should use the tight bounding box metrics for ascent/descent
7558 : // for good drop-cap effects
7559 0 : if (GetStateBits() & TEXT_FIRST_LETTER) {
7560 0 : textMetrics.mAscent = NS_MAX(gfxFloat(0.0), -textMetrics.mBoundingBox.Y());
7561 0 : textMetrics.mDescent = NS_MAX(gfxFloat(0.0), textMetrics.mBoundingBox.YMost());
7562 : }
7563 :
7564 : // Setup metrics for caller
7565 : // Disallow negative widths
7566 0 : aMetrics.width = NSToCoordCeil(NS_MAX(gfxFloat(0.0), textMetrics.mAdvanceWidth));
7567 :
7568 0 : if (transformedCharsFit == 0 && !usedHyphenation) {
7569 0 : aMetrics.ascent = 0;
7570 0 : aMetrics.height = 0;
7571 0 : } else if (boundingBoxType != gfxFont::LOOSE_INK_EXTENTS) {
7572 : // Use actual text metrics for floating first letter frame.
7573 0 : aMetrics.ascent = NSToCoordCeil(textMetrics.mAscent);
7574 0 : aMetrics.height = aMetrics.ascent + NSToCoordCeil(textMetrics.mDescent);
7575 : } else {
7576 : // Otherwise, ascent should contain the overline drawable area.
7577 : // And also descent should contain the underline drawable area.
7578 : // nsFontMetrics::GetMaxAscent/GetMaxDescent contains them.
7579 0 : nsFontMetrics* fm = provider.GetFontMetrics();
7580 0 : nscoord fontAscent = fm->MaxAscent();
7581 0 : nscoord fontDescent = fm->MaxDescent();
7582 0 : aMetrics.ascent = NS_MAX(NSToCoordCeil(textMetrics.mAscent), fontAscent);
7583 0 : nscoord descent = NS_MAX(NSToCoordCeil(textMetrics.mDescent), fontDescent);
7584 0 : aMetrics.height = aMetrics.ascent + descent;
7585 : }
7586 :
7587 0 : NS_ASSERTION(aMetrics.ascent >= 0, "Negative ascent???");
7588 0 : NS_ASSERTION(aMetrics.height - aMetrics.ascent >= 0, "Negative descent???");
7589 :
7590 0 : mAscent = aMetrics.ascent;
7591 :
7592 : // Handle text that runs outside its normal bounds.
7593 0 : nsRect boundingBox = RoundOut(textMetrics.mBoundingBox) + nsPoint(0, mAscent);
7594 0 : aMetrics.SetOverflowAreasToDesiredBounds();
7595 0 : aMetrics.VisualOverflow().UnionRect(aMetrics.VisualOverflow(), boundingBox);
7596 :
7597 : // When we have text decorations, we don't need to compute their overflow now
7598 : // because we're guaranteed to do it later
7599 : // (see nsLineLayout::RelativePositionFrames)
7600 0 : UnionAdditionalOverflow(presContext, *aLineLayout.GetLineContainerRS(),
7601 0 : provider, &aMetrics.VisualOverflow(), false);
7602 :
7603 : /////////////////////////////////////////////////////////////////////
7604 : // Clean up, update state
7605 : /////////////////////////////////////////////////////////////////////
7606 :
7607 : // If all our characters are discarded or collapsed, then trimmable width
7608 : // from the last textframe should be preserved. Otherwise the trimmable width
7609 : // from this textframe overrides. (Currently in CSS trimmable width can be
7610 : // at most one space so there's no way for trimmable width from a previous
7611 : // frame to accumulate with trimmable width from this frame.)
7612 0 : if (transformedCharsFit > 0) {
7613 0 : aLineLayout.SetTrimmableWidth(NSToCoordFloor(trimmableWidth));
7614 0 : AddStateBits(TEXT_HAS_NONCOLLAPSED_CHARACTERS);
7615 : }
7616 0 : if (charsFit > 0 && charsFit == length &&
7617 : textStyle->mHyphens != NS_STYLE_HYPHENS_NONE &&
7618 0 : HasSoftHyphenBefore(frag, mTextRun, offset, end)) {
7619 : // Record a potential break after final soft hyphen
7620 : aLineLayout.NotifyOptionalBreakPosition(mContent, offset + length,
7621 0 : textMetrics.mAdvanceWidth + provider.GetHyphenWidth() <= availWidth,
7622 0 : eNormalBreak);
7623 : }
7624 0 : bool breakAfter = forceBreakAfter;
7625 : // length == 0 means either the text is empty or it's all collapsed away
7626 0 : bool emptyTextAtStartOfLine = atStartOfLine && length == 0;
7627 0 : if (!breakAfter && charsFit == length && !emptyTextAtStartOfLine &&
7628 0 : transformedOffset + transformedLength == mTextRun->GetLength() &&
7629 0 : (mTextRun->GetFlags() & nsTextFrameUtils::TEXT_HAS_TRAILING_BREAK)) {
7630 : // We placed all the text in the textrun and we have a break opportunity at
7631 : // the end of the textrun. We need to record it because the following
7632 : // content may not care about nsLineBreaker.
7633 :
7634 : // Note that because we didn't break, we can be sure that (thanks to the
7635 : // code up above) textMetrics.mAdvanceWidth includes the width of any
7636 : // trailing whitespace. So we need to subtract trimmableWidth here
7637 : // because if we did break at this point, that much width would be trimmed.
7638 0 : if (textMetrics.mAdvanceWidth - trimmableWidth > availWidth) {
7639 0 : breakAfter = true;
7640 : } else {
7641 : aLineLayout.NotifyOptionalBreakPosition(mContent, offset + length,
7642 0 : true, eNormalBreak);
7643 : }
7644 : }
7645 :
7646 : // Compute reflow status
7647 : aStatus = contentLength == maxContentLength
7648 0 : ? NS_FRAME_COMPLETE : NS_FRAME_NOT_COMPLETE;
7649 :
7650 0 : if (charsFit == 0 && length > 0 && !usedHyphenation) {
7651 : // Couldn't place any text
7652 0 : aStatus = NS_INLINE_LINE_BREAK_BEFORE();
7653 0 : } else if (contentLength > 0 && mContentOffset + contentLength - 1 == newLineOffset) {
7654 : // Ends in \n
7655 0 : aStatus = NS_INLINE_LINE_BREAK_AFTER(aStatus);
7656 0 : aLineLayout.SetLineEndsInBR(true);
7657 0 : } else if (breakAfter) {
7658 0 : aStatus = NS_INLINE_LINE_BREAK_AFTER(aStatus);
7659 : }
7660 0 : if (completedFirstLetter) {
7661 0 : aLineLayout.SetFirstLetterStyleOK(false);
7662 0 : aStatus |= NS_INLINE_BREAK_FIRST_LETTER_COMPLETE;
7663 : }
7664 :
7665 : // Updated the cached NewlineProperty, or delete it.
7666 0 : if (contentLength < maxContentLength &&
7667 0 : textStyle->NewlineIsSignificant() &&
7668 : (contentNewLineOffset < 0 ||
7669 : mContentOffset + contentLength <= contentNewLineOffset)) {
7670 0 : if (!cachedNewlineOffset) {
7671 0 : cachedNewlineOffset = new NewlineProperty;
7672 0 : if (NS_FAILED(mContent->SetProperty(nsGkAtoms::newline, cachedNewlineOffset,
7673 : NewlineProperty::Destroy))) {
7674 : delete cachedNewlineOffset;
7675 0 : cachedNewlineOffset = nsnull;
7676 : }
7677 : }
7678 0 : if (cachedNewlineOffset) {
7679 0 : cachedNewlineOffset->mStartOffset = offset;
7680 0 : cachedNewlineOffset->mNewlineOffset = contentNewLineOffset;
7681 : }
7682 0 : } else if (cachedNewlineOffset) {
7683 0 : mContent->DeleteProperty(nsGkAtoms::newline);
7684 : }
7685 :
7686 : // Compute space and letter counts for justification, if required
7687 0 : if (!textStyle->WhiteSpaceIsSignificant() &&
7688 0 : (lineContainer->GetStyleText()->mTextAlign == NS_STYLE_TEXT_ALIGN_JUSTIFY ||
7689 0 : lineContainer->GetStyleText()->mTextAlignLast == NS_STYLE_TEXT_ALIGN_JUSTIFY)) {
7690 0 : AddStateBits(TEXT_JUSTIFICATION_ENABLED); // This will include a space for trailing whitespace, if any is present.
7691 : // This is corrected for in nsLineLayout::TrimWhiteSpaceIn.
7692 : PRInt32 numJustifiableCharacters =
7693 0 : provider.ComputeJustifiableCharacters(offset, charsFit);
7694 :
7695 0 : NS_ASSERTION(numJustifiableCharacters <= charsFit,
7696 : "Bad justifiable character count");
7697 : aLineLayout.SetTextJustificationWeights(numJustifiableCharacters,
7698 0 : charsFit - numJustifiableCharacters);
7699 : }
7700 :
7701 0 : SetLength(contentLength, &aLineLayout, ALLOW_FRAME_CREATION_AND_DESTRUCTION);
7702 :
7703 0 : Invalidate(aMetrics.VisualOverflow());
7704 :
7705 : #ifdef NOISY_REFLOW
7706 : ListTag(stdout);
7707 : printf(": desiredSize=%d,%d(b=%d) status=%x\n",
7708 : aMetrics.width, aMetrics.height, aMetrics.ascent,
7709 : aStatus);
7710 : #endif
7711 : }
7712 :
7713 : /* virtual */ bool
7714 0 : nsTextFrame::CanContinueTextRun() const
7715 : {
7716 : // We can continue a text run through a text frame
7717 0 : return true;
7718 : }
7719 :
7720 : nsTextFrame::TrimOutput
7721 0 : nsTextFrame::TrimTrailingWhiteSpace(nsRenderingContext* aRC)
7722 : {
7723 : TrimOutput result;
7724 0 : result.mChanged = false;
7725 0 : result.mLastCharIsJustifiable = false;
7726 0 : result.mDeltaWidth = 0;
7727 :
7728 0 : AddStateBits(TEXT_END_OF_LINE);
7729 :
7730 0 : PRInt32 contentLength = GetContentLength();
7731 0 : if (!contentLength)
7732 0 : return result;
7733 :
7734 0 : gfxContext* ctx = aRC->ThebesContext();
7735 : gfxSkipCharsIterator start =
7736 0 : EnsureTextRun(nsTextFrame::eInflated, GetFontSizeInflation(), ctx);
7737 0 : NS_ENSURE_TRUE(mTextRun, result);
7738 :
7739 0 : PRUint32 trimmedStart = start.GetSkippedOffset();
7740 :
7741 0 : const nsTextFragment* frag = mContent->GetText();
7742 0 : TrimmedOffsets trimmed = GetTrimmedOffsets(frag, true);
7743 0 : gfxSkipCharsIterator trimmedEndIter = start;
7744 0 : const nsStyleText* textStyle = GetStyleText();
7745 0 : gfxFloat delta = 0;
7746 0 : PRUint32 trimmedEnd = trimmedEndIter.ConvertOriginalToSkipped(trimmed.GetEnd());
7747 :
7748 0 : if (GetStateBits() & TEXT_TRIMMED_TRAILING_WHITESPACE) {
7749 : // We pre-trimmed this frame, so the last character is justifiable
7750 0 : result.mLastCharIsJustifiable = true;
7751 0 : } else if (trimmed.GetEnd() < GetContentEnd()) {
7752 0 : gfxSkipCharsIterator end = trimmedEndIter;
7753 0 : PRUint32 endOffset = end.ConvertOriginalToSkipped(GetContentOffset() + contentLength);
7754 0 : if (trimmedEnd < endOffset) {
7755 : // We can't be dealing with tabs here ... they wouldn't be trimmed. So it's
7756 : // OK to pass null for the line container.
7757 : PropertyProvider provider(mTextRun, textStyle, frag, this, start, contentLength,
7758 0 : nsnull, 0, nsTextFrame::eInflated);
7759 0 : delta = mTextRun->GetAdvanceWidth(trimmedEnd, endOffset - trimmedEnd, &provider);
7760 : // non-compressed whitespace being skipped at end of line -> justifiable
7761 : // XXX should we actually *count* justifiable characters that should be
7762 : // removed from the overall count? I think so...
7763 0 : result.mLastCharIsJustifiable = true;
7764 0 : result.mChanged = true;
7765 : }
7766 : }
7767 :
7768 0 : if (!result.mLastCharIsJustifiable &&
7769 0 : (GetStateBits() & TEXT_JUSTIFICATION_ENABLED)) {
7770 : // Check if any character in the last cluster is justifiable
7771 : PropertyProvider provider(mTextRun, textStyle, frag, this, start, contentLength,
7772 0 : nsnull, 0, nsTextFrame::eInflated);
7773 0 : bool isCJK = IsChineseOrJapanese(this);
7774 0 : gfxSkipCharsIterator justificationStart(start), justificationEnd(trimmedEndIter);
7775 0 : provider.FindJustificationRange(&justificationStart, &justificationEnd);
7776 :
7777 : PRInt32 i;
7778 0 : for (i = justificationEnd.GetOriginalOffset(); i < trimmed.GetEnd(); ++i) {
7779 0 : if (IsJustifiableCharacter(frag, i, isCJK)) {
7780 0 : result.mLastCharIsJustifiable = true;
7781 : }
7782 : }
7783 : }
7784 :
7785 : gfxFloat advanceDelta;
7786 : mTextRun->SetLineBreaks(trimmedStart, trimmedEnd - trimmedStart,
7787 0 : (GetStateBits() & TEXT_START_OF_LINE) != 0, true,
7788 0 : &advanceDelta, ctx);
7789 0 : if (advanceDelta != 0) {
7790 0 : result.mChanged = true;
7791 : }
7792 :
7793 : // aDeltaWidth is *subtracted* from our width.
7794 : // If advanceDelta is positive then setting the line break made us longer,
7795 : // so aDeltaWidth could go negative.
7796 0 : result.mDeltaWidth = NSToCoordFloor(delta - advanceDelta);
7797 : // If aDeltaWidth goes negative, that means this frame might not actually fit
7798 : // anymore!!! We need higher level line layout to recover somehow.
7799 : // If it's because the frame has a soft hyphen that is now being displayed,
7800 : // this should actually be OK, because our reflow recorded the break
7801 : // opportunity that allowed the soft hyphen to be used, and we wouldn't
7802 : // have recorded the opportunity unless the hyphen fit (or was the first
7803 : // opportunity on the line).
7804 : // Otherwise this can/ really only happen when we have glyphs with special
7805 : // shapes at the end of lines, I think. Breaking inside a kerning pair won't
7806 : // do it because that would mean we broke inside this textrun, and
7807 : // BreakAndMeasureText should make sure the resulting shaped substring fits.
7808 : // Maybe if we passed a maxTextLength? But that only happens at direction
7809 : // changes (so we wouldn't kern across the boundary) or for first-letter
7810 : // (which always fits because it starts the line!).
7811 0 : NS_WARN_IF_FALSE(result.mDeltaWidth >= 0,
7812 : "Negative deltawidth, something odd is happening");
7813 :
7814 : #ifdef NOISY_TRIM
7815 : ListTag(stdout);
7816 : printf(": trim => %d\n", result.mDeltaWidth);
7817 : #endif
7818 0 : return result;
7819 : }
7820 :
7821 : nsOverflowAreas
7822 0 : nsTextFrame::RecomputeOverflow(const nsHTMLReflowState& aBlockReflowState)
7823 : {
7824 0 : nsRect bounds(nsPoint(0, 0), GetSize());
7825 0 : nsOverflowAreas result(bounds, bounds);
7826 :
7827 0 : gfxSkipCharsIterator iter = EnsureTextRun(nsTextFrame::eInflated);
7828 0 : if (!mTextRun)
7829 : return result;
7830 :
7831 0 : PropertyProvider provider(this, iter, nsTextFrame::eInflated);
7832 0 : provider.InitializeForDisplay(true);
7833 :
7834 : gfxTextRun::Metrics textMetrics =
7835 0 : mTextRun->MeasureText(provider.GetStart().GetSkippedOffset(),
7836 : ComputeTransformedLength(provider),
7837 : gfxFont::LOOSE_INK_EXTENTS, nsnull,
7838 0 : &provider);
7839 0 : nsRect &vis = result.VisualOverflow();
7840 0 : vis.UnionRect(vis, RoundOut(textMetrics.mBoundingBox) + nsPoint(0, mAscent));
7841 : UnionAdditionalOverflow(PresContext(), aBlockReflowState, provider,
7842 0 : &vis, true);
7843 : return result;
7844 : }
7845 0 : static PRUnichar TransformChar(const nsStyleText* aStyle, gfxTextRun* aTextRun,
7846 : PRUint32 aSkippedOffset, PRUnichar aChar)
7847 : {
7848 0 : if (aChar == '\n') {
7849 0 : return aStyle->NewlineIsSignificant() ? aChar : ' ';
7850 : }
7851 0 : switch (aStyle->mTextTransform) {
7852 : case NS_STYLE_TEXT_TRANSFORM_LOWERCASE:
7853 0 : aChar = ToLowerCase(aChar);
7854 0 : break;
7855 : case NS_STYLE_TEXT_TRANSFORM_UPPERCASE:
7856 0 : aChar = ToUpperCase(aChar);
7857 0 : break;
7858 : case NS_STYLE_TEXT_TRANSFORM_CAPITALIZE:
7859 0 : if (aTextRun->CanBreakLineBefore(aSkippedOffset)) {
7860 0 : aChar = ToTitleCase(aChar);
7861 : }
7862 0 : break;
7863 : }
7864 :
7865 0 : return aChar;
7866 : }
7867 :
7868 0 : nsresult nsTextFrame::GetRenderedText(nsAString* aAppendToString,
7869 : gfxSkipChars* aSkipChars,
7870 : gfxSkipCharsIterator* aSkipIter,
7871 : PRUint32 aSkippedStartOffset,
7872 : PRUint32 aSkippedMaxLength)
7873 : {
7874 : // The handling of aSkippedStartOffset and aSkippedMaxLength could be more efficient...
7875 0 : gfxSkipCharsBuilder skipCharsBuilder;
7876 : nsTextFrame* textFrame;
7877 0 : const nsTextFragment* textFrag = mContent->GetText();
7878 0 : PRUint32 keptCharsLength = 0;
7879 0 : PRUint32 validCharsLength = 0;
7880 :
7881 : // Build skipChars and copy text, for each text frame in this continuation block
7882 0 : for (textFrame = this; textFrame;
7883 0 : textFrame = static_cast<nsTextFrame*>(textFrame->GetNextContinuation())) {
7884 : // For each text frame continuation in this block ...
7885 :
7886 0 : if (textFrame->GetStateBits() & NS_FRAME_IS_DIRTY) {
7887 : // We don't trust dirty frames, expecially when computing rendered text.
7888 0 : break;
7889 : }
7890 :
7891 : // Ensure the text run and grab the gfxSkipCharsIterator for it
7892 : gfxSkipCharsIterator iter =
7893 0 : textFrame->EnsureTextRun(nsTextFrame::eInflated);
7894 0 : if (!textFrame->mTextRun)
7895 0 : return NS_ERROR_FAILURE;
7896 :
7897 : // Skip to the start of the text run, past ignored chars at start of line
7898 : // XXX In the future we may decide to trim extra spaces before a hard line
7899 : // break, in which case we need to accurately detect those sitations and
7900 : // call GetTrimmedOffsets() with true to trim whitespace at the line's end
7901 0 : TrimmedOffsets trimmedContentOffsets = textFrame->GetTrimmedOffsets(textFrag, false);
7902 0 : PRInt32 startOfLineSkipChars = trimmedContentOffsets.mStart - textFrame->mContentOffset;
7903 0 : if (startOfLineSkipChars > 0) {
7904 0 : skipCharsBuilder.SkipChars(startOfLineSkipChars);
7905 0 : iter.SetOriginalOffset(trimmedContentOffsets.mStart);
7906 : }
7907 :
7908 : // Keep and copy the appropriate chars withing the caller's requested range
7909 0 : const nsStyleText* textStyle = textFrame->GetStyleText();
7910 0 : while (iter.GetOriginalOffset() < trimmedContentOffsets.GetEnd() &&
7911 : keptCharsLength < aSkippedMaxLength) {
7912 : // For each original char from content text
7913 0 : if (iter.IsOriginalCharSkipped() || ++validCharsLength <= aSkippedStartOffset) {
7914 0 : skipCharsBuilder.SkipChar();
7915 : } else {
7916 0 : ++keptCharsLength;
7917 0 : skipCharsBuilder.KeepChar();
7918 0 : if (aAppendToString) {
7919 : aAppendToString->Append(
7920 : TransformChar(textStyle, textFrame->mTextRun, iter.GetSkippedOffset(),
7921 0 : textFrag->CharAt(iter.GetOriginalOffset())));
7922 : }
7923 : }
7924 0 : iter.AdvanceOriginal(1);
7925 : }
7926 0 : if (keptCharsLength >= aSkippedMaxLength) {
7927 0 : break; // Already past the end, don't build string or gfxSkipCharsIter anymore
7928 : }
7929 : }
7930 :
7931 0 : if (aSkipChars) {
7932 0 : aSkipChars->TakeFrom(&skipCharsBuilder); // Copy skipChars into aSkipChars
7933 0 : if (aSkipIter) {
7934 : // Caller must provide both pointers in order to retrieve a gfxSkipCharsIterator,
7935 : // because the gfxSkipCharsIterator holds a weak pointer to the gfxSkipCars.
7936 0 : *aSkipIter = gfxSkipCharsIterator(*aSkipChars, GetContentLength());
7937 : }
7938 : }
7939 :
7940 0 : return NS_OK;
7941 : }
7942 :
7943 : #ifdef DEBUG
7944 : // Translate the mapped content into a string that's printable
7945 : void
7946 0 : nsTextFrame::ToCString(nsCString& aBuf, PRInt32* aTotalContentLength) const
7947 : {
7948 : // Get the frames text content
7949 0 : const nsTextFragment* frag = mContent->GetText();
7950 0 : if (!frag) {
7951 0 : return;
7952 : }
7953 :
7954 : // Compute the total length of the text content.
7955 0 : *aTotalContentLength = frag->GetLength();
7956 :
7957 0 : PRInt32 contentLength = GetContentLength();
7958 : // Set current fragment and current fragment offset
7959 0 : if (0 == contentLength) {
7960 0 : return;
7961 : }
7962 0 : PRInt32 fragOffset = GetContentOffset();
7963 0 : PRInt32 n = fragOffset + contentLength;
7964 0 : while (fragOffset < n) {
7965 0 : PRUnichar ch = frag->CharAt(fragOffset++);
7966 0 : if (ch == '\r') {
7967 0 : aBuf.AppendLiteral("\\r");
7968 0 : } else if (ch == '\n') {
7969 0 : aBuf.AppendLiteral("\\n");
7970 0 : } else if (ch == '\t') {
7971 0 : aBuf.AppendLiteral("\\t");
7972 0 : } else if ((ch < ' ') || (ch >= 127)) {
7973 0 : aBuf.Append(nsPrintfCString("\\u%04x", ch));
7974 : } else {
7975 0 : aBuf.Append(ch);
7976 : }
7977 : }
7978 : }
7979 : #endif
7980 :
7981 : nsIAtom*
7982 0 : nsTextFrame::GetType() const
7983 : {
7984 0 : return nsGkAtoms::textFrame;
7985 : }
7986 :
7987 : /* virtual */ bool
7988 0 : nsTextFrame::IsEmpty()
7989 : {
7990 0 : NS_ASSERTION(!(mState & TEXT_IS_ONLY_WHITESPACE) ||
7991 : !(mState & TEXT_ISNOT_ONLY_WHITESPACE),
7992 : "Invalid state");
7993 :
7994 : // XXXldb Should this check compatibility mode as well???
7995 0 : const nsStyleText* textStyle = GetStyleText();
7996 0 : if (textStyle->WhiteSpaceIsSignificant()) {
7997 : // XXX shouldn't we return true if the length is zero?
7998 0 : return false;
7999 : }
8000 :
8001 0 : if (mState & TEXT_ISNOT_ONLY_WHITESPACE) {
8002 0 : return false;
8003 : }
8004 :
8005 0 : if (mState & TEXT_IS_ONLY_WHITESPACE) {
8006 0 : return true;
8007 : }
8008 :
8009 0 : bool isEmpty = IsAllWhitespace(mContent->GetText(),
8010 0 : textStyle->mWhiteSpace != NS_STYLE_WHITESPACE_PRE_LINE);
8011 0 : mState |= (isEmpty ? TEXT_IS_ONLY_WHITESPACE : TEXT_ISNOT_ONLY_WHITESPACE);
8012 0 : return isEmpty;
8013 : }
8014 :
8015 : #ifdef DEBUG
8016 : NS_IMETHODIMP
8017 0 : nsTextFrame::GetFrameName(nsAString& aResult) const
8018 : {
8019 0 : MakeFrameName(NS_LITERAL_STRING("Text"), aResult);
8020 : PRInt32 totalContentLength;
8021 0 : nsCAutoString tmp;
8022 0 : ToCString(tmp, &totalContentLength);
8023 0 : tmp.SetLength(NS_MIN(tmp.Length(), 50u));
8024 0 : aResult += NS_LITERAL_STRING("\"") + NS_ConvertASCIItoUTF16(tmp) + NS_LITERAL_STRING("\"");
8025 0 : return NS_OK;
8026 : }
8027 :
8028 : NS_IMETHODIMP_(nsFrameState)
8029 0 : nsTextFrame::GetDebugStateBits() const
8030 : {
8031 : // mask out our emptystate flags; those are just caches
8032 0 : return nsFrame::GetDebugStateBits() &
8033 0 : ~(TEXT_WHITESPACE_FLAGS | TEXT_REFLOW_FLAGS);
8034 : }
8035 :
8036 : NS_IMETHODIMP
8037 0 : nsTextFrame::List(FILE* out, PRInt32 aIndent) const
8038 : {
8039 : // Output the tag
8040 0 : IndentBy(out, aIndent);
8041 0 : ListTag(out);
8042 0 : if (HasView()) {
8043 0 : fprintf(out, " [view=%p]", static_cast<void*>(GetView()));
8044 : }
8045 0 : fprintf(out, " [run=%p]", static_cast<void*>(mTextRun));
8046 :
8047 : // Output the first/last content offset and prev/next in flow info
8048 0 : bool isComplete = PRUint32(GetContentEnd()) == GetContent()->TextLength();
8049 : fprintf(out, "[%d,%d,%c] ",
8050 : GetContentOffset(), GetContentLength(),
8051 0 : isComplete ? 'T':'F');
8052 :
8053 0 : if (GetNextSibling()) {
8054 0 : fprintf(out, " next=%p", static_cast<void*>(GetNextSibling()));
8055 : }
8056 0 : nsIFrame* prevContinuation = GetPrevContinuation();
8057 0 : if (nsnull != prevContinuation) {
8058 0 : fprintf(out, " prev-continuation=%p", static_cast<void*>(prevContinuation));
8059 : }
8060 0 : if (nsnull != mNextContinuation) {
8061 0 : fprintf(out, " next-continuation=%p", static_cast<void*>(mNextContinuation));
8062 : }
8063 :
8064 : // Output the rect and state
8065 0 : fprintf(out, " {%d,%d,%d,%d}", mRect.x, mRect.y, mRect.width, mRect.height);
8066 0 : fprintf(out, " [state=%016llx]", (unsigned long long)mState);
8067 0 : if (IsSelected()) {
8068 0 : fprintf(out, " SELECTED");
8069 : }
8070 0 : fprintf(out, " [content=%p]", static_cast<void*>(mContent));
8071 0 : if (HasOverflowAreas()) {
8072 0 : nsRect overflowArea = GetVisualOverflowRect();
8073 : fprintf(out, " [vis-overflow=%d,%d,%d,%d]",
8074 : overflowArea.x, overflowArea.y,
8075 0 : overflowArea.width, overflowArea.height);
8076 0 : overflowArea = GetScrollableOverflowRect();
8077 : fprintf(out, " [scr-overflow=%d,%d,%d,%d]",
8078 : overflowArea.x, overflowArea.y,
8079 0 : overflowArea.width, overflowArea.height);
8080 : }
8081 0 : fprintf(out, " sc=%p", static_cast<void*>(mStyleContext));
8082 0 : nsIAtom* pseudoTag = mStyleContext->GetPseudo();
8083 0 : if (pseudoTag) {
8084 0 : nsAutoString atomString;
8085 0 : pseudoTag->ToString(atomString);
8086 : fprintf(out, " pst=%s",
8087 0 : NS_LossyConvertUTF16toASCII(atomString).get());
8088 : }
8089 0 : fputs("\n", out);
8090 0 : return NS_OK;
8091 : }
8092 : #endif
8093 :
8094 : void
8095 0 : nsTextFrame::AdjustOffsetsForBidi(PRInt32 aStart, PRInt32 aEnd)
8096 : {
8097 0 : AddStateBits(NS_FRAME_IS_BIDI);
8098 0 : mContent->DeleteProperty(nsGkAtoms::flowlength);
8099 :
8100 : /*
8101 : * After Bidi resolution we may need to reassign text runs.
8102 : * This is called during bidi resolution from the block container, so we
8103 : * shouldn't be holding a local reference to a textrun anywhere.
8104 : */
8105 0 : ClearTextRuns();
8106 :
8107 0 : nsTextFrame* prev = static_cast<nsTextFrame*>(GetPrevContinuation());
8108 0 : if (prev) {
8109 : // the bidi resolver can be very evil when columns/pages are involved. Don't
8110 : // let it violate our invariants.
8111 0 : PRInt32 prevOffset = prev->GetContentOffset();
8112 0 : aStart = NS_MAX(aStart, prevOffset);
8113 0 : aEnd = NS_MAX(aEnd, prevOffset);
8114 0 : prev->ClearTextRuns();
8115 : }
8116 :
8117 0 : mContentOffset = aStart;
8118 0 : SetLength(aEnd - aStart, nsnull, 0);
8119 :
8120 : /**
8121 : * After inserting text the caret Bidi level must be set to the level of the
8122 : * inserted text.This is difficult, because we cannot know what the level is
8123 : * until after the Bidi algorithm is applied to the whole paragraph.
8124 : *
8125 : * So we set the caret Bidi level to UNDEFINED here, and the caret code will
8126 : * set it correctly later
8127 : */
8128 0 : nsRefPtr<nsFrameSelection> frameSelection = GetFrameSelection();
8129 0 : if (frameSelection) {
8130 0 : frameSelection->UndefineCaretBidiLevel();
8131 : }
8132 0 : }
8133 :
8134 : /**
8135 : * @return true if this text frame ends with a newline character. It should return
8136 : * false if it is not a text frame.
8137 : */
8138 : bool
8139 0 : nsTextFrame::HasTerminalNewline() const
8140 : {
8141 0 : return ::HasTerminalNewline(this);
8142 : }
8143 :
8144 : bool
8145 0 : nsTextFrame::IsAtEndOfLine() const
8146 : {
8147 0 : return (GetStateBits() & TEXT_END_OF_LINE) != 0;
8148 : }
8149 :
8150 : nscoord
8151 0 : nsTextFrame::GetBaseline() const
8152 : {
8153 0 : return mAscent;
8154 : }
8155 :
8156 : bool
8157 0 : nsTextFrame::HasAnyNoncollapsedCharacters()
8158 : {
8159 0 : gfxSkipCharsIterator iter = EnsureTextRun(nsTextFrame::eInflated);
8160 0 : PRInt32 offset = GetContentOffset(),
8161 0 : offsetEnd = GetContentEnd();
8162 0 : PRInt32 skippedOffset = iter.ConvertOriginalToSkipped(offset);
8163 0 : PRInt32 skippedOffsetEnd = iter.ConvertOriginalToSkipped(offsetEnd);
8164 0 : return skippedOffset != skippedOffsetEnd;
8165 : }
|