layout/generic/nsTextFrame.cpp

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

mercurial