Bumping manifests a=b2g-bump
[gecko.git] / layout / generic / nsTextFrame.cpp
blobe6e7d934ea928a05f8404035b9283c4dd2eaffdb
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/. */
6 /* rendering object for textual content of elements */
8 #include "nsTextFrame.h"
10 #include "gfx2DGlue.h"
11 #include "mozilla/Attributes.h"
12 #include "mozilla/DebugOnly.h"
13 #include "mozilla/Likely.h"
14 #include "mozilla/MathAlgorithms.h"
15 #include "mozilla/TextEvents.h"
16 #include "mozilla/BinarySearch.h"
18 #include "nsCOMPtr.h"
19 #include "nsBlockFrame.h"
20 #include "nsCRT.h"
21 #include "nsFontMetrics.h"
22 #include "nsSplittableFrame.h"
23 #include "nsLineLayout.h"
24 #include "nsString.h"
25 #include "nsUnicharUtils.h"
26 #include "nsPresContext.h"
27 #include "nsIContent.h"
28 #include "nsStyleConsts.h"
29 #include "nsStyleContext.h"
30 #include "nsStyleStruct.h"
31 #include "nsStyleStructInlines.h"
32 #include "SVGTextFrame.h"
33 #include "nsCoord.h"
34 #include "nsRenderingContext.h"
35 #include "nsIPresShell.h"
36 #include "nsTArray.h"
37 #include "nsCSSPseudoElements.h"
38 #include "nsCSSFrameConstructor.h"
39 #include "nsCompatibility.h"
40 #include "nsCSSColorUtils.h"
41 #include "nsLayoutUtils.h"
42 #include "nsDisplayList.h"
43 #include "nsFrame.h"
44 #include "nsIMathMLFrame.h"
45 #include "nsPlaceholderFrame.h"
46 #include "nsTextFrameUtils.h"
47 #include "nsTextRunTransformations.h"
48 #include "MathMLTextRunFactory.h"
49 #include "nsExpirationTracker.h"
50 #include "nsUnicodeProperties.h"
52 #include "nsTextFragment.h"
53 #include "nsGkAtoms.h"
54 #include "nsFrameSelection.h"
55 #include "nsRange.h"
56 #include "nsCSSRendering.h"
57 #include "nsContentUtils.h"
58 #include "nsLineBreaker.h"
59 #include "nsIWordBreaker.h"
60 #include "nsGenericDOMDataNode.h"
61 #include "nsIFrameInlines.h"
63 #include <algorithm>
64 #ifdef ACCESSIBILITY
65 #include "nsAccessibilityService.h"
66 #endif
67 #include "nsAutoPtr.h"
69 #include "nsPrintfCString.h"
71 #include "gfxContext.h"
73 #include "mozilla/dom/Element.h"
74 #include "mozilla/LookAndFeel.h"
76 #include "GeckoProfiler.h"
78 #ifdef DEBUG
79 #undef NOISY_REFLOW
80 #undef NOISY_TRIM
81 #else
82 #undef NOISY_REFLOW
83 #undef NOISY_TRIM
84 #endif
86 #ifdef DrawText
87 #undef DrawText
88 #endif
90 using namespace mozilla;
91 using namespace mozilla::dom;
92 using namespace mozilla::gfx;
94 struct TabWidth {
95 TabWidth(uint32_t aOffset, uint32_t aWidth)
96 : mOffset(aOffset), mWidth(float(aWidth))
97 { }
99 uint32_t mOffset; // DOM offset relative to the current frame's offset.
100 float mWidth; // extra space to be added at this position (in app units)
103 struct TabWidthStore {
104 explicit TabWidthStore(int32_t aValidForContentOffset)
105 : mLimit(0)
106 , mValidForContentOffset(aValidForContentOffset)
109 // Apply tab widths to the aSpacing array, which corresponds to characters
110 // beginning at aOffset and has length aLength. (Width records outside this
111 // range will be ignored.)
112 void ApplySpacing(gfxTextRun::PropertyProvider::Spacing *aSpacing,
113 uint32_t aOffset, uint32_t aLength);
115 // Offset up to which tabs have been measured; positions beyond this have not
116 // been calculated yet but may be appended if needed later. It's a DOM
117 // offset relative to the current frame's offset.
118 uint32_t mLimit;
120 // Need to recalc tab offsets if frame content offset differs from this.
121 int32_t mValidForContentOffset;
123 // A TabWidth record for each tab character measured so far.
124 nsTArray<TabWidth> mWidths;
127 namespace {
129 struct TabwidthAdaptor
131 const nsTArray<TabWidth>& mWidths;
132 explicit TabwidthAdaptor(const nsTArray<TabWidth>& aWidths)
133 : mWidths(aWidths) {}
134 uint32_t operator[](size_t aIdx) const {
135 return mWidths[aIdx].mOffset;
139 } // namespace
141 void
142 TabWidthStore::ApplySpacing(gfxTextRun::PropertyProvider::Spacing *aSpacing,
143 uint32_t aOffset, uint32_t aLength)
145 size_t i = 0;
146 const size_t len = mWidths.Length();
148 // If aOffset is non-zero, do a binary search to find where to start
149 // processing the tab widths, in case the list is really long. (See bug
150 // 953247.)
151 // We need to start from the first entry where mOffset >= aOffset.
152 if (aOffset > 0) {
153 mozilla::BinarySearch(TabwidthAdaptor(mWidths), 0, len, aOffset, &i);
156 uint32_t limit = aOffset + aLength;
157 while (i < len) {
158 const TabWidth& tw = mWidths[i];
159 if (tw.mOffset >= limit) {
160 break;
162 aSpacing[tw.mOffset - aOffset].mAfter += tw.mWidth;
163 i++;
167 static void DestroyTabWidth(void* aPropertyValue)
169 delete static_cast<TabWidthStore*>(aPropertyValue);
172 NS_DECLARE_FRAME_PROPERTY(TabWidthProperty, DestroyTabWidth)
174 NS_DECLARE_FRAME_PROPERTY(OffsetToFrameProperty, nullptr)
176 // text runs are destroyed by the text run cache
177 NS_DECLARE_FRAME_PROPERTY(UninflatedTextRunProperty, nullptr)
179 NS_DECLARE_FRAME_PROPERTY(FontSizeInflationProperty, nullptr)
181 class GlyphObserver : public gfxFont::GlyphChangeObserver {
182 public:
183 GlyphObserver(gfxFont* aFont, nsTextFrame* aFrame)
184 : gfxFont::GlyphChangeObserver(aFont), mFrame(aFrame) {}
185 virtual void NotifyGlyphsChanged() MOZ_OVERRIDE;
186 private:
187 nsTextFrame* mFrame;
190 static void DestroyGlyphObserverList(void* aPropertyValue)
192 delete static_cast<nsTArray<nsAutoPtr<GlyphObserver> >*>(aPropertyValue);
196 * This property is set on text frames with TEXT_IN_TEXTRUN_USER_DATA set that
197 * have potentially-animated glyphs.
198 * The only reason this list is in a property is to automatically destroy the
199 * list when the frame is deleted, unregistering the observers.
201 NS_DECLARE_FRAME_PROPERTY(TextFrameGlyphObservers, DestroyGlyphObserverList);
203 #define TEXT_REFLOW_FLAGS \
204 (TEXT_FIRST_LETTER|TEXT_START_OF_LINE|TEXT_END_OF_LINE|TEXT_HYPHEN_BREAK| \
205 TEXT_TRIMMED_TRAILING_WHITESPACE|TEXT_JUSTIFICATION_ENABLED| \
206 TEXT_HAS_NONCOLLAPSED_CHARACTERS|TEXT_SELECTION_UNDERLINE_OVERFLOWED)
208 #define TEXT_WHITESPACE_FLAGS (TEXT_IS_ONLY_WHITESPACE | \
209 TEXT_ISNOT_ONLY_WHITESPACE)
212 * Some general notes
214 * Text frames delegate work to gfxTextRun objects. The gfxTextRun object
215 * transforms text to positioned glyphs. It can report the geometry of the
216 * glyphs and paint them. Text frames configure gfxTextRuns by providing text,
217 * spacing, language, and other information.
219 * A gfxTextRun can cover more than one DOM text node. This is necessary to
220 * get kerning, ligatures and shaping for text that spans multiple text nodes
221 * but is all the same font. The userdata for a gfxTextRun object is a
222 * TextRunUserData* or an nsIFrame*.
224 * We go to considerable effort to make sure things work even if in-flow
225 * siblings have different style contexts (i.e., first-letter and first-line).
227 * Our convention is that unsigned integer character offsets are offsets into
228 * the transformed string. Signed integer character offsets are offsets into
229 * the DOM string.
231 * XXX currently we don't handle hyphenated breaks between text frames where the
232 * hyphen occurs at the end of the first text frame, e.g.
233 * <b>Kit&shy;</b>ty
237 * We use an array of these objects to record which text frames
238 * are associated with the textrun. mStartFrame is the start of a list of
239 * text frames. Some sequence of its continuations are covered by the textrun.
240 * A content textnode can have at most one TextRunMappedFlow associated with it
241 * for a given textrun.
243 * mDOMOffsetToBeforeTransformOffset is added to DOM offsets for those frames to obtain
244 * the offset into the before-transformation text of the textrun. It can be
245 * positive (when a text node starts in the middle of a text run) or
246 * negative (when a text run starts in the middle of a text node). Of course
247 * it can also be zero.
249 struct TextRunMappedFlow {
250 nsTextFrame* mStartFrame;
251 int32_t mDOMOffsetToBeforeTransformOffset;
252 // The text mapped starts at mStartFrame->GetContentOffset() and is this long
253 uint32_t mContentLength;
257 * This is our user data for the textrun, when textRun->GetFlags() does not
258 * have TEXT_IS_SIMPLE_FLOW set. When TEXT_IS_SIMPLE_FLOW is set, there is
259 * just one flow, the textrun's user data pointer is a pointer to mStartFrame
260 * for that flow, mDOMOffsetToBeforeTransformOffset is zero, and mContentLength
261 * is the length of the text node.
263 struct TextRunUserData {
264 TextRunMappedFlow* mMappedFlows;
265 uint32_t mMappedFlowCount;
266 uint32_t mLastFlowIndex;
270 * This helper object computes colors used for painting, and also IME
271 * underline information. The data is computed lazily and cached as necessary.
272 * These live for just the duration of one paint operation.
274 class nsTextPaintStyle {
275 public:
276 explicit nsTextPaintStyle(nsTextFrame* aFrame);
278 void SetResolveColors(bool aResolveColors) {
279 NS_ASSERTION(mFrame->IsSVGText() || aResolveColors,
280 "must resolve colors is frame is not for SVG text");
281 mResolveColors = aResolveColors;
284 nscolor GetTextColor();
286 * Compute the colors for normally-selected text. Returns false if
287 * the normal selection is not being displayed.
289 bool GetSelectionColors(nscolor* aForeColor,
290 nscolor* aBackColor);
291 void GetHighlightColors(nscolor* aForeColor,
292 nscolor* aBackColor);
293 void GetURLSecondaryColor(nscolor* aForeColor);
294 void GetIMESelectionColors(int32_t aIndex,
295 nscolor* aForeColor,
296 nscolor* aBackColor);
297 // if this returns false, we don't need to draw underline.
298 bool GetSelectionUnderlineForPaint(int32_t aIndex,
299 nscolor* aLineColor,
300 float* aRelativeSize,
301 uint8_t* aStyle);
303 // if this returns false, we don't need to draw underline.
304 static bool GetSelectionUnderline(nsPresContext* aPresContext,
305 int32_t aIndex,
306 nscolor* aLineColor,
307 float* aRelativeSize,
308 uint8_t* aStyle);
310 // if this returns false, no text-shadow was specified for the selection
311 // and the *aShadow parameter was not modified.
312 bool GetSelectionShadow(nsCSSShadowArray** aShadow);
314 nsPresContext* PresContext() const { return mPresContext; }
316 enum {
317 eIndexRawInput = 0,
318 eIndexSelRawText,
319 eIndexConvText,
320 eIndexSelConvText,
321 eIndexSpellChecker
324 static int32_t GetUnderlineStyleIndexForSelectionType(int32_t aSelectionType)
326 switch (aSelectionType) {
327 case nsISelectionController::SELECTION_IME_RAWINPUT:
328 return eIndexRawInput;
329 case nsISelectionController::SELECTION_IME_SELECTEDRAWTEXT:
330 return eIndexSelRawText;
331 case nsISelectionController::SELECTION_IME_CONVERTEDTEXT:
332 return eIndexConvText;
333 case nsISelectionController::SELECTION_IME_SELECTEDCONVERTEDTEXT:
334 return eIndexSelConvText;
335 case nsISelectionController::SELECTION_SPELLCHECK:
336 return eIndexSpellChecker;
337 default:
338 NS_WARNING("non-IME selection type");
339 return eIndexRawInput;
343 protected:
344 nsTextFrame* mFrame;
345 nsPresContext* mPresContext;
346 bool mInitCommonColors;
347 bool mInitSelectionColorsAndShadow;
348 bool mResolveColors;
350 // Selection data
352 int16_t mSelectionStatus; // see nsIDocument.h SetDisplaySelection()
353 nscolor mSelectionTextColor;
354 nscolor mSelectionBGColor;
355 nsRefPtr<nsCSSShadowArray> mSelectionShadow;
356 bool mHasSelectionShadow;
358 // Common data
360 int32_t mSufficientContrast;
361 nscolor mFrameBackgroundColor;
363 // selection colors and underline info, the colors are resolved colors if
364 // mResolveColors is true (which is the default), i.e., the foreground color
365 // and background color are swapped if it's needed. And also line color will
366 // be resolved from them.
367 struct nsSelectionStyle {
368 bool mInit;
369 nscolor mTextColor;
370 nscolor mBGColor;
371 nscolor mUnderlineColor;
372 uint8_t mUnderlineStyle;
373 float mUnderlineRelativeSize;
375 nsSelectionStyle mSelectionStyle[5];
377 // Color initializations
378 void InitCommonColors();
379 bool InitSelectionColorsAndShadow();
381 nsSelectionStyle* GetSelectionStyle(int32_t aIndex);
382 void InitSelectionStyle(int32_t aIndex);
384 bool EnsureSufficientContrast(nscolor *aForeColor, nscolor *aBackColor);
386 nscolor GetResolvedForeColor(nscolor aColor, nscolor aDefaultForeColor,
387 nscolor aBackColor);
390 static void
391 DestroyUserData(void* aUserData)
393 TextRunUserData* userData = static_cast<TextRunUserData*>(aUserData);
394 if (userData) {
395 nsMemory::Free(userData);
400 * Remove |aTextRun| from the frame continuation chain starting at
401 * |aStartContinuation| if non-null, otherwise starting at |aFrame|.
402 * Unmark |aFrame| as a text run owner if it's the frame we start at.
403 * Return true if |aStartContinuation| is non-null and was found
404 * in the next-continuation chain of |aFrame|.
406 static bool
407 ClearAllTextRunReferences(nsTextFrame* aFrame, gfxTextRun* aTextRun,
408 nsTextFrame* aStartContinuation,
409 nsFrameState aWhichTextRunState)
411 NS_PRECONDITION(aFrame, "");
412 NS_PRECONDITION(!aStartContinuation ||
413 (!aStartContinuation->GetTextRun(nsTextFrame::eInflated) ||
414 aStartContinuation->GetTextRun(nsTextFrame::eInflated) == aTextRun) ||
415 (!aStartContinuation->GetTextRun(nsTextFrame::eNotInflated) ||
416 aStartContinuation->GetTextRun(nsTextFrame::eNotInflated) == aTextRun),
417 "wrong aStartContinuation for this text run");
419 if (!aStartContinuation || aStartContinuation == aFrame) {
420 aFrame->RemoveStateBits(aWhichTextRunState);
421 } else {
422 do {
423 NS_ASSERTION(aFrame->GetType() == nsGkAtoms::textFrame, "Bad frame");
424 aFrame = static_cast<nsTextFrame*>(aFrame->GetNextContinuation());
425 } while (aFrame && aFrame != aStartContinuation);
427 bool found = aStartContinuation == aFrame;
428 while (aFrame) {
429 NS_ASSERTION(aFrame->GetType() == nsGkAtoms::textFrame, "Bad frame");
430 if (!aFrame->RemoveTextRun(aTextRun)) {
431 break;
433 aFrame = static_cast<nsTextFrame*>(aFrame->GetNextContinuation());
435 NS_POSTCONDITION(!found || aStartContinuation, "how did we find null?");
436 return found;
440 * Kill all references to |aTextRun| starting at |aStartContinuation|.
441 * It could be referenced by any of its owners, and all their in-flows.
442 * If |aStartContinuation| is null then process all userdata frames
443 * and their continuations.
444 * @note the caller is expected to take care of possibly destroying the
445 * text run if all userdata frames were reset (userdata is deallocated
446 * by this function though). The caller can detect this has occured by
447 * checking |aTextRun->GetUserData() == nullptr|.
449 static void
450 UnhookTextRunFromFrames(gfxTextRun* aTextRun, nsTextFrame* aStartContinuation)
452 if (!aTextRun->GetUserData())
453 return;
455 if (aTextRun->GetFlags() & nsTextFrameUtils::TEXT_IS_SIMPLE_FLOW) {
456 nsTextFrame* userDataFrame = static_cast<nsTextFrame*>(
457 static_cast<nsIFrame*>(aTextRun->GetUserData()));
458 nsFrameState whichTextRunState =
459 userDataFrame->GetTextRun(nsTextFrame::eInflated) == aTextRun
460 ? TEXT_IN_TEXTRUN_USER_DATA
461 : TEXT_IN_UNINFLATED_TEXTRUN_USER_DATA;
462 DebugOnly<bool> found =
463 ClearAllTextRunReferences(userDataFrame, aTextRun,
464 aStartContinuation, whichTextRunState);
465 NS_ASSERTION(!aStartContinuation || found,
466 "aStartContinuation wasn't found in simple flow text run");
467 if (!(userDataFrame->GetStateBits() & whichTextRunState)) {
468 aTextRun->SetUserData(nullptr);
470 } else {
471 TextRunUserData* userData =
472 static_cast<TextRunUserData*>(aTextRun->GetUserData());
473 int32_t destroyFromIndex = aStartContinuation ? -1 : 0;
474 for (uint32_t i = 0; i < userData->mMappedFlowCount; ++i) {
475 nsTextFrame* userDataFrame = userData->mMappedFlows[i].mStartFrame;
476 nsFrameState whichTextRunState =
477 userDataFrame->GetTextRun(nsTextFrame::eInflated) == aTextRun
478 ? TEXT_IN_TEXTRUN_USER_DATA
479 : TEXT_IN_UNINFLATED_TEXTRUN_USER_DATA;
480 bool found =
481 ClearAllTextRunReferences(userDataFrame, aTextRun,
482 aStartContinuation, whichTextRunState);
483 if (found) {
484 if (userDataFrame->GetStateBits() & whichTextRunState) {
485 destroyFromIndex = i + 1;
487 else {
488 destroyFromIndex = i;
490 aStartContinuation = nullptr;
493 NS_ASSERTION(destroyFromIndex >= 0,
494 "aStartContinuation wasn't found in multi flow text run");
495 if (destroyFromIndex == 0) {
496 DestroyUserData(userData);
497 aTextRun->SetUserData(nullptr);
499 else {
500 userData->mMappedFlowCount = uint32_t(destroyFromIndex);
501 if (userData->mLastFlowIndex >= uint32_t(destroyFromIndex)) {
502 userData->mLastFlowIndex = uint32_t(destroyFromIndex) - 1;
508 void
509 GlyphObserver::NotifyGlyphsChanged()
511 nsIPresShell* shell = mFrame->PresContext()->PresShell();
512 for (nsIFrame* f = mFrame; f;
513 f = nsLayoutUtils::GetNextContinuationOrIBSplitSibling(f)) {
514 if (f != mFrame && f->HasAnyStateBits(TEXT_IN_TEXTRUN_USER_DATA)) {
515 // f will have its own GlyphObserver (if needed) so we can stop here.
516 break;
518 f->InvalidateFrame();
519 // Theoretically we could just update overflow areas, perhaps using
520 // OverflowChangedTracker, but that would do a bunch of work eagerly that
521 // we should probably do lazily here since there could be a lot
522 // of text frames affected and we'd like to coalesce the work. So that's
523 // not easy to do well.
524 shell->FrameNeedsReflow(f, nsIPresShell::eResize, NS_FRAME_IS_DIRTY);
528 class FrameTextRunCache;
530 static FrameTextRunCache *gTextRuns = nullptr;
533 * Cache textruns and expire them after 3*10 seconds of no use.
535 class FrameTextRunCache MOZ_FINAL : public nsExpirationTracker<gfxTextRun,3> {
536 public:
537 enum { TIMEOUT_SECONDS = 10 };
538 FrameTextRunCache()
539 : nsExpirationTracker<gfxTextRun,3>(TIMEOUT_SECONDS*1000) {}
540 ~FrameTextRunCache() {
541 AgeAllGenerations();
544 void RemoveFromCache(gfxTextRun* aTextRun) {
545 if (aTextRun->GetExpirationState()->IsTracked()) {
546 RemoveObject(aTextRun);
550 // This gets called when the timeout has expired on a gfxTextRun
551 virtual void NotifyExpired(gfxTextRun* aTextRun) {
552 UnhookTextRunFromFrames(aTextRun, nullptr);
553 RemoveFromCache(aTextRun);
554 delete aTextRun;
558 // Helper to create a textrun and remember it in the textframe cache,
559 // for either 8-bit or 16-bit text strings
560 template<typename T>
561 gfxTextRun *
562 MakeTextRun(const T *aText, uint32_t aLength,
563 gfxFontGroup *aFontGroup, const gfxFontGroup::Parameters* aParams,
564 uint32_t aFlags, gfxMissingFontRecorder *aMFR)
566 nsAutoPtr<gfxTextRun> textRun(aFontGroup->MakeTextRun(aText, aLength,
567 aParams, aFlags,
568 aMFR));
569 if (!textRun) {
570 return nullptr;
572 nsresult rv = gTextRuns->AddObject(textRun);
573 if (NS_FAILED(rv)) {
574 gTextRuns->RemoveFromCache(textRun);
575 return nullptr;
577 #ifdef NOISY_BIDI
578 printf("Created textrun\n");
579 #endif
580 return textRun.forget();
583 void
584 nsTextFrameTextRunCache::Init() {
585 gTextRuns = new FrameTextRunCache();
588 void
589 nsTextFrameTextRunCache::Shutdown() {
590 delete gTextRuns;
591 gTextRuns = nullptr;
594 int32_t nsTextFrame::GetContentEnd() const {
595 nsTextFrame* next = static_cast<nsTextFrame*>(GetNextContinuation());
596 return next ? next->GetContentOffset() : mContent->GetText()->GetLength();
599 struct FlowLengthProperty {
600 int32_t mStartOffset;
601 // The offset of the next fixed continuation after mStartOffset, or
602 // of the end of the text if there is none
603 int32_t mEndFlowOffset;
606 int32_t nsTextFrame::GetInFlowContentLength() {
607 if (!(mState & NS_FRAME_IS_BIDI)) {
608 return mContent->TextLength() - mContentOffset;
611 FlowLengthProperty* flowLength =
612 static_cast<FlowLengthProperty*>(mContent->GetProperty(nsGkAtoms::flowlength));
615 * This frame must start inside the cached flow. If the flow starts at
616 * mContentOffset but this frame is empty, logically it might be before the
617 * start of the cached flow.
619 if (flowLength &&
620 (flowLength->mStartOffset < mContentOffset ||
621 (flowLength->mStartOffset == mContentOffset && GetContentEnd() > mContentOffset)) &&
622 flowLength->mEndFlowOffset > mContentOffset) {
623 #ifdef DEBUG
624 NS_ASSERTION(flowLength->mEndFlowOffset >= GetContentEnd(),
625 "frame crosses fixed continuation boundary");
626 #endif
627 return flowLength->mEndFlowOffset - mContentOffset;
630 nsTextFrame* nextBidi = static_cast<nsTextFrame*>(LastInFlow()->GetNextContinuation());
631 int32_t endFlow = nextBidi ? nextBidi->GetContentOffset() : mContent->TextLength();
633 if (!flowLength) {
634 flowLength = new FlowLengthProperty;
635 if (NS_FAILED(mContent->SetProperty(nsGkAtoms::flowlength, flowLength,
636 nsINode::DeleteProperty<FlowLengthProperty>))) {
637 delete flowLength;
638 flowLength = nullptr;
641 if (flowLength) {
642 flowLength->mStartOffset = mContentOffset;
643 flowLength->mEndFlowOffset = endFlow;
646 return endFlow - mContentOffset;
649 // Smarter versions of dom::IsSpaceCharacter.
650 // Unicode is really annoying; sometimes a space character isn't whitespace ---
651 // when it combines with another character
652 // So we have several versions of IsSpace for use in different contexts.
654 static bool IsSpaceCombiningSequenceTail(const nsTextFragment* aFrag, uint32_t aPos)
656 NS_ASSERTION(aPos <= aFrag->GetLength(), "Bad offset");
657 if (!aFrag->Is2b())
658 return false;
659 return nsTextFrameUtils::IsSpaceCombiningSequenceTail(
660 aFrag->Get2b() + aPos, aFrag->GetLength() - aPos);
663 // Check whether aPos is a space for CSS 'word-spacing' purposes
664 static bool IsCSSWordSpacingSpace(const nsTextFragment* aFrag,
665 uint32_t aPos, const nsStyleText* aStyleText)
667 NS_ASSERTION(aPos < aFrag->GetLength(), "No text for IsSpace!");
669 char16_t ch = aFrag->CharAt(aPos);
670 switch (ch) {
671 case ' ':
672 case CH_NBSP:
673 return !IsSpaceCombiningSequenceTail(aFrag, aPos + 1);
674 case '\r':
675 case '\t': return !aStyleText->WhiteSpaceIsSignificant();
676 case '\n': return !aStyleText->NewlineIsSignificant();
677 default: return false;
681 // Check whether the string aChars/aLength starts with space that's
682 // trimmable according to CSS 'white-space:normal/nowrap'.
683 static bool IsTrimmableSpace(const char16_t* aChars, uint32_t aLength)
685 NS_ASSERTION(aLength > 0, "No text for IsSpace!");
687 char16_t ch = *aChars;
688 if (ch == ' ')
689 return !nsTextFrameUtils::IsSpaceCombiningSequenceTail(aChars + 1, aLength - 1);
690 return ch == '\t' || ch == '\f' || ch == '\n' || ch == '\r';
693 // Check whether the character aCh is trimmable according to CSS
694 // 'white-space:normal/nowrap'
695 static bool IsTrimmableSpace(char aCh)
697 return aCh == ' ' || aCh == '\t' || aCh == '\f' || aCh == '\n' || aCh == '\r';
700 static bool IsTrimmableSpace(const nsTextFragment* aFrag, uint32_t aPos,
701 const nsStyleText* aStyleText)
703 NS_ASSERTION(aPos < aFrag->GetLength(), "No text for IsSpace!");
705 switch (aFrag->CharAt(aPos)) {
706 case ' ': return !aStyleText->WhiteSpaceIsSignificant() &&
707 !IsSpaceCombiningSequenceTail(aFrag, aPos + 1);
708 case '\n': return !aStyleText->NewlineIsSignificant() &&
709 aStyleText->mWhiteSpace != NS_STYLE_WHITESPACE_PRE_SPACE;
710 case '\t':
711 case '\r':
712 case '\f': return !aStyleText->WhiteSpaceIsSignificant();
713 default: return false;
717 static bool IsSelectionSpace(const nsTextFragment* aFrag, uint32_t aPos)
719 NS_ASSERTION(aPos < aFrag->GetLength(), "No text for IsSpace!");
720 char16_t ch = aFrag->CharAt(aPos);
721 if (ch == ' ' || ch == CH_NBSP)
722 return !IsSpaceCombiningSequenceTail(aFrag, aPos + 1);
723 return ch == '\t' || ch == '\n' || ch == '\f' || ch == '\r';
726 // Count the amount of trimmable whitespace (as per CSS
727 // 'white-space:normal/nowrap') in a text fragment. The first
728 // character is at offset aStartOffset; the maximum number of characters
729 // to check is aLength. aDirection is -1 or 1 depending on whether we should
730 // progress backwards or forwards.
731 static uint32_t
732 GetTrimmableWhitespaceCount(const nsTextFragment* aFrag,
733 int32_t aStartOffset, int32_t aLength,
734 int32_t aDirection)
736 int32_t count = 0;
737 if (aFrag->Is2b()) {
738 const char16_t* str = aFrag->Get2b() + aStartOffset;
739 int32_t fragLen = aFrag->GetLength() - aStartOffset;
740 for (; count < aLength; ++count) {
741 if (!IsTrimmableSpace(str, fragLen))
742 break;
743 str += aDirection;
744 fragLen -= aDirection;
746 } else {
747 const char* str = aFrag->Get1b() + aStartOffset;
748 for (; count < aLength; ++count) {
749 if (!IsTrimmableSpace(*str))
750 break;
751 str += aDirection;
754 return count;
757 static bool
758 IsAllWhitespace(const nsTextFragment* aFrag, bool aAllowNewline)
760 if (aFrag->Is2b())
761 return false;
762 int32_t len = aFrag->GetLength();
763 const char* str = aFrag->Get1b();
764 for (int32_t i = 0; i < len; ++i) {
765 char ch = str[i];
766 if (ch == ' ' || ch == '\t' || ch == '\r' || (ch == '\n' && aAllowNewline))
767 continue;
768 return false;
770 return true;
773 static void
774 CreateObserverForAnimatedGlyphs(nsTextFrame* aFrame, const nsTArray<gfxFont*>& aFonts)
776 if (!(aFrame->GetStateBits() & TEXT_IN_TEXTRUN_USER_DATA)) {
777 // Maybe the textrun was created for uninflated text.
778 return;
781 nsTArray<nsAutoPtr<GlyphObserver> >* observers =
782 new nsTArray<nsAutoPtr<GlyphObserver> >();
783 for (uint32_t i = 0, count = aFonts.Length(); i < count; ++i) {
784 observers->AppendElement(new GlyphObserver(aFonts[i], aFrame));
786 aFrame->Properties().Set(TextFrameGlyphObservers(), observers);
787 // We are lazy and don't try to remove a property value that might be
788 // obsolete due to style changes or font selection changes. That is
789 // likely to be rarely needed, and we don't want to eat the overhead of
790 // doing it for the overwhelmingly common case of no property existing.
791 // (And we're out of state bits to conveniently use for a fast property
792 // existence check.) The only downside is that in some rare cases we might
793 // keep fonts alive for longer than necessary, or unnecessarily invalidate
794 // frames.
797 static void
798 CreateObserversForAnimatedGlyphs(gfxTextRun* aTextRun)
800 if (!aTextRun->GetUserData()) {
801 return;
803 nsTArray<gfxFont*> fontsWithAnimatedGlyphs;
804 uint32_t numGlyphRuns;
805 const gfxTextRun::GlyphRun* glyphRuns =
806 aTextRun->GetGlyphRuns(&numGlyphRuns);
807 for (uint32_t i = 0; i < numGlyphRuns; ++i) {
808 gfxFont* font = glyphRuns[i].mFont;
809 if (font->GlyphsMayChange() && !fontsWithAnimatedGlyphs.Contains(font)) {
810 fontsWithAnimatedGlyphs.AppendElement(font);
813 if (fontsWithAnimatedGlyphs.IsEmpty()) {
814 return;
817 if (aTextRun->GetFlags() & nsTextFrameUtils::TEXT_IS_SIMPLE_FLOW) {
818 CreateObserverForAnimatedGlyphs(static_cast<nsTextFrame*>(
819 static_cast<nsIFrame*>(aTextRun->GetUserData())), fontsWithAnimatedGlyphs);
820 } else {
821 TextRunUserData* userData =
822 static_cast<TextRunUserData*>(aTextRun->GetUserData());
823 for (uint32_t i = 0; i < userData->mMappedFlowCount; ++i) {
824 CreateObserverForAnimatedGlyphs(userData->mMappedFlows[i].mStartFrame,
825 fontsWithAnimatedGlyphs);
831 * This class accumulates state as we scan a paragraph of text. It detects
832 * textrun boundaries (changes from text to non-text, hard
833 * line breaks, and font changes) and builds a gfxTextRun at each boundary.
834 * It also detects linebreaker run boundaries (changes from text to non-text,
835 * and hard line breaks) and at each boundary runs the linebreaker to compute
836 * potential line breaks. It also records actual line breaks to store them in
837 * the textruns.
839 class BuildTextRunsScanner {
840 public:
841 BuildTextRunsScanner(nsPresContext* aPresContext, gfxContext* aContext,
842 nsIFrame* aLineContainer, nsTextFrame::TextRunType aWhichTextRun) :
843 mCurrentFramesAllSameTextRun(nullptr),
844 mContext(aContext),
845 mLineContainer(aLineContainer),
846 mMissingFonts(aPresContext->MissingFontRecorder()),
847 mBidiEnabled(aPresContext->BidiEnabled()),
848 mSkipIncompleteTextRuns(false),
849 mWhichTextRun(aWhichTextRun),
850 mNextRunContextInfo(nsTextFrameUtils::INCOMING_NONE),
851 mCurrentRunContextInfo(nsTextFrameUtils::INCOMING_NONE) {
852 ResetRunInfo();
854 ~BuildTextRunsScanner() {
855 NS_ASSERTION(mBreakSinks.IsEmpty(), "Should have been cleared");
856 NS_ASSERTION(mTextRunsToDelete.IsEmpty(), "Should have been cleared");
857 NS_ASSERTION(mLineBreakBeforeFrames.IsEmpty(), "Should have been cleared");
858 NS_ASSERTION(mMappedFlows.IsEmpty(), "Should have been cleared");
861 void SetAtStartOfLine() {
862 mStartOfLine = true;
863 mCanStopOnThisLine = false;
865 void SetSkipIncompleteTextRuns(bool aSkip) {
866 mSkipIncompleteTextRuns = aSkip;
868 void SetCommonAncestorWithLastFrame(nsIFrame* aFrame) {
869 mCommonAncestorWithLastFrame = aFrame;
871 bool CanStopOnThisLine() {
872 return mCanStopOnThisLine;
874 nsIFrame* GetCommonAncestorWithLastFrame() {
875 return mCommonAncestorWithLastFrame;
877 void LiftCommonAncestorWithLastFrameToParent(nsIFrame* aFrame) {
878 if (mCommonAncestorWithLastFrame &&
879 mCommonAncestorWithLastFrame->GetParent() == aFrame) {
880 mCommonAncestorWithLastFrame = aFrame;
883 void ScanFrame(nsIFrame* aFrame);
884 bool IsTextRunValidForMappedFlows(gfxTextRun* aTextRun);
885 void FlushFrames(bool aFlushLineBreaks, bool aSuppressTrailingBreak);
886 void FlushLineBreaks(gfxTextRun* aTrailingTextRun);
887 void ResetRunInfo() {
888 mLastFrame = nullptr;
889 mMappedFlows.Clear();
890 mLineBreakBeforeFrames.Clear();
891 mMaxTextLength = 0;
892 mDoubleByteText = false;
894 void AccumulateRunInfo(nsTextFrame* aFrame);
896 * @return null to indicate either textrun construction failed or
897 * we constructed just a partial textrun to set up linebreaker and other
898 * state for following textruns.
900 gfxTextRun* BuildTextRunForFrames(void* aTextBuffer);
901 bool SetupLineBreakerContext(gfxTextRun *aTextRun);
902 void AssignTextRun(gfxTextRun* aTextRun, float aInflation);
903 nsTextFrame* GetNextBreakBeforeFrame(uint32_t* aIndex);
904 enum SetupBreakSinksFlags {
905 SBS_DOUBLE_BYTE = (1 << 0),
906 SBS_EXISTING_TEXTRUN = (1 << 1),
907 SBS_SUPPRESS_SINK = (1 << 2)
909 void SetupBreakSinksForTextRun(gfxTextRun* aTextRun,
910 const void* aTextPtr,
911 uint32_t aFlags);
912 struct FindBoundaryState {
913 nsIFrame* mStopAtFrame;
914 nsTextFrame* mFirstTextFrame;
915 nsTextFrame* mLastTextFrame;
916 bool mSeenTextRunBoundaryOnLaterLine;
917 bool mSeenTextRunBoundaryOnThisLine;
918 bool mSeenSpaceForLineBreakingOnThisLine;
920 enum FindBoundaryResult {
921 FB_CONTINUE,
922 FB_STOPPED_AT_STOP_FRAME,
923 FB_FOUND_VALID_TEXTRUN_BOUNDARY
925 FindBoundaryResult FindBoundaries(nsIFrame* aFrame, FindBoundaryState* aState);
927 bool ContinueTextRunAcrossFrames(nsTextFrame* aFrame1, nsTextFrame* aFrame2);
929 // Like TextRunMappedFlow but with some differences. mStartFrame to mEndFrame
930 // (exclusive) are a sequence of in-flow frames (if mEndFrame is null, then
931 // continuations starting from mStartFrame are a sequence of in-flow frames).
932 struct MappedFlow {
933 nsTextFrame* mStartFrame;
934 nsTextFrame* mEndFrame;
935 // When we consider breaking between elements, the nearest common
936 // ancestor of the elements containing the characters is the one whose
937 // CSS 'white-space' property governs. So this records the nearest common
938 // ancestor of mStartFrame and the previous text frame, or null if there
939 // was no previous text frame on this line.
940 nsIFrame* mAncestorControllingInitialBreak;
942 int32_t GetContentEnd() {
943 return mEndFrame ? mEndFrame->GetContentOffset()
944 : mStartFrame->GetContent()->GetText()->GetLength();
948 class BreakSink MOZ_FINAL : public nsILineBreakSink {
949 public:
950 BreakSink(gfxTextRun* aTextRun, gfxContext* aContext, uint32_t aOffsetIntoTextRun,
951 bool aExistingTextRun) :
952 mTextRun(aTextRun), mContext(aContext),
953 mOffsetIntoTextRun(aOffsetIntoTextRun),
954 mChangedBreaks(false), mExistingTextRun(aExistingTextRun) {}
956 virtual void SetBreaks(uint32_t aOffset, uint32_t aLength,
957 uint8_t* aBreakBefore) MOZ_OVERRIDE {
958 if (mTextRun->SetPotentialLineBreaks(aOffset + mOffsetIntoTextRun, aLength,
959 aBreakBefore, mContext)) {
960 mChangedBreaks = true;
961 // Be conservative and assume that some breaks have been set
962 mTextRun->ClearFlagBits(nsTextFrameUtils::TEXT_NO_BREAKS);
966 virtual void SetCapitalization(uint32_t aOffset, uint32_t aLength,
967 bool* aCapitalize) MOZ_OVERRIDE {
968 MOZ_ASSERT(mTextRun->GetFlags() & nsTextFrameUtils::TEXT_IS_TRANSFORMED,
969 "Text run should be transformed!");
970 if (mTextRun->GetFlags() & nsTextFrameUtils::TEXT_IS_TRANSFORMED) {
971 nsTransformedTextRun* transformedTextRun =
972 static_cast<nsTransformedTextRun*>(mTextRun);
973 transformedTextRun->SetCapitalization(aOffset + mOffsetIntoTextRun, aLength,
974 aCapitalize, mContext);
978 void Finish(gfxMissingFontRecorder* aMFR) {
979 MOZ_ASSERT(!(mTextRun->GetFlags() &
980 (gfxTextRunFactory::TEXT_UNUSED_FLAGS |
981 nsTextFrameUtils::TEXT_UNUSED_FLAG)),
982 "Flag set that should never be set! (memory safety error?)");
983 if (mTextRun->GetFlags() & nsTextFrameUtils::TEXT_IS_TRANSFORMED) {
984 nsTransformedTextRun* transformedTextRun =
985 static_cast<nsTransformedTextRun*>(mTextRun);
986 transformedTextRun->FinishSettingProperties(mContext, aMFR);
988 // The way nsTransformedTextRun is implemented, its glyph runs aren't
989 // available until after nsTransformedTextRun::FinishSettingProperties()
990 // is called. So that's why we defer checking for animated glyphs to here.
991 CreateObserversForAnimatedGlyphs(mTextRun);
994 gfxTextRun* mTextRun;
995 gfxContext* mContext;
996 uint32_t mOffsetIntoTextRun;
997 bool mChangedBreaks;
998 bool mExistingTextRun;
1001 private:
1002 nsAutoTArray<MappedFlow,10> mMappedFlows;
1003 nsAutoTArray<nsTextFrame*,50> mLineBreakBeforeFrames;
1004 nsAutoTArray<nsAutoPtr<BreakSink>,10> mBreakSinks;
1005 nsAutoTArray<gfxTextRun*,5> mTextRunsToDelete;
1006 nsLineBreaker mLineBreaker;
1007 gfxTextRun* mCurrentFramesAllSameTextRun;
1008 gfxContext* mContext;
1009 nsIFrame* mLineContainer;
1010 nsTextFrame* mLastFrame;
1011 // The common ancestor of the current frame and the previous leaf frame
1012 // on the line, or null if there was no previous leaf frame.
1013 nsIFrame* mCommonAncestorWithLastFrame;
1014 gfxMissingFontRecorder* mMissingFonts;
1015 // mMaxTextLength is an upper bound on the size of the text in all mapped frames
1016 // The value UINT32_MAX represents overflow; text will be discarded
1017 uint32_t mMaxTextLength;
1018 bool mDoubleByteText;
1019 bool mBidiEnabled;
1020 bool mStartOfLine;
1021 bool mSkipIncompleteTextRuns;
1022 bool mCanStopOnThisLine;
1023 nsTextFrame::TextRunType mWhichTextRun;
1024 uint8_t mNextRunContextInfo;
1025 uint8_t mCurrentRunContextInfo;
1028 static nsIFrame*
1029 FindLineContainer(nsIFrame* aFrame)
1031 while (aFrame && (aFrame->IsFrameOfType(nsIFrame::eLineParticipant) ||
1032 aFrame->CanContinueTextRun())) {
1033 aFrame = aFrame->GetParent();
1035 return aFrame;
1038 static bool
1039 IsLineBreakingWhiteSpace(char16_t aChar)
1041 // 0x0A (\n) is not handled as white-space by the line breaker, since
1042 // we break before it, if it isn't transformed to a normal space.
1043 // (If we treat it as normal white-space then we'd only break after it.)
1044 // However, it does induce a line break or is converted to a regular
1045 // space, and either way it can be used to bound the region of text
1046 // that needs to be analyzed for line breaking.
1047 return nsLineBreaker::IsSpace(aChar) || aChar == 0x0A;
1050 static bool
1051 TextContainsLineBreakerWhiteSpace(const void* aText, uint32_t aLength,
1052 bool aIsDoubleByte)
1054 if (aIsDoubleByte) {
1055 const char16_t* chars = static_cast<const char16_t*>(aText);
1056 for (uint32_t i = 0; i < aLength; ++i) {
1057 if (IsLineBreakingWhiteSpace(chars[i]))
1058 return true;
1060 return false;
1061 } else {
1062 const uint8_t* chars = static_cast<const uint8_t*>(aText);
1063 for (uint32_t i = 0; i < aLength; ++i) {
1064 if (IsLineBreakingWhiteSpace(chars[i]))
1065 return true;
1067 return false;
1071 struct FrameTextTraversal {
1072 // These fields identify which frames should be recursively scanned
1073 // The first normal frame to scan (or null, if no such frame should be scanned)
1074 nsIFrame* mFrameToScan;
1075 // The first overflow frame to scan (or null, if no such frame should be scanned)
1076 nsIFrame* mOverflowFrameToScan;
1077 // Whether to scan the siblings of mFrameToDescendInto/mOverflowFrameToDescendInto
1078 bool mScanSiblings;
1080 // These identify the boundaries of the context required for
1081 // line breaking or textrun construction
1082 bool mLineBreakerCanCrossFrameBoundary;
1083 bool mTextRunCanCrossFrameBoundary;
1085 nsIFrame* NextFrameToScan() {
1086 nsIFrame* f;
1087 if (mFrameToScan) {
1088 f = mFrameToScan;
1089 mFrameToScan = mScanSiblings ? f->GetNextSibling() : nullptr;
1090 } else if (mOverflowFrameToScan) {
1091 f = mOverflowFrameToScan;
1092 mOverflowFrameToScan = mScanSiblings ? f->GetNextSibling() : nullptr;
1093 } else {
1094 f = nullptr;
1096 return f;
1100 static FrameTextTraversal
1101 CanTextCrossFrameBoundary(nsIFrame* aFrame, nsIAtom* aType)
1103 NS_ASSERTION(aType == aFrame->GetType(), "Wrong type");
1105 FrameTextTraversal result;
1107 bool continuesTextRun = aFrame->CanContinueTextRun();
1108 if (aType == nsGkAtoms::placeholderFrame) {
1109 // placeholders are "invisible", so a text run should be able to span
1110 // across one. But don't descend into the out-of-flow.
1111 result.mLineBreakerCanCrossFrameBoundary = true;
1112 result.mOverflowFrameToScan = nullptr;
1113 if (continuesTextRun) {
1114 // ... Except for first-letter floats, which are really in-flow
1115 // from the point of view of capitalization etc, so we'd better
1116 // descend into them. But we actually need to break the textrun for
1117 // first-letter floats since things look bad if, say, we try to make a
1118 // ligature across the float boundary.
1119 result.mFrameToScan =
1120 (static_cast<nsPlaceholderFrame*>(aFrame))->GetOutOfFlowFrame();
1121 result.mScanSiblings = false;
1122 result.mTextRunCanCrossFrameBoundary = false;
1123 } else {
1124 result.mFrameToScan = nullptr;
1125 result.mTextRunCanCrossFrameBoundary = true;
1127 } else {
1128 if (continuesTextRun) {
1129 result.mFrameToScan = aFrame->GetFirstPrincipalChild();
1130 result.mOverflowFrameToScan =
1131 aFrame->GetFirstChild(nsIFrame::kOverflowList);
1132 NS_WARN_IF_FALSE(!result.mOverflowFrameToScan,
1133 "Scanning overflow inline frames is something we should avoid");
1134 result.mScanSiblings = true;
1135 result.mTextRunCanCrossFrameBoundary = true;
1136 result.mLineBreakerCanCrossFrameBoundary = true;
1137 } else if (aFrame->GetType() == nsGkAtoms::rubyTextFrame ||
1138 aFrame->GetType() == nsGkAtoms::rubyTextContainerFrame) {
1139 result.mFrameToScan = aFrame->GetFirstPrincipalChild();
1140 result.mOverflowFrameToScan =
1141 aFrame->GetFirstChild(nsIFrame::kOverflowList);
1142 NS_WARN_IF_FALSE(!result.mOverflowFrameToScan,
1143 "Scanning overflow inline frames is something we should avoid");
1144 result.mScanSiblings = true;
1145 result.mTextRunCanCrossFrameBoundary = false;
1146 result.mLineBreakerCanCrossFrameBoundary = false;
1147 } else {
1148 result.mFrameToScan = nullptr;
1149 result.mOverflowFrameToScan = nullptr;
1150 result.mTextRunCanCrossFrameBoundary = false;
1151 result.mLineBreakerCanCrossFrameBoundary = false;
1154 return result;
1157 BuildTextRunsScanner::FindBoundaryResult
1158 BuildTextRunsScanner::FindBoundaries(nsIFrame* aFrame, FindBoundaryState* aState)
1160 nsIAtom* frameType = aFrame->GetType();
1161 nsTextFrame* textFrame = frameType == nsGkAtoms::textFrame
1162 ? static_cast<nsTextFrame*>(aFrame) : nullptr;
1163 if (textFrame) {
1164 if (aState->mLastTextFrame &&
1165 textFrame != aState->mLastTextFrame->GetNextInFlow() &&
1166 !ContinueTextRunAcrossFrames(aState->mLastTextFrame, textFrame)) {
1167 aState->mSeenTextRunBoundaryOnThisLine = true;
1168 if (aState->mSeenSpaceForLineBreakingOnThisLine)
1169 return FB_FOUND_VALID_TEXTRUN_BOUNDARY;
1171 if (!aState->mFirstTextFrame) {
1172 aState->mFirstTextFrame = textFrame;
1174 aState->mLastTextFrame = textFrame;
1177 if (aFrame == aState->mStopAtFrame)
1178 return FB_STOPPED_AT_STOP_FRAME;
1180 if (textFrame) {
1181 if (!aState->mSeenSpaceForLineBreakingOnThisLine) {
1182 const nsTextFragment* frag = textFrame->GetContent()->GetText();
1183 uint32_t start = textFrame->GetContentOffset();
1184 const void* text = frag->Is2b()
1185 ? static_cast<const void*>(frag->Get2b() + start)
1186 : static_cast<const void*>(frag->Get1b() + start);
1187 if (TextContainsLineBreakerWhiteSpace(text, textFrame->GetContentLength(),
1188 frag->Is2b())) {
1189 aState->mSeenSpaceForLineBreakingOnThisLine = true;
1190 if (aState->mSeenTextRunBoundaryOnLaterLine)
1191 return FB_FOUND_VALID_TEXTRUN_BOUNDARY;
1194 return FB_CONTINUE;
1197 FrameTextTraversal traversal =
1198 CanTextCrossFrameBoundary(aFrame, frameType);
1199 if (!traversal.mTextRunCanCrossFrameBoundary) {
1200 aState->mSeenTextRunBoundaryOnThisLine = true;
1201 if (aState->mSeenSpaceForLineBreakingOnThisLine)
1202 return FB_FOUND_VALID_TEXTRUN_BOUNDARY;
1205 for (nsIFrame* f = traversal.NextFrameToScan(); f;
1206 f = traversal.NextFrameToScan()) {
1207 FindBoundaryResult result = FindBoundaries(f, aState);
1208 if (result != FB_CONTINUE)
1209 return result;
1212 if (!traversal.mTextRunCanCrossFrameBoundary) {
1213 aState->mSeenTextRunBoundaryOnThisLine = true;
1214 if (aState->mSeenSpaceForLineBreakingOnThisLine)
1215 return FB_FOUND_VALID_TEXTRUN_BOUNDARY;
1218 return FB_CONTINUE;
1221 // build text runs for the 200 lines following aForFrame, and stop after that
1222 // when we get a chance.
1223 #define NUM_LINES_TO_BUILD_TEXT_RUNS 200
1226 * General routine for building text runs. This is hairy because of the need
1227 * to build text runs that span content nodes.
1229 * @param aContext The gfxContext we're using to construct this text run.
1230 * @param aForFrame The nsTextFrame for which we're building this text run.
1231 * @param aLineContainer the line container containing aForFrame; if null,
1232 * we'll walk the ancestors to find it. It's required to be non-null
1233 * when aForFrameLine is non-null.
1234 * @param aForFrameLine the line containing aForFrame; if null, we'll figure
1235 * out the line (slowly)
1236 * @param aWhichTextRun The type of text run we want to build. If font inflation
1237 * is enabled, this will be eInflated, otherwise it's eNotInflated.
1239 static void
1240 BuildTextRuns(gfxContext* aContext, nsTextFrame* aForFrame,
1241 nsIFrame* aLineContainer,
1242 const nsLineList::iterator* aForFrameLine,
1243 nsTextFrame::TextRunType aWhichTextRun)
1245 NS_ASSERTION(aForFrame || aLineContainer,
1246 "One of aForFrame or aLineContainer must be set!");
1247 NS_ASSERTION(!aForFrameLine || aLineContainer,
1248 "line but no line container");
1250 nsIFrame* lineContainerChild = aForFrame;
1251 if (!aLineContainer) {
1252 if (aForFrame->IsFloatingFirstLetterChild()) {
1253 lineContainerChild = aForFrame->PresContext()->PresShell()->
1254 GetPlaceholderFrameFor(aForFrame->GetParent());
1256 aLineContainer = FindLineContainer(lineContainerChild);
1257 } else {
1258 NS_ASSERTION(!aForFrame ||
1259 (aLineContainer == FindLineContainer(aForFrame) ||
1260 aLineContainer->GetType() == nsGkAtoms::rubyTextContainerFrame ||
1261 (aLineContainer->GetType() == nsGkAtoms::letterFrame &&
1262 aLineContainer->IsFloating())),
1263 "Wrong line container hint");
1266 if (aForFrame) {
1267 if (aForFrame->HasAnyStateBits(TEXT_IS_IN_TOKEN_MATHML)) {
1268 aLineContainer->AddStateBits(TEXT_IS_IN_TOKEN_MATHML);
1269 if (aForFrame->HasAnyStateBits(NS_FRAME_IS_IN_SINGLE_CHAR_MI)) {
1270 aLineContainer->AddStateBits(NS_FRAME_IS_IN_SINGLE_CHAR_MI);
1273 if (aForFrame->HasAnyStateBits(NS_FRAME_MATHML_SCRIPT_DESCENDANT)) {
1274 aLineContainer->AddStateBits(NS_FRAME_MATHML_SCRIPT_DESCENDANT);
1278 nsPresContext* presContext = aLineContainer->PresContext();
1279 BuildTextRunsScanner scanner(presContext, aContext, aLineContainer,
1280 aWhichTextRun);
1282 nsBlockFrame* block = nsLayoutUtils::GetAsBlock(aLineContainer);
1284 if (!block) {
1285 NS_ASSERTION(!aLineContainer->GetPrevInFlow() && !aLineContainer->GetNextInFlow(),
1286 "Breakable non-block line containers not supported");
1287 // Just loop through all the children of the linecontainer ... it's really
1288 // just one line
1289 scanner.SetAtStartOfLine();
1290 scanner.SetCommonAncestorWithLastFrame(nullptr);
1291 nsIFrame* child = aLineContainer->GetFirstPrincipalChild();
1292 while (child) {
1293 scanner.ScanFrame(child);
1294 child = child->GetNextSibling();
1296 // Set mStartOfLine so FlushFrames knows its textrun ends a line
1297 scanner.SetAtStartOfLine();
1298 scanner.FlushFrames(true, false);
1299 return;
1302 // Find the line containing 'lineContainerChild'.
1304 bool isValid = true;
1305 nsBlockInFlowLineIterator backIterator(block, &isValid);
1306 if (aForFrameLine) {
1307 backIterator = nsBlockInFlowLineIterator(block, *aForFrameLine);
1308 } else {
1309 backIterator = nsBlockInFlowLineIterator(block, lineContainerChild, &isValid);
1310 NS_ASSERTION(isValid, "aForFrame not found in block, someone lied to us");
1311 NS_ASSERTION(backIterator.GetContainer() == block,
1312 "Someone lied to us about the block");
1314 nsBlockFrame::line_iterator startLine = backIterator.GetLine();
1316 // Find a line where we can start building text runs. We choose the last line
1317 // where:
1318 // -- there is a textrun boundary between the start of the line and the
1319 // start of aForFrame
1320 // -- there is a space between the start of the line and the textrun boundary
1321 // (this is so we can be sure the line breaks will be set properly
1322 // on the textruns we construct).
1323 // The possibly-partial text runs up to and including the first space
1324 // are not reconstructed. We construct partial text runs for that text ---
1325 // for the sake of simplifying the code and feeding the linebreaker ---
1326 // but we discard them instead of assigning them to frames.
1327 // This is a little awkward because we traverse lines in the reverse direction
1328 // but we traverse the frames in each line in the forward direction.
1329 nsBlockInFlowLineIterator forwardIterator = backIterator;
1330 nsIFrame* stopAtFrame = lineContainerChild;
1331 nsTextFrame* nextLineFirstTextFrame = nullptr;
1332 bool seenTextRunBoundaryOnLaterLine = false;
1333 bool mayBeginInTextRun = true;
1334 while (true) {
1335 forwardIterator = backIterator;
1336 nsBlockFrame::line_iterator line = backIterator.GetLine();
1337 if (!backIterator.Prev() || backIterator.GetLine()->IsBlock()) {
1338 mayBeginInTextRun = false;
1339 break;
1342 BuildTextRunsScanner::FindBoundaryState state = { stopAtFrame, nullptr, nullptr,
1343 bool(seenTextRunBoundaryOnLaterLine), false, false };
1344 nsIFrame* child = line->mFirstChild;
1345 bool foundBoundary = false;
1346 for (int32_t i = line->GetChildCount() - 1; i >= 0; --i) {
1347 BuildTextRunsScanner::FindBoundaryResult result =
1348 scanner.FindBoundaries(child, &state);
1349 if (result == BuildTextRunsScanner::FB_FOUND_VALID_TEXTRUN_BOUNDARY) {
1350 foundBoundary = true;
1351 break;
1352 } else if (result == BuildTextRunsScanner::FB_STOPPED_AT_STOP_FRAME) {
1353 break;
1355 child = child->GetNextSibling();
1357 if (foundBoundary)
1358 break;
1359 if (!stopAtFrame && state.mLastTextFrame && nextLineFirstTextFrame &&
1360 !scanner.ContinueTextRunAcrossFrames(state.mLastTextFrame, nextLineFirstTextFrame)) {
1361 // Found a usable textrun boundary at the end of the line
1362 if (state.mSeenSpaceForLineBreakingOnThisLine)
1363 break;
1364 seenTextRunBoundaryOnLaterLine = true;
1365 } else if (state.mSeenTextRunBoundaryOnThisLine) {
1366 seenTextRunBoundaryOnLaterLine = true;
1368 stopAtFrame = nullptr;
1369 if (state.mFirstTextFrame) {
1370 nextLineFirstTextFrame = state.mFirstTextFrame;
1373 scanner.SetSkipIncompleteTextRuns(mayBeginInTextRun);
1375 // Now iterate over all text frames starting from the current line. First-in-flow
1376 // text frames will be accumulated into textRunFrames as we go. When a
1377 // text run boundary is required we flush textRunFrames ((re)building their
1378 // gfxTextRuns as necessary).
1379 bool seenStartLine = false;
1380 uint32_t linesAfterStartLine = 0;
1381 do {
1382 nsBlockFrame::line_iterator line = forwardIterator.GetLine();
1383 if (line->IsBlock())
1384 break;
1385 line->SetInvalidateTextRuns(false);
1386 scanner.SetAtStartOfLine();
1387 scanner.SetCommonAncestorWithLastFrame(nullptr);
1388 nsIFrame* child = line->mFirstChild;
1389 for (int32_t i = line->GetChildCount() - 1; i >= 0; --i) {
1390 scanner.ScanFrame(child);
1391 child = child->GetNextSibling();
1393 if (line.get() == startLine.get()) {
1394 seenStartLine = true;
1396 if (seenStartLine) {
1397 ++linesAfterStartLine;
1398 if (linesAfterStartLine >= NUM_LINES_TO_BUILD_TEXT_RUNS && scanner.CanStopOnThisLine()) {
1399 // Don't flush frames; we may be in the middle of a textrun
1400 // that we can't end here. That's OK, we just won't build it.
1401 // Note that we must already have finished the textrun for aForFrame,
1402 // because we've seen the end of a textrun in a line after the line
1403 // containing aForFrame.
1404 scanner.FlushLineBreaks(nullptr);
1405 // This flushes out mMappedFlows and mLineBreakBeforeFrames, which
1406 // silences assertions in the scanner destructor.
1407 scanner.ResetRunInfo();
1408 return;
1411 } while (forwardIterator.Next());
1413 // Set mStartOfLine so FlushFrames knows its textrun ends a line
1414 scanner.SetAtStartOfLine();
1415 scanner.FlushFrames(true, false);
1418 static char16_t*
1419 ExpandBuffer(char16_t* aDest, uint8_t* aSrc, uint32_t aCount)
1421 while (aCount) {
1422 *aDest = *aSrc;
1423 ++aDest;
1424 ++aSrc;
1425 --aCount;
1427 return aDest;
1430 bool BuildTextRunsScanner::IsTextRunValidForMappedFlows(gfxTextRun* aTextRun)
1432 if (aTextRun->GetFlags() & nsTextFrameUtils::TEXT_IS_SIMPLE_FLOW)
1433 return mMappedFlows.Length() == 1 &&
1434 mMappedFlows[0].mStartFrame == static_cast<nsTextFrame*>(aTextRun->GetUserData()) &&
1435 mMappedFlows[0].mEndFrame == nullptr;
1437 TextRunUserData* userData = static_cast<TextRunUserData*>(aTextRun->GetUserData());
1438 if (userData->mMappedFlowCount != mMappedFlows.Length())
1439 return false;
1440 for (uint32_t i = 0; i < mMappedFlows.Length(); ++i) {
1441 if (userData->mMappedFlows[i].mStartFrame != mMappedFlows[i].mStartFrame ||
1442 int32_t(userData->mMappedFlows[i].mContentLength) !=
1443 mMappedFlows[i].GetContentEnd() - mMappedFlows[i].mStartFrame->GetContentOffset())
1444 return false;
1446 return true;
1450 * This gets called when we need to make a text run for the current list of
1451 * frames.
1453 void BuildTextRunsScanner::FlushFrames(bool aFlushLineBreaks, bool aSuppressTrailingBreak)
1455 gfxTextRun* textRun = nullptr;
1456 if (!mMappedFlows.IsEmpty()) {
1457 if (!mSkipIncompleteTextRuns && mCurrentFramesAllSameTextRun &&
1458 ((mCurrentFramesAllSameTextRun->GetFlags() & nsTextFrameUtils::TEXT_INCOMING_WHITESPACE) != 0) ==
1459 ((mCurrentRunContextInfo & nsTextFrameUtils::INCOMING_WHITESPACE) != 0) &&
1460 ((mCurrentFramesAllSameTextRun->GetFlags() & gfxTextRunFactory::TEXT_INCOMING_ARABICCHAR) != 0) ==
1461 ((mCurrentRunContextInfo & nsTextFrameUtils::INCOMING_ARABICCHAR) != 0) &&
1462 IsTextRunValidForMappedFlows(mCurrentFramesAllSameTextRun)) {
1463 // Optimization: We do not need to (re)build the textrun.
1464 textRun = mCurrentFramesAllSameTextRun;
1466 // Feed this run's text into the linebreaker to provide context.
1467 if (!SetupLineBreakerContext(textRun)) {
1468 return;
1471 // Update mNextRunContextInfo appropriately
1472 mNextRunContextInfo = nsTextFrameUtils::INCOMING_NONE;
1473 if (textRun->GetFlags() & nsTextFrameUtils::TEXT_TRAILING_WHITESPACE) {
1474 mNextRunContextInfo |= nsTextFrameUtils::INCOMING_WHITESPACE;
1476 if (textRun->GetFlags() & gfxTextRunFactory::TEXT_TRAILING_ARABICCHAR) {
1477 mNextRunContextInfo |= nsTextFrameUtils::INCOMING_ARABICCHAR;
1479 } else {
1480 AutoFallibleTArray<uint8_t,BIG_TEXT_NODE_SIZE> buffer;
1481 uint32_t bufferSize = mMaxTextLength*(mDoubleByteText ? 2 : 1);
1482 if (bufferSize < mMaxTextLength || bufferSize == UINT32_MAX ||
1483 !buffer.AppendElements(bufferSize)) {
1484 return;
1486 textRun = BuildTextRunForFrames(buffer.Elements());
1490 if (aFlushLineBreaks) {
1491 FlushLineBreaks(aSuppressTrailingBreak ? nullptr : textRun);
1494 mCanStopOnThisLine = true;
1495 ResetRunInfo();
1498 void BuildTextRunsScanner::FlushLineBreaks(gfxTextRun* aTrailingTextRun)
1500 bool trailingLineBreak;
1501 nsresult rv = mLineBreaker.Reset(&trailingLineBreak);
1502 // textRun may be null for various reasons, including because we constructed
1503 // a partial textrun just to get the linebreaker and other state set up
1504 // to build the next textrun.
1505 if (NS_SUCCEEDED(rv) && trailingLineBreak && aTrailingTextRun) {
1506 aTrailingTextRun->SetFlagBits(nsTextFrameUtils::TEXT_HAS_TRAILING_BREAK);
1509 for (uint32_t i = 0; i < mBreakSinks.Length(); ++i) {
1510 if (!mBreakSinks[i]->mExistingTextRun || mBreakSinks[i]->mChangedBreaks) {
1511 // TODO cause frames associated with the textrun to be reflowed, if they
1512 // aren't being reflowed already!
1514 mBreakSinks[i]->Finish(mMissingFonts);
1516 mBreakSinks.Clear();
1518 for (uint32_t i = 0; i < mTextRunsToDelete.Length(); ++i) {
1519 gfxTextRun* deleteTextRun = mTextRunsToDelete[i];
1520 gTextRuns->RemoveFromCache(deleteTextRun);
1521 delete deleteTextRun;
1523 mTextRunsToDelete.Clear();
1526 void BuildTextRunsScanner::AccumulateRunInfo(nsTextFrame* aFrame)
1528 if (mMaxTextLength != UINT32_MAX) {
1529 NS_ASSERTION(mMaxTextLength < UINT32_MAX - aFrame->GetContentLength(), "integer overflow");
1530 if (mMaxTextLength >= UINT32_MAX - aFrame->GetContentLength()) {
1531 mMaxTextLength = UINT32_MAX;
1532 } else {
1533 mMaxTextLength += aFrame->GetContentLength();
1536 mDoubleByteText |= aFrame->GetContent()->GetText()->Is2b();
1537 mLastFrame = aFrame;
1538 mCommonAncestorWithLastFrame = aFrame->GetParent();
1540 MappedFlow* mappedFlow = &mMappedFlows[mMappedFlows.Length() - 1];
1541 NS_ASSERTION(mappedFlow->mStartFrame == aFrame ||
1542 mappedFlow->GetContentEnd() == aFrame->GetContentOffset(),
1543 "Overlapping or discontiguous frames => BAD");
1544 mappedFlow->mEndFrame = static_cast<nsTextFrame*>(aFrame->GetNextContinuation());
1545 if (mCurrentFramesAllSameTextRun != aFrame->GetTextRun(mWhichTextRun)) {
1546 mCurrentFramesAllSameTextRun = nullptr;
1549 if (mStartOfLine) {
1550 mLineBreakBeforeFrames.AppendElement(aFrame);
1551 mStartOfLine = false;
1555 static nscoord StyleToCoord(const nsStyleCoord& aCoord)
1557 if (eStyleUnit_Coord == aCoord.GetUnit()) {
1558 return aCoord.GetCoordValue();
1559 } else {
1560 return 0;
1564 static bool
1565 HasTerminalNewline(const nsTextFrame* aFrame)
1567 if (aFrame->GetContentLength() == 0)
1568 return false;
1569 const nsTextFragment* frag = aFrame->GetContent()->GetText();
1570 return frag->CharAt(aFrame->GetContentEnd() - 1) == '\n';
1573 static nscoord
1574 LetterSpacing(nsIFrame* aFrame, const nsStyleText* aStyleText = nullptr)
1576 if (aFrame->IsSVGText()) {
1577 return 0;
1579 if (!aStyleText) {
1580 aStyleText = aFrame->StyleText();
1582 return StyleToCoord(aStyleText->mLetterSpacing);
1585 static nscoord
1586 WordSpacing(nsIFrame* aFrame, const nsStyleText* aStyleText = nullptr)
1588 if (aFrame->IsSVGText()) {
1589 return 0;
1591 if (!aStyleText) {
1592 aStyleText = aFrame->StyleText();
1594 return aStyleText->mWordSpacing;
1597 bool
1598 BuildTextRunsScanner::ContinueTextRunAcrossFrames(nsTextFrame* aFrame1, nsTextFrame* aFrame2)
1600 // We don't need to check font size inflation, since
1601 // |FindLineContainer| above (via |nsIFrame::CanContinueTextRun|)
1602 // ensures that text runs never cross block boundaries. This means
1603 // that the font size inflation on all text frames in the text run is
1604 // already guaranteed to be the same as each other (and for the line
1605 // container).
1606 if (mBidiEnabled &&
1607 (NS_GET_EMBEDDING_LEVEL(aFrame1) != NS_GET_EMBEDDING_LEVEL(aFrame2) ||
1608 NS_GET_PARAGRAPH_DEPTH(aFrame1) != NS_GET_PARAGRAPH_DEPTH(aFrame2)))
1609 return false;
1611 nsStyleContext* sc1 = aFrame1->StyleContext();
1612 const nsStyleText* textStyle1 = sc1->StyleText();
1613 // If the first frame ends in a preformatted newline, then we end the textrun
1614 // here. This avoids creating giant textruns for an entire plain text file.
1615 // Note that we create a single text frame for a preformatted text node,
1616 // even if it has newlines in it, so typically we won't see trailing newlines
1617 // until after reflow has broken up the frame into one (or more) frames per
1618 // line. That's OK though.
1619 if (textStyle1->NewlineIsSignificant() && HasTerminalNewline(aFrame1))
1620 return false;
1622 if (aFrame1->GetContent() == aFrame2->GetContent() &&
1623 aFrame1->GetNextInFlow() != aFrame2) {
1624 // aFrame2 must be a non-fluid continuation of aFrame1. This can happen
1625 // sometimes when the unicode-bidi property is used; the bidi resolver
1626 // breaks text into different frames even though the text has the same
1627 // direction. We can't allow these two frames to share the same textrun
1628 // because that would violate our invariant that two flows in the same
1629 // textrun have different content elements.
1630 return false;
1633 nsStyleContext* sc2 = aFrame2->StyleContext();
1634 const nsStyleText* textStyle2 = sc2->StyleText();
1635 if (sc1 == sc2)
1636 return true;
1638 const nsStyleFont* fontStyle1 = sc1->StyleFont();
1639 const nsStyleFont* fontStyle2 = sc2->StyleFont();
1640 nscoord letterSpacing1 = LetterSpacing(aFrame1);
1641 nscoord letterSpacing2 = LetterSpacing(aFrame2);
1642 return fontStyle1->mFont.BaseEquals(fontStyle2->mFont) &&
1643 sc1->StyleFont()->mLanguage == sc2->StyleFont()->mLanguage &&
1644 textStyle1->mTextTransform == textStyle2->mTextTransform &&
1645 nsLayoutUtils::GetTextRunFlagsForStyle(sc1, fontStyle1, textStyle1, letterSpacing1) ==
1646 nsLayoutUtils::GetTextRunFlagsForStyle(sc2, fontStyle2, textStyle2, letterSpacing2);
1649 void BuildTextRunsScanner::ScanFrame(nsIFrame* aFrame)
1651 // First check if we can extend the current mapped frame block. This is common.
1652 if (mMappedFlows.Length() > 0) {
1653 MappedFlow* mappedFlow = &mMappedFlows[mMappedFlows.Length() - 1];
1654 if (mappedFlow->mEndFrame == aFrame &&
1655 (aFrame->GetStateBits() & NS_FRAME_IS_FLUID_CONTINUATION)) {
1656 NS_ASSERTION(aFrame->GetType() == nsGkAtoms::textFrame,
1657 "Flow-sibling of a text frame is not a text frame?");
1659 // Don't do this optimization if mLastFrame has a terminal newline...
1660 // it's quite likely preformatted and we might want to end the textrun here.
1661 // This is almost always true:
1662 if (mLastFrame->StyleContext() == aFrame->StyleContext() &&
1663 !HasTerminalNewline(mLastFrame)) {
1664 AccumulateRunInfo(static_cast<nsTextFrame*>(aFrame));
1665 return;
1670 nsIAtom* frameType = aFrame->GetType();
1671 // Now see if we can add a new set of frames to the current textrun
1672 if (frameType == nsGkAtoms::textFrame) {
1673 nsTextFrame* frame = static_cast<nsTextFrame*>(aFrame);
1675 if (mLastFrame) {
1676 if (!ContinueTextRunAcrossFrames(mLastFrame, frame)) {
1677 FlushFrames(false, false);
1678 } else {
1679 if (mLastFrame->GetContent() == frame->GetContent()) {
1680 AccumulateRunInfo(frame);
1681 return;
1686 MappedFlow* mappedFlow = mMappedFlows.AppendElement();
1687 if (!mappedFlow)
1688 return;
1690 mappedFlow->mStartFrame = frame;
1691 mappedFlow->mAncestorControllingInitialBreak = mCommonAncestorWithLastFrame;
1693 AccumulateRunInfo(frame);
1694 if (mMappedFlows.Length() == 1) {
1695 mCurrentFramesAllSameTextRun = frame->GetTextRun(mWhichTextRun);
1696 mCurrentRunContextInfo = mNextRunContextInfo;
1698 return;
1701 FrameTextTraversal traversal =
1702 CanTextCrossFrameBoundary(aFrame, frameType);
1703 bool isBR = frameType == nsGkAtoms::brFrame;
1704 if (!traversal.mLineBreakerCanCrossFrameBoundary) {
1705 // BR frames are special. We do not need or want to record a break opportunity
1706 // before a BR frame.
1707 FlushFrames(true, isBR);
1708 mCommonAncestorWithLastFrame = aFrame;
1709 mNextRunContextInfo &= ~nsTextFrameUtils::INCOMING_WHITESPACE;
1710 mStartOfLine = false;
1711 } else if (!traversal.mTextRunCanCrossFrameBoundary) {
1712 FlushFrames(false, false);
1715 for (nsIFrame* f = traversal.NextFrameToScan(); f;
1716 f = traversal.NextFrameToScan()) {
1717 ScanFrame(f);
1720 if (!traversal.mLineBreakerCanCrossFrameBoundary) {
1721 // Really if we're a BR frame this is unnecessary since descendInto will be
1722 // false. In fact this whole "if" statement should move into the descendInto.
1723 FlushFrames(true, isBR);
1724 mCommonAncestorWithLastFrame = aFrame;
1725 mNextRunContextInfo &= ~nsTextFrameUtils::INCOMING_WHITESPACE;
1726 } else if (!traversal.mTextRunCanCrossFrameBoundary) {
1727 FlushFrames(false, false);
1730 LiftCommonAncestorWithLastFrameToParent(aFrame->GetParent());
1733 nsTextFrame*
1734 BuildTextRunsScanner::GetNextBreakBeforeFrame(uint32_t* aIndex)
1736 uint32_t index = *aIndex;
1737 if (index >= mLineBreakBeforeFrames.Length())
1738 return nullptr;
1739 *aIndex = index + 1;
1740 return static_cast<nsTextFrame*>(mLineBreakBeforeFrames.ElementAt(index));
1743 static uint32_t
1744 GetSpacingFlags(nscoord spacing)
1746 return spacing ? gfxTextRunFactory::TEXT_ENABLE_SPACING : 0;
1749 static gfxFontGroup*
1750 GetFontGroupForFrame(nsIFrame* aFrame, float aFontSizeInflation,
1751 nsFontMetrics** aOutFontMetrics = nullptr)
1753 if (aOutFontMetrics)
1754 *aOutFontMetrics = nullptr;
1756 nsRefPtr<nsFontMetrics> metrics;
1757 nsLayoutUtils::GetFontMetricsForFrame(aFrame, getter_AddRefs(metrics),
1758 aFontSizeInflation);
1760 if (!metrics)
1761 return nullptr;
1763 if (aOutFontMetrics) {
1764 *aOutFontMetrics = metrics;
1765 NS_ADDREF(*aOutFontMetrics);
1767 // XXX this is a bit bogus, we're releasing 'metrics' so the
1768 // returned font-group might actually be torn down, although because
1769 // of the way the device context caches font metrics, this seems to
1770 // not actually happen. But we should fix this.
1771 return metrics->GetThebesFontGroup();
1774 static already_AddRefed<gfxContext>
1775 CreateReferenceThebesContext(nsTextFrame* aTextFrame)
1777 return aTextFrame->PresContext()->PresShell()->CreateReferenceRenderingContext();
1781 * The returned textrun must be deleted when no longer needed.
1783 static gfxTextRun*
1784 GetHyphenTextRun(gfxTextRun* aTextRun, gfxContext* aContext, nsTextFrame* aTextFrame)
1786 nsRefPtr<gfxContext> ctx = aContext;
1787 if (!ctx) {
1788 ctx = CreateReferenceThebesContext(aTextFrame);
1790 if (!ctx)
1791 return nullptr;
1793 return aTextRun->GetFontGroup()->
1794 MakeHyphenTextRun(ctx, aTextRun->GetAppUnitsPerDevUnit());
1797 static gfxFont::Metrics
1798 GetFirstFontMetrics(gfxFontGroup* aFontGroup, bool aVerticalMetrics)
1800 if (!aFontGroup)
1801 return gfxFont::Metrics();
1802 gfxFont* font = aFontGroup->GetFirstValidFont();
1803 return font->GetMetrics(aVerticalMetrics ? gfxFont::eVertical
1804 : gfxFont::eHorizontal);
1807 PR_STATIC_ASSERT(NS_STYLE_WHITESPACE_NORMAL == 0);
1808 PR_STATIC_ASSERT(NS_STYLE_WHITESPACE_PRE == 1);
1809 PR_STATIC_ASSERT(NS_STYLE_WHITESPACE_NOWRAP == 2);
1810 PR_STATIC_ASSERT(NS_STYLE_WHITESPACE_PRE_WRAP == 3);
1811 PR_STATIC_ASSERT(NS_STYLE_WHITESPACE_PRE_LINE == 4);
1812 PR_STATIC_ASSERT(NS_STYLE_WHITESPACE_PRE_SPACE == 5);
1814 static const nsTextFrameUtils::CompressionMode CSSWhitespaceToCompressionMode[] =
1816 nsTextFrameUtils::COMPRESS_WHITESPACE_NEWLINE, // normal
1817 nsTextFrameUtils::COMPRESS_NONE, // pre
1818 nsTextFrameUtils::COMPRESS_WHITESPACE_NEWLINE, // nowrap
1819 nsTextFrameUtils::COMPRESS_NONE, // pre-wrap
1820 nsTextFrameUtils::COMPRESS_WHITESPACE, // pre-line
1821 nsTextFrameUtils::COMPRESS_NONE_TRANSFORM_TO_SPACE // -moz-pre-space
1824 gfxTextRun*
1825 BuildTextRunsScanner::BuildTextRunForFrames(void* aTextBuffer)
1827 gfxSkipChars skipChars;
1829 const void* textPtr = aTextBuffer;
1830 bool anyTextTransformStyle = false;
1831 bool anyMathMLStyling = false;
1832 uint8_t sstyScriptLevel = 0;
1833 uint32_t mathFlags = 0;
1834 uint32_t textFlags = nsTextFrameUtils::TEXT_NO_BREAKS;
1836 if (mCurrentRunContextInfo & nsTextFrameUtils::INCOMING_WHITESPACE) {
1837 textFlags |= nsTextFrameUtils::TEXT_INCOMING_WHITESPACE;
1839 if (mCurrentRunContextInfo & nsTextFrameUtils::INCOMING_ARABICCHAR) {
1840 textFlags |= gfxTextRunFactory::TEXT_INCOMING_ARABICCHAR;
1843 nsAutoTArray<int32_t,50> textBreakPoints;
1844 TextRunUserData dummyData;
1845 TextRunMappedFlow dummyMappedFlow;
1847 TextRunUserData* userData;
1848 TextRunUserData* userDataToDestroy;
1849 // If the situation is particularly simple (and common) we don't need to
1850 // allocate userData.
1851 if (mMappedFlows.Length() == 1 && !mMappedFlows[0].mEndFrame &&
1852 mMappedFlows[0].mStartFrame->GetContentOffset() == 0) {
1853 userData = &dummyData;
1854 userDataToDestroy = nullptr;
1855 dummyData.mMappedFlows = &dummyMappedFlow;
1856 } else {
1857 userData = static_cast<TextRunUserData*>
1858 (nsMemory::Alloc(sizeof(TextRunUserData) + mMappedFlows.Length()*sizeof(TextRunMappedFlow)));
1859 userDataToDestroy = userData;
1860 userData->mMappedFlows = reinterpret_cast<TextRunMappedFlow*>(userData + 1);
1862 userData->mMappedFlowCount = mMappedFlows.Length();
1863 userData->mLastFlowIndex = 0;
1865 uint32_t currentTransformedTextOffset = 0;
1867 uint32_t nextBreakIndex = 0;
1868 nsTextFrame* nextBreakBeforeFrame = GetNextBreakBeforeFrame(&nextBreakIndex);
1869 bool isSVG = mLineContainer->IsSVGText();
1870 bool enabledJustification = mLineContainer &&
1871 (mLineContainer->StyleText()->mTextAlign == NS_STYLE_TEXT_ALIGN_JUSTIFY ||
1872 mLineContainer->StyleText()->mTextAlignLast == NS_STYLE_TEXT_ALIGN_JUSTIFY);
1874 // for word-break style
1875 switch (mLineContainer->StyleText()->mWordBreak) {
1876 case NS_STYLE_WORDBREAK_BREAK_ALL:
1877 mLineBreaker.SetWordBreak(nsILineBreaker::kWordBreak_BreakAll);
1878 break;
1879 case NS_STYLE_WORDBREAK_KEEP_ALL:
1880 mLineBreaker.SetWordBreak(nsILineBreaker::kWordBreak_KeepAll);
1881 break;
1882 default:
1883 mLineBreaker.SetWordBreak(nsILineBreaker::kWordBreak_Normal);
1884 break;
1887 const nsStyleText* textStyle = nullptr;
1888 const nsStyleFont* fontStyle = nullptr;
1889 nsStyleContext* lastStyleContext = nullptr;
1890 for (uint32_t i = 0; i < mMappedFlows.Length(); ++i) {
1891 MappedFlow* mappedFlow = &mMappedFlows[i];
1892 nsTextFrame* f = mappedFlow->mStartFrame;
1894 lastStyleContext = f->StyleContext();
1895 // Detect use of text-transform or font-variant anywhere in the run
1896 textStyle = f->StyleText();
1897 if (NS_STYLE_TEXT_TRANSFORM_NONE != textStyle->mTextTransform) {
1898 anyTextTransformStyle = true;
1900 textFlags |= GetSpacingFlags(LetterSpacing(f));
1901 textFlags |= GetSpacingFlags(WordSpacing(f));
1902 nsTextFrameUtils::CompressionMode compression =
1903 CSSWhitespaceToCompressionMode[textStyle->mWhiteSpace];
1904 if ((enabledJustification || f->StyleContext()->IsInlineDescendantOfRuby()) &&
1905 !textStyle->WhiteSpaceIsSignificant() && !isSVG) {
1906 textFlags |= gfxTextRunFactory::TEXT_ENABLE_SPACING;
1908 fontStyle = f->StyleFont();
1909 nsIFrame* parent = mLineContainer->GetParent();
1910 if (NS_MATHML_MATHVARIANT_NONE != fontStyle->mMathVariant) {
1911 if (NS_MATHML_MATHVARIANT_NORMAL != fontStyle->mMathVariant) {
1912 anyMathMLStyling = true;
1914 } else if (mLineContainer->GetStateBits() & NS_FRAME_IS_IN_SINGLE_CHAR_MI) {
1915 textFlags |= nsTextFrameUtils::TEXT_IS_SINGLE_CHAR_MI;
1916 anyMathMLStyling = true;
1917 // Test for fontstyle attribute as StyleFont() may not be accurate
1918 // To be consistent in terms of ignoring CSS style changes, fontweight
1919 // gets checked too.
1920 if (parent) {
1921 nsIContent* content = parent->GetContent();
1922 if (content) {
1923 if (content->AttrValueIs(kNameSpaceID_None,
1924 nsGkAtoms::fontstyle_,
1925 NS_LITERAL_STRING("normal"),
1926 eCaseMatters)) {
1927 mathFlags |= MathMLTextRunFactory::MATH_FONT_STYLING_NORMAL;
1929 if (content->AttrValueIs(kNameSpaceID_None,
1930 nsGkAtoms::fontweight_,
1931 NS_LITERAL_STRING("bold"),
1932 eCaseMatters)) {
1933 mathFlags |= MathMLTextRunFactory::MATH_FONT_WEIGHT_BOLD;
1938 if (mLineContainer->HasAnyStateBits(TEXT_IS_IN_TOKEN_MATHML)) {
1939 // All MathML tokens except <mtext> use 'math' script.
1940 if (!(parent && parent->GetContent() &&
1941 parent->GetContent()->Tag() == nsGkAtoms::mtext_)) {
1942 textFlags |= gfxTextRunFactory::TEXT_USE_MATH_SCRIPT;
1944 nsIMathMLFrame* mathFrame = do_QueryFrame(parent);
1945 if (mathFrame) {
1946 nsPresentationData presData;
1947 mathFrame->GetPresentationData(presData);
1948 if (NS_MATHML_IS_DTLS_SET(presData.flags)) {
1949 mathFlags |= MathMLTextRunFactory::MATH_FONT_FEATURE_DTLS;
1950 anyMathMLStyling = true;
1954 nsIFrame* child = mLineContainer;
1955 uint8_t oldScriptLevel = 0;
1956 while (parent &&
1957 child->HasAnyStateBits(NS_FRAME_MATHML_SCRIPT_DESCENDANT)) {
1958 // Reconstruct the script level ignoring any user overrides. It is
1959 // calculated this way instead of using scriptlevel to ensure the
1960 // correct ssty font feature setting is used even if the user sets a
1961 // different (especially negative) scriptlevel.
1962 nsIMathMLFrame* mathFrame= do_QueryFrame(parent);
1963 if (mathFrame) {
1964 sstyScriptLevel += mathFrame->ScriptIncrement(child);
1966 if (sstyScriptLevel < oldScriptLevel) {
1967 // overflow
1968 sstyScriptLevel = UINT8_MAX;
1969 break;
1971 child = parent;
1972 parent = parent->GetParent();
1973 oldScriptLevel = sstyScriptLevel;
1975 if (sstyScriptLevel) {
1976 anyMathMLStyling = true;
1979 // Figure out what content is included in this flow.
1980 nsIContent* content = f->GetContent();
1981 const nsTextFragment* frag = content->GetText();
1982 int32_t contentStart = mappedFlow->mStartFrame->GetContentOffset();
1983 int32_t contentEnd = mappedFlow->GetContentEnd();
1984 int32_t contentLength = contentEnd - contentStart;
1986 TextRunMappedFlow* newFlow = &userData->mMappedFlows[i];
1987 newFlow->mStartFrame = mappedFlow->mStartFrame;
1988 newFlow->mDOMOffsetToBeforeTransformOffset = skipChars.GetOriginalCharCount() -
1989 mappedFlow->mStartFrame->GetContentOffset();
1990 newFlow->mContentLength = contentLength;
1992 while (nextBreakBeforeFrame && nextBreakBeforeFrame->GetContent() == content) {
1993 textBreakPoints.AppendElement(
1994 nextBreakBeforeFrame->GetContentOffset() + newFlow->mDOMOffsetToBeforeTransformOffset);
1995 nextBreakBeforeFrame = GetNextBreakBeforeFrame(&nextBreakIndex);
1998 uint32_t analysisFlags;
1999 if (frag->Is2b()) {
2000 NS_ASSERTION(mDoubleByteText, "Wrong buffer char size!");
2001 char16_t* bufStart = static_cast<char16_t*>(aTextBuffer);
2002 char16_t* bufEnd = nsTextFrameUtils::TransformText(
2003 frag->Get2b() + contentStart, contentLength, bufStart,
2004 compression, &mNextRunContextInfo, &skipChars, &analysisFlags);
2005 aTextBuffer = bufEnd;
2006 currentTransformedTextOffset = bufEnd - static_cast<const char16_t*>(textPtr);
2007 } else {
2008 if (mDoubleByteText) {
2009 // Need to expand the text. First transform it into a temporary buffer,
2010 // then expand.
2011 AutoFallibleTArray<uint8_t,BIG_TEXT_NODE_SIZE> tempBuf;
2012 uint8_t* bufStart = tempBuf.AppendElements(contentLength);
2013 if (!bufStart) {
2014 DestroyUserData(userDataToDestroy);
2015 return nullptr;
2017 uint8_t* end = nsTextFrameUtils::TransformText(
2018 reinterpret_cast<const uint8_t*>(frag->Get1b()) + contentStart, contentLength,
2019 bufStart, compression, &mNextRunContextInfo, &skipChars, &analysisFlags);
2020 aTextBuffer = ExpandBuffer(static_cast<char16_t*>(aTextBuffer),
2021 tempBuf.Elements(), end - tempBuf.Elements());
2022 currentTransformedTextOffset =
2023 static_cast<char16_t*>(aTextBuffer) - static_cast<const char16_t*>(textPtr);
2024 } else {
2025 uint8_t* bufStart = static_cast<uint8_t*>(aTextBuffer);
2026 uint8_t* end = nsTextFrameUtils::TransformText(
2027 reinterpret_cast<const uint8_t*>(frag->Get1b()) + contentStart, contentLength,
2028 bufStart, compression, &mNextRunContextInfo, &skipChars, &analysisFlags);
2029 aTextBuffer = end;
2030 currentTransformedTextOffset = end - static_cast<const uint8_t*>(textPtr);
2033 textFlags |= analysisFlags;
2036 void* finalUserData;
2037 if (userData == &dummyData) {
2038 textFlags |= nsTextFrameUtils::TEXT_IS_SIMPLE_FLOW;
2039 userData = nullptr;
2040 finalUserData = mMappedFlows[0].mStartFrame;
2041 } else {
2042 finalUserData = userData;
2045 uint32_t transformedLength = currentTransformedTextOffset;
2047 // Now build the textrun
2048 nsTextFrame* firstFrame = mMappedFlows[0].mStartFrame;
2049 float fontInflation;
2050 if (mWhichTextRun == nsTextFrame::eNotInflated) {
2051 fontInflation = 1.0f;
2052 } else {
2053 fontInflation = nsLayoutUtils::FontSizeInflationFor(firstFrame);
2056 gfxFontGroup* fontGroup = GetFontGroupForFrame(firstFrame, fontInflation);
2057 if (!fontGroup) {
2058 DestroyUserData(userDataToDestroy);
2059 return nullptr;
2062 if (textFlags & nsTextFrameUtils::TEXT_HAS_TAB) {
2063 textFlags |= gfxTextRunFactory::TEXT_ENABLE_SPACING;
2065 if (textFlags & nsTextFrameUtils::TEXT_HAS_SHY) {
2066 textFlags |= gfxTextRunFactory::TEXT_ENABLE_HYPHEN_BREAKS;
2068 if (mBidiEnabled && (IS_LEVEL_RTL(NS_GET_EMBEDDING_LEVEL(firstFrame)))) {
2069 textFlags |= gfxTextRunFactory::TEXT_IS_RTL;
2071 if (mNextRunContextInfo & nsTextFrameUtils::INCOMING_WHITESPACE) {
2072 textFlags |= nsTextFrameUtils::TEXT_TRAILING_WHITESPACE;
2074 if (mNextRunContextInfo & nsTextFrameUtils::INCOMING_ARABICCHAR) {
2075 textFlags |= gfxTextRunFactory::TEXT_TRAILING_ARABICCHAR;
2077 // ContinueTextRunAcrossFrames guarantees that it doesn't matter which
2078 // frame's style is used, so we use a mixture of the first frame and
2079 // last frame's style
2080 textFlags |= nsLayoutUtils::GetTextRunFlagsForStyle(lastStyleContext,
2081 fontStyle, textStyle, LetterSpacing(firstFrame, textStyle));
2082 // XXX this is a bit of a hack. For performance reasons, if we're favouring
2083 // performance over quality, don't try to get accurate glyph extents.
2084 if (!(textFlags & gfxTextRunFactory::TEXT_OPTIMIZE_SPEED)) {
2085 textFlags |= gfxTextRunFactory::TEXT_NEED_BOUNDING_BOX;
2088 // Convert linebreak coordinates to transformed string offsets
2089 NS_ASSERTION(nextBreakIndex == mLineBreakBeforeFrames.Length(),
2090 "Didn't find all the frames to break-before...");
2091 gfxSkipCharsIterator iter(skipChars);
2092 nsAutoTArray<uint32_t,50> textBreakPointsAfterTransform;
2093 for (uint32_t i = 0; i < textBreakPoints.Length(); ++i) {
2094 nsTextFrameUtils::AppendLineBreakOffset(&textBreakPointsAfterTransform,
2095 iter.ConvertOriginalToSkipped(textBreakPoints[i]));
2097 if (mStartOfLine) {
2098 nsTextFrameUtils::AppendLineBreakOffset(&textBreakPointsAfterTransform,
2099 transformedLength);
2102 // Setup factory chain
2103 nsAutoPtr<nsTransformingTextRunFactory> transformingFactory;
2104 if (anyTextTransformStyle) {
2105 transformingFactory =
2106 new nsCaseTransformTextRunFactory(transformingFactory.forget());
2108 if (anyMathMLStyling) {
2109 transformingFactory =
2110 new MathMLTextRunFactory(transformingFactory.forget(), mathFlags,
2111 sstyScriptLevel, fontInflation);
2113 nsTArray<nsStyleContext*> styles;
2114 if (transformingFactory) {
2115 iter.SetOriginalOffset(0);
2116 for (uint32_t i = 0; i < mMappedFlows.Length(); ++i) {
2117 MappedFlow* mappedFlow = &mMappedFlows[i];
2118 nsTextFrame* f;
2119 for (f = mappedFlow->mStartFrame; f != mappedFlow->mEndFrame;
2120 f = static_cast<nsTextFrame*>(f->GetNextContinuation())) {
2121 uint32_t offset = iter.GetSkippedOffset();
2122 iter.AdvanceOriginal(f->GetContentLength());
2123 uint32_t end = iter.GetSkippedOffset();
2124 nsStyleContext* sc = f->StyleContext();
2125 uint32_t j;
2126 for (j = offset; j < end; ++j) {
2127 styles.AppendElement(sc);
2131 textFlags |= nsTextFrameUtils::TEXT_IS_TRANSFORMED;
2132 NS_ASSERTION(iter.GetSkippedOffset() == transformedLength,
2133 "We didn't cover all the characters in the text run!");
2136 gfxTextRun* textRun;
2137 gfxTextRunFactory::Parameters params =
2138 { mContext, finalUserData, &skipChars,
2139 textBreakPointsAfterTransform.Elements(),
2140 uint32_t(textBreakPointsAfterTransform.Length()),
2141 int32_t(firstFrame->PresContext()->AppUnitsPerDevPixel())};
2143 if (mDoubleByteText) {
2144 const char16_t* text = static_cast<const char16_t*>(textPtr);
2145 if (transformingFactory) {
2146 textRun = transformingFactory->MakeTextRun(text, transformedLength,
2147 &params, fontGroup, textFlags,
2148 styles.Elements(), true);
2149 if (textRun) {
2150 // ownership of the factory has passed to the textrun
2151 transformingFactory.forget();
2153 } else {
2154 textRun = MakeTextRun(text, transformedLength, fontGroup, &params,
2155 textFlags, mMissingFonts);
2157 } else {
2158 const uint8_t* text = static_cast<const uint8_t*>(textPtr);
2159 textFlags |= gfxFontGroup::TEXT_IS_8BIT;
2160 if (transformingFactory) {
2161 textRun = transformingFactory->MakeTextRun(text, transformedLength,
2162 &params, fontGroup, textFlags,
2163 styles.Elements(), true);
2164 if (textRun) {
2165 // ownership of the factory has passed to the textrun
2166 transformingFactory.forget();
2168 } else {
2169 textRun = MakeTextRun(text, transformedLength, fontGroup, &params,
2170 textFlags, mMissingFonts);
2173 if (!textRun) {
2174 DestroyUserData(userDataToDestroy);
2175 return nullptr;
2178 // We have to set these up after we've created the textrun, because
2179 // the breaks may be stored in the textrun during this very call.
2180 // This is a bit annoying because it requires another loop over the frames
2181 // making up the textrun, but I don't see a way to avoid this.
2182 uint32_t flags = 0;
2183 if (mDoubleByteText) {
2184 flags |= SBS_DOUBLE_BYTE;
2186 if (mSkipIncompleteTextRuns) {
2187 flags |= SBS_SUPPRESS_SINK;
2189 SetupBreakSinksForTextRun(textRun, textPtr, flags);
2191 if (mSkipIncompleteTextRuns) {
2192 mSkipIncompleteTextRuns = !TextContainsLineBreakerWhiteSpace(textPtr,
2193 transformedLength, mDoubleByteText);
2194 // Arrange for this textrun to be deleted the next time the linebreaker
2195 // is flushed out
2196 mTextRunsToDelete.AppendElement(textRun);
2197 // Since we're doing to destroy the user data now, avoid a dangling
2198 // pointer. Strictly speaking we don't need to do this since it should
2199 // not be used (since this textrun will not be used and will be
2200 // itself deleted soon), but it's always better to not have dangling
2201 // pointers around.
2202 textRun->SetUserData(nullptr);
2203 DestroyUserData(userDataToDestroy);
2204 return nullptr;
2207 // Actually wipe out the textruns associated with the mapped frames and associate
2208 // those frames with this text run.
2209 AssignTextRun(textRun, fontInflation);
2210 return textRun;
2213 // This is a cut-down version of BuildTextRunForFrames used to set up
2214 // context for the line-breaker, when the textrun has already been created.
2215 // So it does the same walk over the mMappedFlows, but doesn't actually
2216 // build a new textrun.
2217 bool
2218 BuildTextRunsScanner::SetupLineBreakerContext(gfxTextRun *aTextRun)
2220 AutoFallibleTArray<uint8_t,BIG_TEXT_NODE_SIZE> buffer;
2221 uint32_t bufferSize = mMaxTextLength*(mDoubleByteText ? 2 : 1);
2222 if (bufferSize < mMaxTextLength || bufferSize == UINT32_MAX) {
2223 return false;
2225 void *textPtr = buffer.AppendElements(bufferSize);
2226 if (!textPtr) {
2227 return false;
2230 gfxSkipChars skipChars;
2232 nsAutoTArray<int32_t,50> textBreakPoints;
2233 TextRunUserData dummyData;
2234 TextRunMappedFlow dummyMappedFlow;
2236 TextRunUserData* userData;
2237 TextRunUserData* userDataToDestroy;
2238 // If the situation is particularly simple (and common) we don't need to
2239 // allocate userData.
2240 if (mMappedFlows.Length() == 1 && !mMappedFlows[0].mEndFrame &&
2241 mMappedFlows[0].mStartFrame->GetContentOffset() == 0) {
2242 userData = &dummyData;
2243 userDataToDestroy = nullptr;
2244 dummyData.mMappedFlows = &dummyMappedFlow;
2245 } else {
2246 userData = static_cast<TextRunUserData*>
2247 (nsMemory::Alloc(sizeof(TextRunUserData) + mMappedFlows.Length()*sizeof(TextRunMappedFlow)));
2248 userDataToDestroy = userData;
2249 userData->mMappedFlows = reinterpret_cast<TextRunMappedFlow*>(userData + 1);
2251 userData->mMappedFlowCount = mMappedFlows.Length();
2252 userData->mLastFlowIndex = 0;
2254 uint32_t nextBreakIndex = 0;
2255 nsTextFrame* nextBreakBeforeFrame = GetNextBreakBeforeFrame(&nextBreakIndex);
2257 const nsStyleText* textStyle = nullptr;
2258 for (uint32_t i = 0; i < mMappedFlows.Length(); ++i) {
2259 MappedFlow* mappedFlow = &mMappedFlows[i];
2260 nsTextFrame* f = mappedFlow->mStartFrame;
2262 textStyle = f->StyleText();
2263 nsTextFrameUtils::CompressionMode compression =
2264 CSSWhitespaceToCompressionMode[textStyle->mWhiteSpace];
2266 // Figure out what content is included in this flow.
2267 nsIContent* content = f->GetContent();
2268 const nsTextFragment* frag = content->GetText();
2269 int32_t contentStart = mappedFlow->mStartFrame->GetContentOffset();
2270 int32_t contentEnd = mappedFlow->GetContentEnd();
2271 int32_t contentLength = contentEnd - contentStart;
2273 TextRunMappedFlow* newFlow = &userData->mMappedFlows[i];
2274 newFlow->mStartFrame = mappedFlow->mStartFrame;
2275 newFlow->mDOMOffsetToBeforeTransformOffset = skipChars.GetOriginalCharCount() -
2276 mappedFlow->mStartFrame->GetContentOffset();
2277 newFlow->mContentLength = contentLength;
2279 while (nextBreakBeforeFrame && nextBreakBeforeFrame->GetContent() == content) {
2280 textBreakPoints.AppendElement(
2281 nextBreakBeforeFrame->GetContentOffset() + newFlow->mDOMOffsetToBeforeTransformOffset);
2282 nextBreakBeforeFrame = GetNextBreakBeforeFrame(&nextBreakIndex);
2285 uint32_t analysisFlags;
2286 if (frag->Is2b()) {
2287 NS_ASSERTION(mDoubleByteText, "Wrong buffer char size!");
2288 char16_t* bufStart = static_cast<char16_t*>(textPtr);
2289 char16_t* bufEnd = nsTextFrameUtils::TransformText(
2290 frag->Get2b() + contentStart, contentLength, bufStart,
2291 compression, &mNextRunContextInfo, &skipChars, &analysisFlags);
2292 textPtr = bufEnd;
2293 } else {
2294 if (mDoubleByteText) {
2295 // Need to expand the text. First transform it into a temporary buffer,
2296 // then expand.
2297 AutoFallibleTArray<uint8_t,BIG_TEXT_NODE_SIZE> tempBuf;
2298 uint8_t* bufStart = tempBuf.AppendElements(contentLength);
2299 if (!bufStart) {
2300 DestroyUserData(userDataToDestroy);
2301 return false;
2303 uint8_t* end = nsTextFrameUtils::TransformText(
2304 reinterpret_cast<const uint8_t*>(frag->Get1b()) + contentStart, contentLength,
2305 bufStart, compression, &mNextRunContextInfo, &skipChars, &analysisFlags);
2306 textPtr = ExpandBuffer(static_cast<char16_t*>(textPtr),
2307 tempBuf.Elements(), end - tempBuf.Elements());
2308 } else {
2309 uint8_t* bufStart = static_cast<uint8_t*>(textPtr);
2310 uint8_t* end = nsTextFrameUtils::TransformText(
2311 reinterpret_cast<const uint8_t*>(frag->Get1b()) + contentStart, contentLength,
2312 bufStart, compression, &mNextRunContextInfo, &skipChars, &analysisFlags);
2313 textPtr = end;
2318 // We have to set these up after we've created the textrun, because
2319 // the breaks may be stored in the textrun during this very call.
2320 // This is a bit annoying because it requires another loop over the frames
2321 // making up the textrun, but I don't see a way to avoid this.
2322 uint32_t flags = 0;
2323 if (mDoubleByteText) {
2324 flags |= SBS_DOUBLE_BYTE;
2326 if (mSkipIncompleteTextRuns) {
2327 flags |= SBS_SUPPRESS_SINK;
2329 SetupBreakSinksForTextRun(aTextRun, buffer.Elements(), flags);
2331 DestroyUserData(userDataToDestroy);
2333 return true;
2336 static bool
2337 HasCompressedLeadingWhitespace(nsTextFrame* aFrame, const nsStyleText* aStyleText,
2338 int32_t aContentEndOffset,
2339 const gfxSkipCharsIterator& aIterator)
2341 if (!aIterator.IsOriginalCharSkipped())
2342 return false;
2344 gfxSkipCharsIterator iter = aIterator;
2345 int32_t frameContentOffset = aFrame->GetContentOffset();
2346 const nsTextFragment* frag = aFrame->GetContent()->GetText();
2347 while (frameContentOffset < aContentEndOffset && iter.IsOriginalCharSkipped()) {
2348 if (IsTrimmableSpace(frag, frameContentOffset, aStyleText))
2349 return true;
2350 ++frameContentOffset;
2351 iter.AdvanceOriginal(1);
2353 return false;
2356 void
2357 BuildTextRunsScanner::SetupBreakSinksForTextRun(gfxTextRun* aTextRun,
2358 const void* aTextPtr,
2359 uint32_t aFlags)
2361 // textruns have uniform language
2362 const nsStyleFont *styleFont = mMappedFlows[0].mStartFrame->StyleFont();
2363 // We should only use a language for hyphenation if it was specified
2364 // explicitly.
2365 nsIAtom* hyphenationLanguage =
2366 styleFont->mExplicitLanguage ? styleFont->mLanguage : nullptr;
2367 // We keep this pointed at the skip-chars data for the current mappedFlow.
2368 // This lets us cheaply check whether the flow has compressed initial
2369 // whitespace...
2370 gfxSkipCharsIterator iter(aTextRun->GetSkipChars());
2372 for (uint32_t i = 0; i < mMappedFlows.Length(); ++i) {
2373 MappedFlow* mappedFlow = &mMappedFlows[i];
2374 uint32_t offset = iter.GetSkippedOffset();
2375 gfxSkipCharsIterator iterNext = iter;
2376 iterNext.AdvanceOriginal(mappedFlow->GetContentEnd() -
2377 mappedFlow->mStartFrame->GetContentOffset());
2379 nsAutoPtr<BreakSink>* breakSink = mBreakSinks.AppendElement(
2380 new BreakSink(aTextRun, mContext, offset,
2381 (aFlags & SBS_EXISTING_TEXTRUN) != 0));
2382 if (!breakSink || !*breakSink)
2383 return;
2385 uint32_t length = iterNext.GetSkippedOffset() - offset;
2386 uint32_t flags = 0;
2387 nsIFrame* initialBreakController = mappedFlow->mAncestorControllingInitialBreak;
2388 if (!initialBreakController) {
2389 initialBreakController = mLineContainer;
2391 if (!initialBreakController->StyleText()->
2392 WhiteSpaceCanWrap(initialBreakController)) {
2393 flags |= nsLineBreaker::BREAK_SUPPRESS_INITIAL;
2395 nsTextFrame* startFrame = mappedFlow->mStartFrame;
2396 const nsStyleText* textStyle = startFrame->StyleText();
2397 if (!textStyle->WhiteSpaceCanWrap(startFrame)) {
2398 flags |= nsLineBreaker::BREAK_SUPPRESS_INSIDE;
2400 if (aTextRun->GetFlags() & nsTextFrameUtils::TEXT_NO_BREAKS) {
2401 flags |= nsLineBreaker::BREAK_SKIP_SETTING_NO_BREAKS;
2403 if (textStyle->mTextTransform == NS_STYLE_TEXT_TRANSFORM_CAPITALIZE) {
2404 flags |= nsLineBreaker::BREAK_NEED_CAPITALIZATION;
2406 if (textStyle->mHyphens == NS_STYLE_HYPHENS_AUTO) {
2407 flags |= nsLineBreaker::BREAK_USE_AUTO_HYPHENATION;
2410 if (HasCompressedLeadingWhitespace(startFrame, textStyle,
2411 mappedFlow->GetContentEnd(), iter)) {
2412 mLineBreaker.AppendInvisibleWhitespace(flags);
2415 if (length > 0) {
2416 BreakSink* sink =
2417 (aFlags & SBS_SUPPRESS_SINK) ? nullptr : (*breakSink).get();
2418 if (aFlags & SBS_DOUBLE_BYTE) {
2419 const char16_t* text = reinterpret_cast<const char16_t*>(aTextPtr);
2420 mLineBreaker.AppendText(hyphenationLanguage, text + offset,
2421 length, flags, sink);
2422 } else {
2423 const uint8_t* text = reinterpret_cast<const uint8_t*>(aTextPtr);
2424 mLineBreaker.AppendText(hyphenationLanguage, text + offset,
2425 length, flags, sink);
2429 iter = iterNext;
2433 // Find the flow corresponding to aContent in aUserData
2434 static inline TextRunMappedFlow*
2435 FindFlowForContent(TextRunUserData* aUserData, nsIContent* aContent)
2437 // Find the flow that contains us
2438 int32_t i = aUserData->mLastFlowIndex;
2439 int32_t delta = 1;
2440 int32_t sign = 1;
2441 // Search starting at the current position and examine close-by
2442 // positions first, moving further and further away as we go.
2443 while (i >= 0 && uint32_t(i) < aUserData->mMappedFlowCount) {
2444 TextRunMappedFlow* flow = &aUserData->mMappedFlows[i];
2445 if (flow->mStartFrame->GetContent() == aContent) {
2446 return flow;
2449 i += delta;
2450 sign = -sign;
2451 delta = -delta + sign;
2454 // We ran into an array edge. Add |delta| to |i| once more to get
2455 // back to the side where we still need to search, then step in
2456 // the |sign| direction.
2457 i += delta;
2458 if (sign > 0) {
2459 for (; i < int32_t(aUserData->mMappedFlowCount); ++i) {
2460 TextRunMappedFlow* flow = &aUserData->mMappedFlows[i];
2461 if (flow->mStartFrame->GetContent() == aContent) {
2462 return flow;
2465 } else {
2466 for (; i >= 0; --i) {
2467 TextRunMappedFlow* flow = &aUserData->mMappedFlows[i];
2468 if (flow->mStartFrame->GetContent() == aContent) {
2469 return flow;
2474 return nullptr;
2477 void
2478 BuildTextRunsScanner::AssignTextRun(gfxTextRun* aTextRun, float aInflation)
2480 for (uint32_t i = 0; i < mMappedFlows.Length(); ++i) {
2481 MappedFlow* mappedFlow = &mMappedFlows[i];
2482 nsTextFrame* startFrame = mappedFlow->mStartFrame;
2483 nsTextFrame* endFrame = mappedFlow->mEndFrame;
2484 nsTextFrame* f;
2485 for (f = startFrame; f != endFrame;
2486 f = static_cast<nsTextFrame*>(f->GetNextContinuation())) {
2487 #ifdef DEBUG_roc
2488 if (f->GetTextRun(mWhichTextRun)) {
2489 gfxTextRun* textRun = f->GetTextRun(mWhichTextRun);
2490 if (textRun->GetFlags() & nsTextFrameUtils::TEXT_IS_SIMPLE_FLOW) {
2491 if (mMappedFlows[0].mStartFrame != static_cast<nsTextFrame*>(textRun->GetUserData())) {
2492 NS_WARNING("REASSIGNING SIMPLE FLOW TEXT RUN!");
2494 } else {
2495 TextRunUserData* userData =
2496 static_cast<TextRunUserData*>(textRun->GetUserData());
2498 if (userData->mMappedFlowCount >= mMappedFlows.Length() ||
2499 userData->mMappedFlows[userData->mMappedFlowCount - 1].mStartFrame !=
2500 mMappedFlows[userData->mMappedFlowCount - 1].mStartFrame) {
2501 NS_WARNING("REASSIGNING MULTIFLOW TEXT RUN (not append)!");
2505 #endif
2507 gfxTextRun* oldTextRun = f->GetTextRun(mWhichTextRun);
2508 if (oldTextRun) {
2509 nsTextFrame* firstFrame = nullptr;
2510 uint32_t startOffset = 0;
2511 if (oldTextRun->GetFlags() & nsTextFrameUtils::TEXT_IS_SIMPLE_FLOW) {
2512 firstFrame = static_cast<nsTextFrame*>(oldTextRun->GetUserData());
2514 else {
2515 TextRunUserData* userData = static_cast<TextRunUserData*>(oldTextRun->GetUserData());
2516 firstFrame = userData->mMappedFlows[0].mStartFrame;
2517 if (MOZ_UNLIKELY(f != firstFrame)) {
2518 TextRunMappedFlow* flow = FindFlowForContent(userData, f->GetContent());
2519 if (flow) {
2520 startOffset = flow->mDOMOffsetToBeforeTransformOffset;
2522 else {
2523 NS_ERROR("Can't find flow containing frame 'f'");
2528 // Optimization: if |f| is the first frame in the flow then there are no
2529 // prev-continuations that use |oldTextRun|.
2530 nsTextFrame* clearFrom = nullptr;
2531 if (MOZ_UNLIKELY(f != firstFrame)) {
2532 // If all the frames in the mapped flow starting at |f| (inclusive)
2533 // are empty then we let the prev-continuations keep the old text run.
2534 gfxSkipCharsIterator iter(oldTextRun->GetSkipChars(), startOffset, f->GetContentOffset());
2535 uint32_t textRunOffset = iter.ConvertOriginalToSkipped(f->GetContentOffset());
2536 clearFrom = textRunOffset == oldTextRun->GetLength() ? f : nullptr;
2538 f->ClearTextRun(clearFrom, mWhichTextRun);
2540 #ifdef DEBUG
2541 if (firstFrame && !firstFrame->GetTextRun(mWhichTextRun)) {
2542 // oldTextRun was destroyed - assert that we don't reference it.
2543 for (uint32_t j = 0; j < mBreakSinks.Length(); ++j) {
2544 NS_ASSERTION(oldTextRun != mBreakSinks[j]->mTextRun,
2545 "destroyed text run is still in use");
2548 #endif
2550 f->SetTextRun(aTextRun, mWhichTextRun, aInflation);
2552 // Set this bit now; we can't set it any earlier because
2553 // f->ClearTextRun() might clear it out.
2554 nsFrameState whichTextRunState =
2555 startFrame->GetTextRun(nsTextFrame::eInflated) == aTextRun
2556 ? TEXT_IN_TEXTRUN_USER_DATA
2557 : TEXT_IN_UNINFLATED_TEXTRUN_USER_DATA;
2558 startFrame->AddStateBits(whichTextRunState);
2562 NS_QUERYFRAME_HEAD(nsTextFrame)
2563 NS_QUERYFRAME_ENTRY(nsTextFrame)
2564 NS_QUERYFRAME_TAIL_INHERITING(nsTextFrameBase)
2566 gfxSkipCharsIterator
2567 nsTextFrame::EnsureTextRun(TextRunType aWhichTextRun,
2568 gfxContext* aReferenceContext,
2569 nsIFrame* aLineContainer,
2570 const nsLineList::iterator* aLine,
2571 uint32_t* aFlowEndInTextRun)
2573 gfxTextRun *textRun = GetTextRun(aWhichTextRun);
2574 if (textRun && (!aLine || !(*aLine)->GetInvalidateTextRuns())) {
2575 if (textRun->GetExpirationState()->IsTracked()) {
2576 gTextRuns->MarkUsed(textRun);
2578 } else {
2579 nsRefPtr<gfxContext> ctx = aReferenceContext;
2580 if (!ctx) {
2581 ctx = CreateReferenceThebesContext(this);
2583 if (ctx) {
2584 BuildTextRuns(ctx, this, aLineContainer, aLine, aWhichTextRun);
2586 textRun = GetTextRun(aWhichTextRun);
2587 if (!textRun) {
2588 // A text run was not constructed for this frame. This is bad. The caller
2589 // will check mTextRun.
2590 static const gfxSkipChars emptySkipChars;
2591 return gfxSkipCharsIterator(emptySkipChars, 0);
2593 TabWidthStore* tabWidths =
2594 static_cast<TabWidthStore*>(Properties().Get(TabWidthProperty()));
2595 if (tabWidths && tabWidths->mValidForContentOffset != GetContentOffset()) {
2596 Properties().Delete(TabWidthProperty());
2600 if (textRun->GetFlags() & nsTextFrameUtils::TEXT_IS_SIMPLE_FLOW) {
2601 if (aFlowEndInTextRun) {
2602 *aFlowEndInTextRun = textRun->GetLength();
2604 return gfxSkipCharsIterator(textRun->GetSkipChars(), 0, mContentOffset);
2607 TextRunUserData* userData = static_cast<TextRunUserData*>(textRun->GetUserData());
2608 TextRunMappedFlow* flow = FindFlowForContent(userData, mContent);
2609 if (flow) {
2610 // Since textruns can only contain one flow for a given content element,
2611 // this must be our flow.
2612 uint32_t flowIndex = flow - userData->mMappedFlows;
2613 userData->mLastFlowIndex = flowIndex;
2614 gfxSkipCharsIterator iter(textRun->GetSkipChars(),
2615 flow->mDOMOffsetToBeforeTransformOffset, mContentOffset);
2616 if (aFlowEndInTextRun) {
2617 if (flowIndex + 1 < userData->mMappedFlowCount) {
2618 gfxSkipCharsIterator end(textRun->GetSkipChars());
2619 *aFlowEndInTextRun = end.ConvertOriginalToSkipped(
2620 flow[1].mStartFrame->GetContentOffset() + flow[1].mDOMOffsetToBeforeTransformOffset);
2621 } else {
2622 *aFlowEndInTextRun = textRun->GetLength();
2625 return iter;
2628 NS_ERROR("Can't find flow containing this frame???");
2629 static const gfxSkipChars emptySkipChars;
2630 return gfxSkipCharsIterator(emptySkipChars, 0);
2633 static uint32_t
2634 GetEndOfTrimmedText(const nsTextFragment* aFrag, const nsStyleText* aStyleText,
2635 uint32_t aStart, uint32_t aEnd,
2636 gfxSkipCharsIterator* aIterator)
2638 aIterator->SetSkippedOffset(aEnd);
2639 while (aIterator->GetSkippedOffset() > aStart) {
2640 aIterator->AdvanceSkipped(-1);
2641 if (!IsTrimmableSpace(aFrag, aIterator->GetOriginalOffset(), aStyleText))
2642 return aIterator->GetSkippedOffset() + 1;
2644 return aStart;
2647 nsTextFrame::TrimmedOffsets
2648 nsTextFrame::GetTrimmedOffsets(const nsTextFragment* aFrag,
2649 bool aTrimAfter, bool aPostReflow)
2651 NS_ASSERTION(mTextRun, "Need textrun here");
2652 if (aPostReflow) {
2653 // This should not be used during reflow. We need our TEXT_REFLOW_FLAGS
2654 // to be set correctly. If our parent wasn't reflowed due to the frame
2655 // tree being too deep then the return value doesn't matter.
2656 NS_ASSERTION(!(GetStateBits() & NS_FRAME_FIRST_REFLOW) ||
2657 (GetParent()->GetStateBits() &
2658 NS_FRAME_TOO_DEEP_IN_FRAME_TREE),
2659 "Can only call this on frames that have been reflowed");
2660 NS_ASSERTION(!(GetStateBits() & NS_FRAME_IN_REFLOW),
2661 "Can only call this on frames that are not being reflowed");
2664 TrimmedOffsets offsets = { GetContentOffset(), GetContentLength() };
2665 const nsStyleText* textStyle = StyleText();
2666 // Note that pre-line newlines should still allow us to trim spaces
2667 // for display
2668 if (textStyle->WhiteSpaceIsSignificant())
2669 return offsets;
2671 if (!aPostReflow || (GetStateBits() & TEXT_START_OF_LINE)) {
2672 int32_t whitespaceCount =
2673 GetTrimmableWhitespaceCount(aFrag,
2674 offsets.mStart, offsets.mLength, 1);
2675 offsets.mStart += whitespaceCount;
2676 offsets.mLength -= whitespaceCount;
2679 if (aTrimAfter && (!aPostReflow || (GetStateBits() & TEXT_END_OF_LINE))) {
2680 // This treats a trailing 'pre-line' newline as trimmable. That's fine,
2681 // it's actually what we want since we want whitespace before it to
2682 // be trimmed.
2683 int32_t whitespaceCount =
2684 GetTrimmableWhitespaceCount(aFrag,
2685 offsets.GetEnd() - 1, offsets.mLength, -1);
2686 offsets.mLength -= whitespaceCount;
2688 return offsets;
2691 static bool IsJustifiableCharacter(const nsTextFragment* aFrag, int32_t aPos,
2692 bool aLangIsCJ)
2694 NS_ASSERTION(aPos >= 0, "negative position?!");
2695 char16_t ch = aFrag->CharAt(aPos);
2696 if (ch == '\n' || ch == '\t' || ch == '\r')
2697 return true;
2698 if (ch == ' ' || ch == CH_NBSP) {
2699 // Don't justify spaces that are combined with diacriticals
2700 if (!aFrag->Is2b())
2701 return true;
2702 return !nsTextFrameUtils::IsSpaceCombiningSequenceTail(
2703 aFrag->Get2b() + aPos + 1, aFrag->GetLength() - (aPos + 1));
2705 if (ch < 0x2150u)
2706 return false;
2707 if (aLangIsCJ) {
2708 if ((0x2150u <= ch && ch <= 0x22ffu) || // Number Forms, Arrows, Mathematical Operators
2709 (0x2460u <= ch && ch <= 0x24ffu) || // Enclosed Alphanumerics
2710 (0x2580u <= ch && ch <= 0x27bfu) || // Block Elements, Geometric Shapes, Miscellaneous Symbols, Dingbats
2711 (0x27f0u <= ch && ch <= 0x2bffu) || // Supplemental Arrows-A, Braille Patterns, Supplemental Arrows-B,
2712 // Miscellaneous Mathematical Symbols-B, Supplemental Mathematical Operators,
2713 // Miscellaneous Symbols and Arrows
2714 (0x2e80u <= ch && ch <= 0x312fu) || // CJK Radicals Supplement, CJK Radicals Supplement,
2715 // Ideographic Description Characters, CJK Symbols and Punctuation,
2716 // Hiragana, Katakana, Bopomofo
2717 (0x3190u <= ch && ch <= 0xabffu) || // Kanbun, Bopomofo Extended, Katakana Phonetic Extensions,
2718 // Enclosed CJK Letters and Months, CJK Compatibility,
2719 // CJK Unified Ideographs Extension A, Yijing Hexagram Symbols,
2720 // CJK Unified Ideographs, Yi Syllables, Yi Radicals
2721 (0xf900u <= ch && ch <= 0xfaffu) || // CJK Compatibility Ideographs
2722 (0xff5eu <= ch && ch <= 0xff9fu) // Halfwidth and Fullwidth Forms(a part)
2724 return true;
2726 char16_t ch2;
2727 if (NS_IS_HIGH_SURROGATE(ch) && aFrag->GetLength() > uint32_t(aPos) + 1 &&
2728 NS_IS_LOW_SURROGATE(ch2 = aFrag->CharAt(aPos + 1))) {
2729 uint32_t u = SURROGATE_TO_UCS4(ch, ch2);
2730 if (0x20000u <= u && u <= 0x2ffffu) { // CJK Unified Ideographs Extension B,
2731 // CJK Unified Ideographs Extension C,
2732 // CJK Unified Ideographs Extension D,
2733 // CJK Compatibility Ideographs Supplement
2734 return true;
2738 return false;
2741 void
2742 nsTextFrame::ClearMetrics(nsHTMLReflowMetrics& aMetrics)
2744 aMetrics.ClearSize();
2745 aMetrics.SetBlockStartAscent(0);
2746 mAscent = 0;
2749 static int32_t FindChar(const nsTextFragment* frag,
2750 int32_t aOffset, int32_t aLength, char16_t ch)
2752 int32_t i = 0;
2753 if (frag->Is2b()) {
2754 const char16_t* str = frag->Get2b() + aOffset;
2755 for (; i < aLength; ++i) {
2756 if (*str == ch)
2757 return i + aOffset;
2758 ++str;
2760 } else {
2761 if (uint16_t(ch) <= 0xFF) {
2762 const char* str = frag->Get1b() + aOffset;
2763 const void* p = memchr(str, ch, aLength);
2764 if (p)
2765 return (static_cast<const char*>(p) - str) + aOffset;
2768 return -1;
2771 static bool IsChineseOrJapanese(nsIFrame* aFrame)
2773 if (aFrame->StyleContext()->IsInlineDescendantOfRuby()) {
2774 // Always treat ruby as CJ language so that those characters can
2775 // be expanded properly even when surrounded by other language.
2776 return true;
2779 nsIAtom* language = aFrame->StyleFont()->mLanguage;
2780 if (!language) {
2781 return false;
2783 const char16_t *lang = language->GetUTF16String();
2784 return (!nsCRT::strncmp(lang, MOZ_UTF16("ja"), 2) ||
2785 !nsCRT::strncmp(lang, MOZ_UTF16("zh"), 2)) &&
2786 (language->GetLength() == 2 || lang[2] == '-');
2789 #ifdef DEBUG
2790 static bool IsInBounds(const gfxSkipCharsIterator& aStart, int32_t aContentLength,
2791 uint32_t aOffset, uint32_t aLength) {
2792 if (aStart.GetSkippedOffset() > aOffset)
2793 return false;
2794 if (aContentLength == INT32_MAX)
2795 return true;
2796 gfxSkipCharsIterator iter(aStart);
2797 iter.AdvanceOriginal(aContentLength);
2798 return iter.GetSkippedOffset() >= aOffset + aLength;
2800 #endif
2802 class MOZ_STACK_CLASS PropertyProvider : public gfxTextRun::PropertyProvider {
2803 public:
2805 * Use this constructor for reflow, when we don't know what text is
2806 * really mapped by the frame and we have a lot of other data around.
2808 * @param aLength can be INT32_MAX to indicate we cover all the text
2809 * associated with aFrame up to where its flow chain ends in the given
2810 * textrun. If INT32_MAX is passed, justification and hyphen-related methods
2811 * cannot be called, nor can GetOriginalLength().
2813 PropertyProvider(gfxTextRun* aTextRun, const nsStyleText* aTextStyle,
2814 const nsTextFragment* aFrag, nsTextFrame* aFrame,
2815 const gfxSkipCharsIterator& aStart, int32_t aLength,
2816 nsIFrame* aLineContainer,
2817 nscoord aOffsetFromBlockOriginForTabs,
2818 nsTextFrame::TextRunType aWhichTextRun)
2819 : mTextRun(aTextRun), mFontGroup(nullptr),
2820 mTextStyle(aTextStyle), mFrag(aFrag),
2821 mLineContainer(aLineContainer),
2822 mFrame(aFrame), mStart(aStart), mTempIterator(aStart),
2823 mTabWidths(nullptr), mTabWidthsAnalyzedLimit(0),
2824 mLength(aLength),
2825 mWordSpacing(WordSpacing(aFrame, aTextStyle)),
2826 mLetterSpacing(LetterSpacing(aFrame, aTextStyle)),
2827 mHyphenWidth(-1),
2828 mOffsetFromBlockOriginForTabs(aOffsetFromBlockOriginForTabs),
2829 mJustificationSpacing(0),
2830 mReflowing(true),
2831 mWhichTextRun(aWhichTextRun)
2833 NS_ASSERTION(mStart.IsInitialized(), "Start not initialized?");
2837 * Use this constructor after the frame has been reflowed and we don't
2838 * have other data around. Gets everything from the frame. EnsureTextRun
2839 * *must* be called before this!!!
2841 PropertyProvider(nsTextFrame* aFrame, const gfxSkipCharsIterator& aStart,
2842 nsTextFrame::TextRunType aWhichTextRun)
2843 : mTextRun(aFrame->GetTextRun(aWhichTextRun)), mFontGroup(nullptr),
2844 mTextStyle(aFrame->StyleText()),
2845 mFrag(aFrame->GetContent()->GetText()),
2846 mLineContainer(nullptr),
2847 mFrame(aFrame), mStart(aStart), mTempIterator(aStart),
2848 mTabWidths(nullptr), mTabWidthsAnalyzedLimit(0),
2849 mLength(aFrame->GetContentLength()),
2850 mWordSpacing(WordSpacing(aFrame)),
2851 mLetterSpacing(LetterSpacing(aFrame)),
2852 mHyphenWidth(-1),
2853 mOffsetFromBlockOriginForTabs(0),
2854 mJustificationSpacing(0),
2855 mReflowing(false),
2856 mWhichTextRun(aWhichTextRun)
2858 NS_ASSERTION(mTextRun, "Textrun not initialized!");
2861 // Call this after construction if you're not going to reflow the text
2862 void InitializeForDisplay(bool aTrimAfter);
2864 void InitializeForMeasure();
2866 virtual void GetSpacing(uint32_t aStart, uint32_t aLength, Spacing* aSpacing);
2867 virtual gfxFloat GetHyphenWidth();
2868 virtual void GetHyphenationBreaks(uint32_t aStart, uint32_t aLength,
2869 bool* aBreakBefore);
2870 virtual int8_t GetHyphensOption() {
2871 return mTextStyle->mHyphens;
2874 virtual already_AddRefed<gfxContext> GetContext() {
2875 return CreateReferenceThebesContext(GetFrame());
2878 virtual uint32_t GetAppUnitsPerDevUnit() {
2879 return mTextRun->GetAppUnitsPerDevUnit();
2882 void GetSpacingInternal(uint32_t aStart, uint32_t aLength, Spacing* aSpacing,
2883 bool aIgnoreTabs);
2886 * Compute the justification information in given DOM range, and fill data
2887 * necessary for computation of spacing.
2889 void ComputeJustification(int32_t aOffset, int32_t aLength);
2891 const nsStyleText* StyleText() { return mTextStyle; }
2892 nsTextFrame* GetFrame() { return mFrame; }
2893 // This may not be equal to the frame offset/length in because we may have
2894 // adjusted for whitespace trimming according to the state bits set in the frame
2895 // (for the static provider)
2896 const gfxSkipCharsIterator& GetStart() { return mStart; }
2897 // May return INT32_MAX if that was given to the constructor
2898 uint32_t GetOriginalLength() {
2899 NS_ASSERTION(mLength != INT32_MAX, "Length not known");
2900 return mLength;
2902 const nsTextFragment* GetFragment() { return mFrag; }
2904 gfxFontGroup* GetFontGroup() {
2905 if (!mFontGroup)
2906 InitFontGroupAndFontMetrics();
2907 return mFontGroup;
2910 nsFontMetrics* GetFontMetrics() {
2911 if (!mFontMetrics)
2912 InitFontGroupAndFontMetrics();
2913 return mFontMetrics;
2916 void CalcTabWidths(uint32_t aTransformedStart, uint32_t aTransformedLength);
2918 const gfxSkipCharsIterator& GetEndHint() { return mTempIterator; }
2920 const JustificationInfo& GetJustificationInfo() const
2922 return mJustificationInfo;
2925 protected:
2926 void SetupJustificationSpacing(bool aPostReflow);
2928 void InitFontGroupAndFontMetrics() {
2929 float inflation = (mWhichTextRun == nsTextFrame::eInflated)
2930 ? mFrame->GetFontSizeInflation() : 1.0f;
2931 mFontGroup = GetFontGroupForFrame(mFrame, inflation,
2932 getter_AddRefs(mFontMetrics));
2935 gfxTextRun* mTextRun;
2936 gfxFontGroup* mFontGroup;
2937 nsRefPtr<nsFontMetrics> mFontMetrics;
2938 const nsStyleText* mTextStyle;
2939 const nsTextFragment* mFrag;
2940 nsIFrame* mLineContainer;
2941 nsTextFrame* mFrame;
2942 gfxSkipCharsIterator mStart; // Offset in original and transformed string
2943 gfxSkipCharsIterator mTempIterator;
2945 // Either null, or pointing to the frame's TabWidthProperty.
2946 TabWidthStore* mTabWidths;
2947 // How far we've done tab-width calculation; this is ONLY valid when
2948 // mTabWidths is nullptr (otherwise rely on mTabWidths->mLimit instead).
2949 // It's a DOM offset relative to the current frame's offset.
2950 uint32_t mTabWidthsAnalyzedLimit;
2952 int32_t mLength; // DOM string length, may be INT32_MAX
2953 gfxFloat mWordSpacing; // space for each whitespace char
2954 gfxFloat mLetterSpacing; // space for each letter
2955 gfxFloat mHyphenWidth;
2956 gfxFloat mOffsetFromBlockOriginForTabs;
2958 // The total spacing for justification
2959 gfxFloat mJustificationSpacing;
2960 int32_t mTotalJustificationGaps;
2961 JustificationInfo mJustificationInfo;
2962 // The values in mJustificationAssignments corresponds to unskipped
2963 // characters start from mJustificationArrayStart.
2964 uint32_t mJustificationArrayStart;
2965 nsTArray<JustificationAssignment> mJustificationAssignments;
2967 bool mReflowing;
2968 nsTextFrame::TextRunType mWhichTextRun;
2972 * Finds the offset of the first character of the cluster containing aPos
2974 static void FindClusterStart(gfxTextRun* aTextRun, int32_t aOriginalStart,
2975 gfxSkipCharsIterator* aPos)
2977 while (aPos->GetOriginalOffset() > aOriginalStart) {
2978 if (aPos->IsOriginalCharSkipped() ||
2979 aTextRun->IsClusterStart(aPos->GetSkippedOffset())) {
2980 break;
2982 aPos->AdvanceOriginal(-1);
2987 * Finds the offset of the last character of the cluster containing aPos.
2988 * If aAllowSplitLigature is false, we also check for a ligature-group
2989 * start.
2991 static void FindClusterEnd(gfxTextRun* aTextRun, int32_t aOriginalEnd,
2992 gfxSkipCharsIterator* aPos,
2993 bool aAllowSplitLigature = true)
2995 NS_PRECONDITION(aPos->GetOriginalOffset() < aOriginalEnd,
2996 "character outside string");
2997 aPos->AdvanceOriginal(1);
2998 while (aPos->GetOriginalOffset() < aOriginalEnd) {
2999 if (aPos->IsOriginalCharSkipped() ||
3000 (aTextRun->IsClusterStart(aPos->GetSkippedOffset()) &&
3001 (aAllowSplitLigature ||
3002 aTextRun->IsLigatureGroupStart(aPos->GetSkippedOffset())))) {
3003 break;
3005 aPos->AdvanceOriginal(1);
3007 aPos->AdvanceOriginal(-1);
3010 void
3011 PropertyProvider::ComputeJustification(int32_t aOffset, int32_t aLength)
3013 bool isCJ = IsChineseOrJapanese(mFrame);
3014 nsSkipCharsRunIterator
3015 run(mStart, nsSkipCharsRunIterator::LENGTH_INCLUDES_SKIPPED, aLength);
3016 run.SetOriginalOffset(aOffset);
3017 mJustificationArrayStart = run.GetSkippedOffset();
3019 MOZ_ASSERT(mJustificationAssignments.IsEmpty());
3020 mJustificationAssignments.SetCapacity(aLength);
3021 while (run.NextRun()) {
3022 uint32_t originalOffset = run.GetOriginalOffset();
3023 uint32_t skippedOffset = run.GetSkippedOffset();
3024 uint32_t length = run.GetRunLength();
3025 mJustificationAssignments.SetLength(
3026 skippedOffset + length - mJustificationArrayStart);
3028 gfxSkipCharsIterator iter = run.GetPos();
3029 for (uint32_t i = 0; i < length; ++i) {
3030 uint32_t offset = originalOffset + i;
3031 if (!IsJustifiableCharacter(mFrag, offset, isCJ)) {
3032 continue;
3035 iter.SetOriginalOffset(offset);
3037 FindClusterStart(mTextRun, originalOffset, &iter);
3038 uint32_t firstCharOffset = iter.GetSkippedOffset();
3039 uint32_t firstChar = firstCharOffset > mJustificationArrayStart ?
3040 firstCharOffset - mJustificationArrayStart : 0;
3041 if (!firstChar) {
3042 mJustificationInfo.mIsStartJustifiable = true;
3043 } else {
3044 auto& assign = mJustificationAssignments[firstChar];
3045 auto& prevAssign = mJustificationAssignments[firstChar - 1];
3046 if (prevAssign.mGapsAtEnd) {
3047 prevAssign.mGapsAtEnd = 1;
3048 assign.mGapsAtStart = 1;
3049 } else {
3050 assign.mGapsAtStart = 2;
3051 mJustificationInfo.mInnerOpportunities++;
3055 FindClusterEnd(mTextRun, originalOffset + length, &iter);
3056 uint32_t lastChar = iter.GetSkippedOffset() - mJustificationArrayStart;
3057 // Assign the two gaps temporary to the last char. If the next cluster is
3058 // justifiable as well, one of the gaps will be removed by code above.
3059 mJustificationAssignments[lastChar].mGapsAtEnd = 2;
3060 mJustificationInfo.mInnerOpportunities++;
3062 // Skip the whole cluster
3063 i = iter.GetOriginalOffset() - originalOffset;
3067 if (!mJustificationAssignments.IsEmpty() &&
3068 mJustificationAssignments.LastElement().mGapsAtEnd) {
3069 // We counted the expansion opportunity after the last character,
3070 // but it is not an inner opportunity.
3071 MOZ_ASSERT(mJustificationInfo.mInnerOpportunities > 0);
3072 mJustificationInfo.mInnerOpportunities--;
3073 mJustificationInfo.mIsEndJustifiable = true;
3077 // aStart, aLength in transformed string offsets
3078 void
3079 PropertyProvider::GetSpacing(uint32_t aStart, uint32_t aLength,
3080 Spacing* aSpacing)
3082 GetSpacingInternal(aStart, aLength, aSpacing,
3083 (mTextRun->GetFlags() & nsTextFrameUtils::TEXT_HAS_TAB) == 0);
3086 static bool
3087 CanAddSpacingAfter(gfxTextRun* aTextRun, uint32_t aOffset)
3089 if (aOffset + 1 >= aTextRun->GetLength())
3090 return true;
3091 return aTextRun->IsClusterStart(aOffset + 1) &&
3092 aTextRun->IsLigatureGroupStart(aOffset + 1);
3095 void
3096 PropertyProvider::GetSpacingInternal(uint32_t aStart, uint32_t aLength,
3097 Spacing* aSpacing, bool aIgnoreTabs)
3099 NS_PRECONDITION(IsInBounds(mStart, mLength, aStart, aLength), "Range out of bounds");
3101 uint32_t index;
3102 for (index = 0; index < aLength; ++index) {
3103 aSpacing[index].mBefore = 0.0;
3104 aSpacing[index].mAfter = 0.0;
3107 // Find our offset into the original+transformed string
3108 gfxSkipCharsIterator start(mStart);
3109 start.SetSkippedOffset(aStart);
3111 // First, compute the word and letter spacing
3112 if (mWordSpacing || mLetterSpacing) {
3113 // Iterate over non-skipped characters
3114 nsSkipCharsRunIterator
3115 run(start, nsSkipCharsRunIterator::LENGTH_UNSKIPPED_ONLY, aLength);
3116 while (run.NextRun()) {
3117 uint32_t runOffsetInSubstring = run.GetSkippedOffset() - aStart;
3118 gfxSkipCharsIterator iter = run.GetPos();
3119 for (int32_t i = 0; i < run.GetRunLength(); ++i) {
3120 if (CanAddSpacingAfter(mTextRun, run.GetSkippedOffset() + i)) {
3121 // End of a cluster, not in a ligature: put letter-spacing after it
3122 aSpacing[runOffsetInSubstring + i].mAfter += mLetterSpacing;
3124 if (IsCSSWordSpacingSpace(mFrag, i + run.GetOriginalOffset(),
3125 mTextStyle)) {
3126 // It kinda sucks, but space characters can be part of clusters,
3127 // and even still be whitespace (I think!)
3128 iter.SetSkippedOffset(run.GetSkippedOffset() + i);
3129 FindClusterEnd(mTextRun, run.GetOriginalOffset() + run.GetRunLength(),
3130 &iter);
3131 aSpacing[iter.GetSkippedOffset() - aStart].mAfter += mWordSpacing;
3137 // Ignore tab spacing rather than computing it, if the tab size is 0
3138 if (!aIgnoreTabs)
3139 aIgnoreTabs = mFrame->StyleText()->mTabSize == 0;
3141 // Now add tab spacing, if there is any
3142 if (!aIgnoreTabs) {
3143 CalcTabWidths(aStart, aLength);
3144 if (mTabWidths) {
3145 mTabWidths->ApplySpacing(aSpacing,
3146 aStart - mStart.GetSkippedOffset(), aLength);
3150 // Now add in justification spacing
3151 if (mJustificationSpacing > 0 && mTotalJustificationGaps) {
3152 // If there is any spaces trimmed at the end, aStart + aLength may
3153 // be larger than the flags array. When that happens, we can simply
3154 // ignore those spaces.
3155 auto arrayEnd = mJustificationArrayStart +
3156 static_cast<uint32_t>(mJustificationAssignments.Length());
3157 auto end = std::min(aStart + aLength, arrayEnd);
3158 MOZ_ASSERT(aStart >= mJustificationArrayStart);
3159 JustificationApplicationState state(
3160 mTotalJustificationGaps, NSToCoordRound(mJustificationSpacing));
3161 for (auto i = aStart; i < end; i++) {
3162 const auto& assign =
3163 mJustificationAssignments[i - mJustificationArrayStart];
3164 aSpacing[i - aStart].mBefore += state.Consume(assign.mGapsAtStart);
3165 aSpacing[i - aStart].mAfter += state.Consume(assign.mGapsAtEnd);
3170 static gfxFloat
3171 ComputeTabWidthAppUnits(nsIFrame* aFrame, gfxTextRun* aTextRun)
3173 // Get the number of spaces from CSS -moz-tab-size
3174 const nsStyleText* textStyle = aFrame->StyleText();
3176 // Round the space width when converting to appunits the same way
3177 // textruns do
3178 gfxFloat spaceWidthAppUnits =
3179 NS_round(GetFirstFontMetrics(aTextRun->GetFontGroup(),
3180 aTextRun->UseCenterBaseline()).spaceWidth *
3181 aTextRun->GetAppUnitsPerDevUnit());
3182 return textStyle->mTabSize * spaceWidthAppUnits;
3185 // aX and the result are in whole appunits.
3186 static gfxFloat
3187 AdvanceToNextTab(gfxFloat aX, nsIFrame* aFrame,
3188 gfxTextRun* aTextRun, gfxFloat* aCachedTabWidth)
3190 if (*aCachedTabWidth < 0) {
3191 *aCachedTabWidth = ComputeTabWidthAppUnits(aFrame, aTextRun);
3194 // Advance aX to the next multiple of *aCachedTabWidth. We must advance
3195 // by at least 1 appunit.
3196 // XXX should we make this 1 CSS pixel?
3197 return ceil((aX + 1)/(*aCachedTabWidth))*(*aCachedTabWidth);
3200 void
3201 PropertyProvider::CalcTabWidths(uint32_t aStart, uint32_t aLength)
3203 if (!mTabWidths) {
3204 if (mReflowing && !mLineContainer) {
3205 // Intrinsic width computation does its own tab processing. We
3206 // just don't do anything here.
3207 return;
3209 if (!mReflowing) {
3210 mTabWidths = static_cast<TabWidthStore*>
3211 (mFrame->Properties().Get(TabWidthProperty()));
3212 #ifdef DEBUG
3213 // If we're not reflowing, we should have already computed the
3214 // tab widths; check that they're available as far as the last
3215 // tab character present (if any)
3216 for (uint32_t i = aStart + aLength; i > aStart; --i) {
3217 if (mTextRun->CharIsTab(i - 1)) {
3218 uint32_t startOffset = mStart.GetSkippedOffset();
3219 NS_ASSERTION(mTabWidths && mTabWidths->mLimit + startOffset >= i,
3220 "Precomputed tab widths are missing!");
3221 break;
3224 #endif
3225 return;
3229 uint32_t startOffset = mStart.GetSkippedOffset();
3230 MOZ_ASSERT(aStart >= startOffset, "wrong start offset");
3231 MOZ_ASSERT(aStart + aLength <= startOffset + mLength, "beyond the end");
3232 uint32_t tabsEnd =
3233 (mTabWidths ? mTabWidths->mLimit : mTabWidthsAnalyzedLimit) + startOffset;
3234 if (tabsEnd < aStart + aLength) {
3235 NS_ASSERTION(mReflowing,
3236 "We need precomputed tab widths, but don't have enough.");
3238 gfxFloat tabWidth = -1;
3239 for (uint32_t i = tabsEnd; i < aStart + aLength; ++i) {
3240 Spacing spacing;
3241 GetSpacingInternal(i, 1, &spacing, true);
3242 mOffsetFromBlockOriginForTabs += spacing.mBefore;
3244 if (!mTextRun->CharIsTab(i)) {
3245 if (mTextRun->IsClusterStart(i)) {
3246 uint32_t clusterEnd = i + 1;
3247 while (clusterEnd < mTextRun->GetLength() &&
3248 !mTextRun->IsClusterStart(clusterEnd)) {
3249 ++clusterEnd;
3251 mOffsetFromBlockOriginForTabs +=
3252 mTextRun->GetAdvanceWidth(i, clusterEnd - i, nullptr);
3254 } else {
3255 if (!mTabWidths) {
3256 mTabWidths = new TabWidthStore(mFrame->GetContentOffset());
3257 mFrame->Properties().Set(TabWidthProperty(), mTabWidths);
3259 double nextTab = AdvanceToNextTab(mOffsetFromBlockOriginForTabs,
3260 mFrame, mTextRun, &tabWidth);
3261 mTabWidths->mWidths.AppendElement(TabWidth(i - startOffset,
3262 NSToIntRound(nextTab - mOffsetFromBlockOriginForTabs)));
3263 mOffsetFromBlockOriginForTabs = nextTab;
3266 mOffsetFromBlockOriginForTabs += spacing.mAfter;
3269 if (mTabWidths) {
3270 mTabWidths->mLimit = aStart + aLength - startOffset;
3274 if (!mTabWidths) {
3275 // Delete any stale property that may be left on the frame
3276 mFrame->Properties().Delete(TabWidthProperty());
3277 mTabWidthsAnalyzedLimit = std::max(mTabWidthsAnalyzedLimit,
3278 aStart + aLength - startOffset);
3282 gfxFloat
3283 PropertyProvider::GetHyphenWidth()
3285 if (mHyphenWidth < 0) {
3286 mHyphenWidth = GetFontGroup()->GetHyphenWidth(this);
3288 return mHyphenWidth + mLetterSpacing;
3291 void
3292 PropertyProvider::GetHyphenationBreaks(uint32_t aStart, uint32_t aLength,
3293 bool* aBreakBefore)
3295 NS_PRECONDITION(IsInBounds(mStart, mLength, aStart, aLength), "Range out of bounds");
3296 NS_PRECONDITION(mLength != INT32_MAX, "Can't call this with undefined length");
3298 if (!mTextStyle->WhiteSpaceCanWrap(mFrame) ||
3299 mTextStyle->mHyphens == NS_STYLE_HYPHENS_NONE)
3301 memset(aBreakBefore, false, aLength*sizeof(bool));
3302 return;
3305 // Iterate through the original-string character runs
3306 nsSkipCharsRunIterator
3307 run(mStart, nsSkipCharsRunIterator::LENGTH_UNSKIPPED_ONLY, aLength);
3308 run.SetSkippedOffset(aStart);
3309 // We need to visit skipped characters so that we can detect SHY
3310 run.SetVisitSkipped();
3312 int32_t prevTrailingCharOffset = run.GetPos().GetOriginalOffset() - 1;
3313 bool allowHyphenBreakBeforeNextChar =
3314 prevTrailingCharOffset >= mStart.GetOriginalOffset() &&
3315 prevTrailingCharOffset < mStart.GetOriginalOffset() + mLength &&
3316 mFrag->CharAt(prevTrailingCharOffset) == CH_SHY;
3318 while (run.NextRun()) {
3319 NS_ASSERTION(run.GetRunLength() > 0, "Shouldn't return zero-length runs");
3320 if (run.IsSkipped()) {
3321 // Check if there's a soft hyphen which would let us hyphenate before
3322 // the next non-skipped character. Don't look at soft hyphens followed
3323 // by other skipped characters, we won't use them.
3324 allowHyphenBreakBeforeNextChar =
3325 mFrag->CharAt(run.GetOriginalOffset() + run.GetRunLength() - 1) == CH_SHY;
3326 } else {
3327 int32_t runOffsetInSubstring = run.GetSkippedOffset() - aStart;
3328 memset(aBreakBefore + runOffsetInSubstring, false, run.GetRunLength()*sizeof(bool));
3329 // Don't allow hyphen breaks at the start of the line
3330 aBreakBefore[runOffsetInSubstring] = allowHyphenBreakBeforeNextChar &&
3331 (!(mFrame->GetStateBits() & TEXT_START_OF_LINE) ||
3332 run.GetSkippedOffset() > mStart.GetSkippedOffset());
3333 allowHyphenBreakBeforeNextChar = false;
3337 if (mTextStyle->mHyphens == NS_STYLE_HYPHENS_AUTO) {
3338 for (uint32_t i = 0; i < aLength; ++i) {
3339 if (mTextRun->CanHyphenateBefore(aStart + i)) {
3340 aBreakBefore[i] = true;
3346 void
3347 PropertyProvider::InitializeForDisplay(bool aTrimAfter)
3349 nsTextFrame::TrimmedOffsets trimmed =
3350 mFrame->GetTrimmedOffsets(mFrag, aTrimAfter);
3351 mStart.SetOriginalOffset(trimmed.mStart);
3352 mLength = trimmed.mLength;
3353 SetupJustificationSpacing(true);
3356 void
3357 PropertyProvider::InitializeForMeasure()
3359 nsTextFrame::TrimmedOffsets trimmed =
3360 mFrame->GetTrimmedOffsets(mFrag, true, false);
3361 mStart.SetOriginalOffset(trimmed.mStart);
3362 mLength = trimmed.mLength;
3363 SetupJustificationSpacing(false);
3367 static uint32_t GetSkippedDistance(const gfxSkipCharsIterator& aStart,
3368 const gfxSkipCharsIterator& aEnd)
3370 return aEnd.GetSkippedOffset() - aStart.GetSkippedOffset();
3373 void
3374 PropertyProvider::SetupJustificationSpacing(bool aPostReflow)
3376 NS_PRECONDITION(mLength != INT32_MAX, "Can't call this with undefined length");
3378 if (!(mFrame->GetStateBits() & TEXT_JUSTIFICATION_ENABLED))
3379 return;
3381 gfxSkipCharsIterator start(mStart), end(mStart);
3382 // We can't just use our mLength here; when InitializeForDisplay is
3383 // called with false for aTrimAfter, we still shouldn't be assigning
3384 // justification space to any trailing whitespace.
3385 nsTextFrame::TrimmedOffsets trimmed =
3386 mFrame->GetTrimmedOffsets(mFrag, true, aPostReflow);
3387 end.AdvanceOriginal(trimmed.mLength);
3388 gfxSkipCharsIterator realEnd(end);
3389 ComputeJustification(start.GetOriginalOffset(),
3390 end.GetOriginalOffset() - start.GetOriginalOffset());
3392 auto assign = mFrame->GetJustificationAssignment();
3393 mTotalJustificationGaps =
3394 JustificationUtils::CountGaps(mJustificationInfo, assign);
3395 if (!mTotalJustificationGaps || mJustificationAssignments.IsEmpty()) {
3396 // Nothing to do, nothing is justifiable and we shouldn't have any
3397 // justification space assigned
3398 return;
3401 // Remember that textrun measurements are in the run's orientation,
3402 // so its advance "width" is actually a height in vertical writing modes,
3403 // corresponding to the inline-direction of the frame.
3404 gfxFloat naturalWidth =
3405 mTextRun->GetAdvanceWidth(mStart.GetSkippedOffset(),
3406 GetSkippedDistance(mStart, realEnd), this);
3407 if (mFrame->GetStateBits() & TEXT_HYPHEN_BREAK) {
3408 naturalWidth += GetHyphenWidth();
3410 mJustificationSpacing = mFrame->ISize() - naturalWidth;
3411 if (mJustificationSpacing <= 0) {
3412 // No space available
3413 return;
3416 mJustificationAssignments[0].mGapsAtStart = assign.mGapsAtStart;
3417 mJustificationAssignments.LastElement().mGapsAtEnd = assign.mGapsAtEnd;
3420 //----------------------------------------------------------------------
3422 static nscolor
3423 EnsureDifferentColors(nscolor colorA, nscolor colorB)
3425 if (colorA == colorB) {
3426 nscolor res;
3427 res = NS_RGB(NS_GET_R(colorA) ^ 0xff,
3428 NS_GET_G(colorA) ^ 0xff,
3429 NS_GET_B(colorA) ^ 0xff);
3430 return res;
3432 return colorA;
3435 //-----------------------------------------------------------------------------
3437 nsTextPaintStyle::nsTextPaintStyle(nsTextFrame* aFrame)
3438 : mFrame(aFrame),
3439 mPresContext(aFrame->PresContext()),
3440 mInitCommonColors(false),
3441 mInitSelectionColorsAndShadow(false),
3442 mResolveColors(true),
3443 mHasSelectionShadow(false)
3445 for (uint32_t i = 0; i < ArrayLength(mSelectionStyle); i++)
3446 mSelectionStyle[i].mInit = false;
3449 bool
3450 nsTextPaintStyle::EnsureSufficientContrast(nscolor *aForeColor, nscolor *aBackColor)
3452 InitCommonColors();
3454 // If the combination of selection background color and frame background color
3455 // is sufficient contrast, don't exchange the selection colors.
3456 int32_t backLuminosityDifference =
3457 NS_LUMINOSITY_DIFFERENCE(*aBackColor, mFrameBackgroundColor);
3458 if (backLuminosityDifference >= mSufficientContrast)
3459 return false;
3461 // Otherwise, we should use the higher-contrast color for the selection
3462 // background color.
3463 int32_t foreLuminosityDifference =
3464 NS_LUMINOSITY_DIFFERENCE(*aForeColor, mFrameBackgroundColor);
3465 if (backLuminosityDifference < foreLuminosityDifference) {
3466 nscolor tmpColor = *aForeColor;
3467 *aForeColor = *aBackColor;
3468 *aBackColor = tmpColor;
3469 return true;
3471 return false;
3474 nscolor
3475 nsTextPaintStyle::GetTextColor()
3477 if (mFrame->IsSVGText()) {
3478 if (!mResolveColors)
3479 return NS_SAME_AS_FOREGROUND_COLOR;
3481 const nsStyleSVG* style = mFrame->StyleSVG();
3482 switch (style->mFill.mType) {
3483 case eStyleSVGPaintType_None:
3484 return NS_RGBA(0, 0, 0, 0);
3485 case eStyleSVGPaintType_Color:
3486 return nsLayoutUtils::GetColor(mFrame, eCSSProperty_fill);
3487 default:
3488 NS_ERROR("cannot resolve SVG paint to nscolor");
3489 return NS_RGBA(0, 0, 0, 255);
3492 return nsLayoutUtils::GetColor(mFrame, eCSSProperty_color);
3495 bool
3496 nsTextPaintStyle::GetSelectionColors(nscolor* aForeColor,
3497 nscolor* aBackColor)
3499 NS_ASSERTION(aForeColor, "aForeColor is null");
3500 NS_ASSERTION(aBackColor, "aBackColor is null");
3502 if (!InitSelectionColorsAndShadow())
3503 return false;
3505 *aForeColor = mSelectionTextColor;
3506 *aBackColor = mSelectionBGColor;
3507 return true;
3510 void
3511 nsTextPaintStyle::GetHighlightColors(nscolor* aForeColor,
3512 nscolor* aBackColor)
3514 NS_ASSERTION(aForeColor, "aForeColor is null");
3515 NS_ASSERTION(aBackColor, "aBackColor is null");
3517 nscolor backColor =
3518 LookAndFeel::GetColor(LookAndFeel::eColorID_TextHighlightBackground);
3519 nscolor foreColor =
3520 LookAndFeel::GetColor(LookAndFeel::eColorID_TextHighlightForeground);
3521 EnsureSufficientContrast(&foreColor, &backColor);
3522 *aForeColor = foreColor;
3523 *aBackColor = backColor;
3526 void
3527 nsTextPaintStyle::GetURLSecondaryColor(nscolor* aForeColor)
3529 NS_ASSERTION(aForeColor, "aForeColor is null");
3531 nscolor textColor = GetTextColor();
3532 textColor = NS_RGBA(NS_GET_R(textColor),
3533 NS_GET_G(textColor),
3534 NS_GET_B(textColor),
3535 (uint8_t)(255 * 0.5f));
3536 // Don't use true alpha color for readability.
3537 InitCommonColors();
3538 *aForeColor = NS_ComposeColors(mFrameBackgroundColor, textColor);
3541 void
3542 nsTextPaintStyle::GetIMESelectionColors(int32_t aIndex,
3543 nscolor* aForeColor,
3544 nscolor* aBackColor)
3546 NS_ASSERTION(aForeColor, "aForeColor is null");
3547 NS_ASSERTION(aBackColor, "aBackColor is null");
3548 NS_ASSERTION(aIndex >= 0 && aIndex < 5, "Index out of range");
3550 nsSelectionStyle* selectionStyle = GetSelectionStyle(aIndex);
3551 *aForeColor = selectionStyle->mTextColor;
3552 *aBackColor = selectionStyle->mBGColor;
3555 bool
3556 nsTextPaintStyle::GetSelectionUnderlineForPaint(int32_t aIndex,
3557 nscolor* aLineColor,
3558 float* aRelativeSize,
3559 uint8_t* aStyle)
3561 NS_ASSERTION(aLineColor, "aLineColor is null");
3562 NS_ASSERTION(aRelativeSize, "aRelativeSize is null");
3563 NS_ASSERTION(aIndex >= 0 && aIndex < 5, "Index out of range");
3565 nsSelectionStyle* selectionStyle = GetSelectionStyle(aIndex);
3566 if (selectionStyle->mUnderlineStyle == NS_STYLE_BORDER_STYLE_NONE ||
3567 selectionStyle->mUnderlineColor == NS_TRANSPARENT ||
3568 selectionStyle->mUnderlineRelativeSize <= 0.0f)
3569 return false;
3571 *aLineColor = selectionStyle->mUnderlineColor;
3572 *aRelativeSize = selectionStyle->mUnderlineRelativeSize;
3573 *aStyle = selectionStyle->mUnderlineStyle;
3574 return true;
3577 void
3578 nsTextPaintStyle::InitCommonColors()
3580 if (mInitCommonColors)
3581 return;
3583 nsIFrame* bgFrame =
3584 nsCSSRendering::FindNonTransparentBackgroundFrame(mFrame);
3585 NS_ASSERTION(bgFrame, "Cannot find NonTransparentBackgroundFrame.");
3586 nscolor bgColor =
3587 bgFrame->GetVisitedDependentColor(eCSSProperty_background_color);
3589 nscolor defaultBgColor = mPresContext->DefaultBackgroundColor();
3590 mFrameBackgroundColor = NS_ComposeColors(defaultBgColor, bgColor);
3592 if (bgFrame->IsThemed()) {
3593 // Assume a native widget has sufficient contrast always
3594 mSufficientContrast = 0;
3595 mInitCommonColors = true;
3596 return;
3599 NS_ASSERTION(NS_GET_A(defaultBgColor) == 255,
3600 "default background color is not opaque");
3602 nscolor defaultWindowBackgroundColor =
3603 LookAndFeel::GetColor(LookAndFeel::eColorID_WindowBackground);
3604 nscolor selectionTextColor =
3605 LookAndFeel::GetColor(LookAndFeel::eColorID_TextSelectForeground);
3606 nscolor selectionBGColor =
3607 LookAndFeel::GetColor(LookAndFeel::eColorID_TextSelectBackground);
3609 mSufficientContrast =
3610 std::min(std::min(NS_SUFFICIENT_LUMINOSITY_DIFFERENCE,
3611 NS_LUMINOSITY_DIFFERENCE(selectionTextColor,
3612 selectionBGColor)),
3613 NS_LUMINOSITY_DIFFERENCE(defaultWindowBackgroundColor,
3614 selectionBGColor));
3616 mInitCommonColors = true;
3619 static Element*
3620 FindElementAncestorForMozSelection(nsIContent* aContent)
3622 NS_ENSURE_TRUE(aContent, nullptr);
3623 while (aContent && aContent->IsInNativeAnonymousSubtree()) {
3624 aContent = aContent->GetBindingParent();
3626 NS_ASSERTION(aContent, "aContent isn't in non-anonymous tree?");
3627 while (aContent && !aContent->IsElement()) {
3628 aContent = aContent->GetParent();
3630 return aContent ? aContent->AsElement() : nullptr;
3633 bool
3634 nsTextPaintStyle::InitSelectionColorsAndShadow()
3636 if (mInitSelectionColorsAndShadow)
3637 return true;
3639 int16_t selectionFlags;
3640 int16_t selectionStatus = mFrame->GetSelectionStatus(&selectionFlags);
3641 if (!(selectionFlags & nsISelectionDisplay::DISPLAY_TEXT) ||
3642 selectionStatus < nsISelectionController::SELECTION_ON) {
3643 // Not displaying the normal selection.
3644 // We're not caching this fact, so every call to GetSelectionColors
3645 // will come through here. We could avoid this, but it's not really worth it.
3646 return false;
3649 mInitSelectionColorsAndShadow = true;
3651 nsIFrame* nonGeneratedAncestor = nsLayoutUtils::GetNonGeneratedAncestor(mFrame);
3652 Element* selectionElement =
3653 FindElementAncestorForMozSelection(nonGeneratedAncestor->GetContent());
3655 if (selectionElement &&
3656 selectionStatus == nsISelectionController::SELECTION_ON) {
3657 nsRefPtr<nsStyleContext> sc = nullptr;
3658 sc = mPresContext->StyleSet()->
3659 ProbePseudoElementStyle(selectionElement,
3660 nsCSSPseudoElements::ePseudo_mozSelection,
3661 mFrame->StyleContext());
3662 // Use -moz-selection pseudo class.
3663 if (sc) {
3664 mSelectionBGColor =
3665 sc->GetVisitedDependentColor(eCSSProperty_background_color);
3666 mSelectionTextColor = sc->GetVisitedDependentColor(eCSSProperty_color);
3667 mHasSelectionShadow =
3668 nsRuleNode::HasAuthorSpecifiedRules(sc,
3669 NS_AUTHOR_SPECIFIED_TEXT_SHADOW,
3670 true);
3671 if (mHasSelectionShadow) {
3672 mSelectionShadow = sc->StyleText()->mTextShadow;
3674 return true;
3678 nscolor selectionBGColor =
3679 LookAndFeel::GetColor(LookAndFeel::eColorID_TextSelectBackground);
3681 if (selectionStatus == nsISelectionController::SELECTION_ATTENTION) {
3682 mSelectionBGColor =
3683 LookAndFeel::GetColor(
3684 LookAndFeel::eColorID_TextSelectBackgroundAttention);
3685 mSelectionBGColor = EnsureDifferentColors(mSelectionBGColor,
3686 selectionBGColor);
3687 } else if (selectionStatus != nsISelectionController::SELECTION_ON) {
3688 mSelectionBGColor =
3689 LookAndFeel::GetColor(LookAndFeel::eColorID_TextSelectBackgroundDisabled);
3690 mSelectionBGColor = EnsureDifferentColors(mSelectionBGColor,
3691 selectionBGColor);
3692 } else {
3693 mSelectionBGColor = selectionBGColor;
3696 mSelectionTextColor =
3697 LookAndFeel::GetColor(LookAndFeel::eColorID_TextSelectForeground);
3699 if (mResolveColors) {
3700 // On MacOS X, we don't exchange text color and BG color.
3701 if (mSelectionTextColor == NS_DONT_CHANGE_COLOR) {
3702 nsCSSProperty property = mFrame->IsSVGText() ? eCSSProperty_fill :
3703 eCSSProperty_color;
3704 nscoord frameColor = mFrame->GetVisitedDependentColor(property);
3705 mSelectionTextColor = EnsureDifferentColors(frameColor, mSelectionBGColor);
3706 } else if (mSelectionTextColor == NS_CHANGE_COLOR_IF_SAME_AS_BG) {
3707 nsCSSProperty property = mFrame->IsSVGText() ? eCSSProperty_fill :
3708 eCSSProperty_color;
3709 nscolor frameColor = mFrame->GetVisitedDependentColor(property);
3710 if (frameColor == mSelectionBGColor) {
3711 mSelectionTextColor =
3712 LookAndFeel::GetColor(LookAndFeel::eColorID_TextSelectForegroundCustom);
3714 } else {
3715 EnsureSufficientContrast(&mSelectionTextColor, &mSelectionBGColor);
3717 } else {
3718 if (mSelectionTextColor == NS_DONT_CHANGE_COLOR) {
3719 mSelectionTextColor = NS_SAME_AS_FOREGROUND_COLOR;
3722 return true;
3725 nsTextPaintStyle::nsSelectionStyle*
3726 nsTextPaintStyle::GetSelectionStyle(int32_t aIndex)
3728 InitSelectionStyle(aIndex);
3729 return &mSelectionStyle[aIndex];
3732 struct StyleIDs {
3733 LookAndFeel::ColorID mForeground, mBackground, mLine;
3734 LookAndFeel::IntID mLineStyle;
3735 LookAndFeel::FloatID mLineRelativeSize;
3737 static StyleIDs SelectionStyleIDs[] = {
3738 { LookAndFeel::eColorID_IMERawInputForeground,
3739 LookAndFeel::eColorID_IMERawInputBackground,
3740 LookAndFeel::eColorID_IMERawInputUnderline,
3741 LookAndFeel::eIntID_IMERawInputUnderlineStyle,
3742 LookAndFeel::eFloatID_IMEUnderlineRelativeSize },
3743 { LookAndFeel::eColorID_IMESelectedRawTextForeground,
3744 LookAndFeel::eColorID_IMESelectedRawTextBackground,
3745 LookAndFeel::eColorID_IMESelectedRawTextUnderline,
3746 LookAndFeel::eIntID_IMESelectedRawTextUnderlineStyle,
3747 LookAndFeel::eFloatID_IMEUnderlineRelativeSize },
3748 { LookAndFeel::eColorID_IMEConvertedTextForeground,
3749 LookAndFeel::eColorID_IMEConvertedTextBackground,
3750 LookAndFeel::eColorID_IMEConvertedTextUnderline,
3751 LookAndFeel::eIntID_IMEConvertedTextUnderlineStyle,
3752 LookAndFeel::eFloatID_IMEUnderlineRelativeSize },
3753 { LookAndFeel::eColorID_IMESelectedConvertedTextForeground,
3754 LookAndFeel::eColorID_IMESelectedConvertedTextBackground,
3755 LookAndFeel::eColorID_IMESelectedConvertedTextUnderline,
3756 LookAndFeel::eIntID_IMESelectedConvertedTextUnderline,
3757 LookAndFeel::eFloatID_IMEUnderlineRelativeSize },
3758 { LookAndFeel::eColorID_LAST_COLOR,
3759 LookAndFeel::eColorID_LAST_COLOR,
3760 LookAndFeel::eColorID_SpellCheckerUnderline,
3761 LookAndFeel::eIntID_SpellCheckerUnderlineStyle,
3762 LookAndFeel::eFloatID_SpellCheckerUnderlineRelativeSize }
3765 void
3766 nsTextPaintStyle::InitSelectionStyle(int32_t aIndex)
3768 NS_ASSERTION(aIndex >= 0 && aIndex < 5, "aIndex is invalid");
3769 nsSelectionStyle* selectionStyle = &mSelectionStyle[aIndex];
3770 if (selectionStyle->mInit)
3771 return;
3773 StyleIDs* styleIDs = &SelectionStyleIDs[aIndex];
3775 nscolor foreColor, backColor;
3776 if (styleIDs->mForeground == LookAndFeel::eColorID_LAST_COLOR) {
3777 foreColor = NS_SAME_AS_FOREGROUND_COLOR;
3778 } else {
3779 foreColor = LookAndFeel::GetColor(styleIDs->mForeground);
3781 if (styleIDs->mBackground == LookAndFeel::eColorID_LAST_COLOR) {
3782 backColor = NS_TRANSPARENT;
3783 } else {
3784 backColor = LookAndFeel::GetColor(styleIDs->mBackground);
3787 // Convert special color to actual color
3788 NS_ASSERTION(foreColor != NS_TRANSPARENT,
3789 "foreColor cannot be NS_TRANSPARENT");
3790 NS_ASSERTION(backColor != NS_SAME_AS_FOREGROUND_COLOR,
3791 "backColor cannot be NS_SAME_AS_FOREGROUND_COLOR");
3792 NS_ASSERTION(backColor != NS_40PERCENT_FOREGROUND_COLOR,
3793 "backColor cannot be NS_40PERCENT_FOREGROUND_COLOR");
3795 if (mResolveColors) {
3796 foreColor = GetResolvedForeColor(foreColor, GetTextColor(), backColor);
3798 if (NS_GET_A(backColor) > 0)
3799 EnsureSufficientContrast(&foreColor, &backColor);
3802 nscolor lineColor;
3803 float relativeSize;
3804 uint8_t lineStyle;
3805 GetSelectionUnderline(mPresContext, aIndex,
3806 &lineColor, &relativeSize, &lineStyle);
3808 if (mResolveColors)
3809 lineColor = GetResolvedForeColor(lineColor, foreColor, backColor);
3811 selectionStyle->mTextColor = foreColor;
3812 selectionStyle->mBGColor = backColor;
3813 selectionStyle->mUnderlineColor = lineColor;
3814 selectionStyle->mUnderlineStyle = lineStyle;
3815 selectionStyle->mUnderlineRelativeSize = relativeSize;
3816 selectionStyle->mInit = true;
3819 /* static */ bool
3820 nsTextPaintStyle::GetSelectionUnderline(nsPresContext* aPresContext,
3821 int32_t aIndex,
3822 nscolor* aLineColor,
3823 float* aRelativeSize,
3824 uint8_t* aStyle)
3826 NS_ASSERTION(aPresContext, "aPresContext is null");
3827 NS_ASSERTION(aRelativeSize, "aRelativeSize is null");
3828 NS_ASSERTION(aStyle, "aStyle is null");
3829 NS_ASSERTION(aIndex >= 0 && aIndex < 5, "Index out of range");
3831 StyleIDs& styleID = SelectionStyleIDs[aIndex];
3833 nscolor color = LookAndFeel::GetColor(styleID.mLine);
3834 int32_t style = LookAndFeel::GetInt(styleID.mLineStyle);
3835 if (style > NS_STYLE_TEXT_DECORATION_STYLE_MAX) {
3836 NS_ERROR("Invalid underline style value is specified");
3837 style = NS_STYLE_TEXT_DECORATION_STYLE_SOLID;
3839 float size = LookAndFeel::GetFloat(styleID.mLineRelativeSize);
3841 NS_ASSERTION(size, "selection underline relative size must be larger than 0");
3843 if (aLineColor) {
3844 *aLineColor = color;
3846 *aRelativeSize = size;
3847 *aStyle = style;
3849 return style != NS_STYLE_TEXT_DECORATION_STYLE_NONE &&
3850 color != NS_TRANSPARENT &&
3851 size > 0.0f;
3854 bool
3855 nsTextPaintStyle::GetSelectionShadow(nsCSSShadowArray** aShadow)
3857 if (!InitSelectionColorsAndShadow()) {
3858 return false;
3861 if (mHasSelectionShadow) {
3862 *aShadow = mSelectionShadow;
3863 return true;
3866 return false;
3869 inline nscolor Get40PercentColor(nscolor aForeColor, nscolor aBackColor)
3871 nscolor foreColor = NS_RGBA(NS_GET_R(aForeColor),
3872 NS_GET_G(aForeColor),
3873 NS_GET_B(aForeColor),
3874 (uint8_t)(255 * 0.4f));
3875 // Don't use true alpha color for readability.
3876 return NS_ComposeColors(aBackColor, foreColor);
3879 nscolor
3880 nsTextPaintStyle::GetResolvedForeColor(nscolor aColor,
3881 nscolor aDefaultForeColor,
3882 nscolor aBackColor)
3884 if (aColor == NS_SAME_AS_FOREGROUND_COLOR)
3885 return aDefaultForeColor;
3887 if (aColor != NS_40PERCENT_FOREGROUND_COLOR)
3888 return aColor;
3890 // Get actual background color
3891 nscolor actualBGColor = aBackColor;
3892 if (actualBGColor == NS_TRANSPARENT) {
3893 InitCommonColors();
3894 actualBGColor = mFrameBackgroundColor;
3896 return Get40PercentColor(aDefaultForeColor, actualBGColor);
3899 //-----------------------------------------------------------------------------
3901 #ifdef ACCESSIBILITY
3902 a11y::AccType
3903 nsTextFrame::AccessibleType()
3905 if (IsEmpty()) {
3906 nsAutoString renderedWhitespace;
3907 GetRenderedText(&renderedWhitespace, nullptr, nullptr, 0, 1);
3908 if (renderedWhitespace.IsEmpty()) {
3909 return a11y::eNoType;
3913 return a11y::eTextLeafType;
3915 #endif
3918 //-----------------------------------------------------------------------------
3919 void
3920 nsTextFrame::Init(nsIContent* aContent,
3921 nsContainerFrame* aParent,
3922 nsIFrame* aPrevInFlow)
3924 NS_ASSERTION(!aPrevInFlow, "Can't be a continuation!");
3925 NS_PRECONDITION(aContent->IsNodeOfType(nsINode::eTEXT),
3926 "Bogus content!");
3928 // Remove any NewlineOffsetProperty or InFlowContentLengthProperty since they
3929 // might be invalid if the content was modified while there was no frame
3930 aContent->DeleteProperty(nsGkAtoms::newline);
3931 if (PresContext()->BidiEnabled()) {
3932 aContent->DeleteProperty(nsGkAtoms::flowlength);
3935 // Since our content has a frame now, this flag is no longer needed.
3936 aContent->UnsetFlags(NS_CREATE_FRAME_IF_NON_WHITESPACE);
3938 // We're not a continuing frame.
3939 // mContentOffset = 0; not necessary since we get zeroed out at init
3940 nsFrame::Init(aContent, aParent, aPrevInFlow);
3943 void
3944 nsTextFrame::ClearFrameOffsetCache()
3946 // See if we need to remove ourselves from the offset cache
3947 if (GetStateBits() & TEXT_IN_OFFSET_CACHE) {
3948 nsIFrame* primaryFrame = mContent->GetPrimaryFrame();
3949 if (primaryFrame) {
3950 // The primary frame might be null here. For example, nsLineBox::DeleteLineList
3951 // just destroys the frames in order, which means that the primary frame is already
3952 // dead if we're a continuing text frame, in which case, all of its properties are
3953 // gone, and we don't need to worry about deleting this property here.
3954 primaryFrame->Properties().Delete(OffsetToFrameProperty());
3956 RemoveStateBits(TEXT_IN_OFFSET_CACHE);
3960 void
3961 nsTextFrame::DestroyFrom(nsIFrame* aDestructRoot)
3963 ClearFrameOffsetCache();
3965 // We might want to clear NS_CREATE_FRAME_IF_NON_WHITESPACE or
3966 // NS_REFRAME_IF_WHITESPACE on mContent here, since our parent frame
3967 // type might be changing. Not clear whether it's worth it.
3968 ClearTextRuns();
3969 if (mNextContinuation) {
3970 mNextContinuation->SetPrevInFlow(nullptr);
3972 // Let the base class destroy the frame
3973 nsFrame::DestroyFrom(aDestructRoot);
3976 class nsContinuingTextFrame : public nsTextFrame {
3977 public:
3978 NS_DECL_FRAMEARENA_HELPERS
3980 friend nsIFrame* NS_NewContinuingTextFrame(nsIPresShell* aPresShell, nsStyleContext* aContext);
3982 virtual void Init(nsIContent* aContent,
3983 nsContainerFrame* aParent,
3984 nsIFrame* aPrevInFlow) MOZ_OVERRIDE;
3986 virtual void DestroyFrom(nsIFrame* aDestructRoot) MOZ_OVERRIDE;
3988 virtual nsIFrame* GetPrevContinuation() const MOZ_OVERRIDE {
3989 return mPrevContinuation;
3991 virtual void SetPrevContinuation(nsIFrame* aPrevContinuation) MOZ_OVERRIDE {
3992 NS_ASSERTION (!aPrevContinuation || GetType() == aPrevContinuation->GetType(),
3993 "setting a prev continuation with incorrect type!");
3994 NS_ASSERTION (!nsSplittableFrame::IsInPrevContinuationChain(aPrevContinuation, this),
3995 "creating a loop in continuation chain!");
3996 mPrevContinuation = aPrevContinuation;
3997 RemoveStateBits(NS_FRAME_IS_FLUID_CONTINUATION);
3999 virtual nsIFrame* GetPrevInFlowVirtual() const MOZ_OVERRIDE {
4000 return GetPrevInFlow();
4002 nsIFrame* GetPrevInFlow() const {
4003 return (GetStateBits() & NS_FRAME_IS_FLUID_CONTINUATION) ? mPrevContinuation : nullptr;
4005 virtual void SetPrevInFlow(nsIFrame* aPrevInFlow) MOZ_OVERRIDE {
4006 NS_ASSERTION (!aPrevInFlow || GetType() == aPrevInFlow->GetType(),
4007 "setting a prev in flow with incorrect type!");
4008 NS_ASSERTION (!nsSplittableFrame::IsInPrevContinuationChain(aPrevInFlow, this),
4009 "creating a loop in continuation chain!");
4010 mPrevContinuation = aPrevInFlow;
4011 AddStateBits(NS_FRAME_IS_FLUID_CONTINUATION);
4013 virtual nsIFrame* FirstInFlow() const MOZ_OVERRIDE;
4014 virtual nsIFrame* FirstContinuation() const MOZ_OVERRIDE;
4016 virtual void AddInlineMinISize(nsRenderingContext *aRenderingContext,
4017 InlineMinISizeData *aData) MOZ_OVERRIDE;
4018 virtual void AddInlinePrefISize(nsRenderingContext *aRenderingContext,
4019 InlinePrefISizeData *aData) MOZ_OVERRIDE;
4021 virtual nsresult GetRenderedText(nsAString* aString = nullptr,
4022 gfxSkipChars* aSkipChars = nullptr,
4023 gfxSkipCharsIterator* aSkipIter = nullptr,
4024 uint32_t aSkippedStartOffset = 0,
4025 uint32_t aSkippedMaxLength = UINT32_MAX) MOZ_OVERRIDE
4026 { return NS_ERROR_NOT_IMPLEMENTED; } // Call on a primary text frame only
4028 protected:
4029 explicit nsContinuingTextFrame(nsStyleContext* aContext) : nsTextFrame(aContext) {}
4030 nsIFrame* mPrevContinuation;
4033 void
4034 nsContinuingTextFrame::Init(nsIContent* aContent,
4035 nsContainerFrame* aParent,
4036 nsIFrame* aPrevInFlow)
4038 NS_ASSERTION(aPrevInFlow, "Must be a continuation!");
4039 // NOTE: bypassing nsTextFrame::Init!!!
4040 nsFrame::Init(aContent, aParent, aPrevInFlow);
4042 nsTextFrame* nextContinuation =
4043 static_cast<nsTextFrame*>(aPrevInFlow->GetNextContinuation());
4044 // Hook the frame into the flow
4045 SetPrevInFlow(aPrevInFlow);
4046 aPrevInFlow->SetNextInFlow(this);
4047 nsTextFrame* prev = static_cast<nsTextFrame*>(aPrevInFlow);
4048 mContentOffset = prev->GetContentOffset() + prev->GetContentLengthHint();
4049 NS_ASSERTION(mContentOffset < int32_t(aContent->GetText()->GetLength()),
4050 "Creating ContinuingTextFrame, but there is no more content");
4051 if (prev->StyleContext() != StyleContext()) {
4052 // We're taking part of prev's text, and its style may be different
4053 // so clear its textrun which may no longer be valid (and don't set ours)
4054 prev->ClearTextRuns();
4055 } else {
4056 float inflation = prev->GetFontSizeInflation();
4057 SetFontSizeInflation(inflation);
4058 mTextRun = prev->GetTextRun(nsTextFrame::eInflated);
4059 if (inflation != 1.0f) {
4060 gfxTextRun *uninflatedTextRun =
4061 prev->GetTextRun(nsTextFrame::eNotInflated);
4062 if (uninflatedTextRun) {
4063 SetTextRun(uninflatedTextRun, nsTextFrame::eNotInflated, 1.0f);
4067 if (aPrevInFlow->GetStateBits() & NS_FRAME_IS_BIDI) {
4068 FramePropertyTable *propTable = PresContext()->PropertyTable();
4069 // Get all the properties from the prev-in-flow first to take
4070 // advantage of the propTable's cache and simplify the assertion below
4071 void* embeddingLevel = propTable->Get(aPrevInFlow, EmbeddingLevelProperty());
4072 void* baseLevel = propTable->Get(aPrevInFlow, BaseLevelProperty());
4073 void* paragraphDepth = propTable->Get(aPrevInFlow, ParagraphDepthProperty());
4074 propTable->Set(this, EmbeddingLevelProperty(), embeddingLevel);
4075 propTable->Set(this, BaseLevelProperty(), baseLevel);
4076 propTable->Set(this, ParagraphDepthProperty(), paragraphDepth);
4078 if (nextContinuation) {
4079 SetNextContinuation(nextContinuation);
4080 nextContinuation->SetPrevContinuation(this);
4081 // Adjust next-continuations' content offset as needed.
4082 while (nextContinuation &&
4083 nextContinuation->GetContentOffset() < mContentOffset) {
4084 NS_ASSERTION(
4085 embeddingLevel == propTable->Get(nextContinuation, EmbeddingLevelProperty()) &&
4086 baseLevel == propTable->Get(nextContinuation, BaseLevelProperty()) &&
4087 paragraphDepth == propTable->Get(nextContinuation, ParagraphDepthProperty()),
4088 "stealing text from different type of BIDI continuation");
4089 nextContinuation->mContentOffset = mContentOffset;
4090 nextContinuation = static_cast<nsTextFrame*>(nextContinuation->GetNextContinuation());
4093 mState |= NS_FRAME_IS_BIDI;
4094 } // prev frame is bidi
4097 void
4098 nsContinuingTextFrame::DestroyFrom(nsIFrame* aDestructRoot)
4100 ClearFrameOffsetCache();
4102 // The text associated with this frame will become associated with our
4103 // prev-continuation. If that means the text has changed style, then
4104 // we need to wipe out the text run for the text.
4105 // Note that mPrevContinuation can be null if we're destroying the whole
4106 // frame chain from the start to the end.
4107 // If this frame is mentioned in the userData for a textrun (say
4108 // because there's a direction change at the start of this frame), then
4109 // we have to clear the textrun because we're going away and the
4110 // textrun had better not keep a dangling reference to us.
4111 if (IsInTextRunUserData() ||
4112 (mPrevContinuation &&
4113 mPrevContinuation->StyleContext() != StyleContext())) {
4114 ClearTextRuns();
4115 // Clear the previous continuation's text run also, so that it can rebuild
4116 // the text run to include our text.
4117 if (mPrevContinuation) {
4118 nsTextFrame *prevContinuationText =
4119 static_cast<nsTextFrame*>(mPrevContinuation);
4120 prevContinuationText->ClearTextRuns();
4123 nsSplittableFrame::RemoveFromFlow(this);
4124 // Let the base class destroy the frame
4125 nsFrame::DestroyFrom(aDestructRoot);
4128 nsIFrame*
4129 nsContinuingTextFrame::FirstInFlow() const
4131 // Can't cast to |nsContinuingTextFrame*| because the first one isn't.
4132 nsIFrame *firstInFlow,
4133 *previous = const_cast<nsIFrame*>
4134 (static_cast<const nsIFrame*>(this));
4135 do {
4136 firstInFlow = previous;
4137 previous = firstInFlow->GetPrevInFlow();
4138 } while (previous);
4139 MOZ_ASSERT(firstInFlow, "post-condition failed");
4140 return firstInFlow;
4143 nsIFrame*
4144 nsContinuingTextFrame::FirstContinuation() const
4146 // Can't cast to |nsContinuingTextFrame*| because the first one isn't.
4147 nsIFrame *firstContinuation,
4148 *previous = const_cast<nsIFrame*>
4149 (static_cast<const nsIFrame*>(mPrevContinuation));
4151 NS_ASSERTION(previous, "How can an nsContinuingTextFrame be the first continuation?");
4153 do {
4154 firstContinuation = previous;
4155 previous = firstContinuation->GetPrevContinuation();
4156 } while (previous);
4157 MOZ_ASSERT(firstContinuation, "post-condition failed");
4158 return firstContinuation;
4161 // XXX Do we want to do all the work for the first-in-flow or do the
4162 // work for each part? (Be careful of first-letter / first-line, though,
4163 // especially first-line!) Doing all the work on the first-in-flow has
4164 // the advantage of avoiding the potential for incremental reflow bugs,
4165 // but depends on our maintining the frame tree in reasonable ways even
4166 // for edge cases (block-within-inline splits, nextBidi, etc.)
4168 // XXX We really need to make :first-letter happen during frame
4169 // construction.
4171 // Needed for text frames in XUL.
4172 /* virtual */ nscoord
4173 nsTextFrame::GetMinISize(nsRenderingContext *aRenderingContext)
4175 return nsLayoutUtils::MinISizeFromInline(this, aRenderingContext);
4178 // Needed for text frames in XUL.
4179 /* virtual */ nscoord
4180 nsTextFrame::GetPrefISize(nsRenderingContext *aRenderingContext)
4182 return nsLayoutUtils::PrefISizeFromInline(this, aRenderingContext);
4185 /* virtual */ void
4186 nsContinuingTextFrame::AddInlineMinISize(nsRenderingContext *aRenderingContext,
4187 InlineMinISizeData *aData)
4189 // Do nothing, since the first-in-flow accounts for everything.
4190 return;
4193 /* virtual */ void
4194 nsContinuingTextFrame::AddInlinePrefISize(nsRenderingContext *aRenderingContext,
4195 InlinePrefISizeData *aData)
4197 // Do nothing, since the first-in-flow accounts for everything.
4198 return;
4201 static void
4202 DestroySelectionDetails(SelectionDetails* aDetails)
4204 while (aDetails) {
4205 SelectionDetails* next = aDetails->mNext;
4206 delete aDetails;
4207 aDetails = next;
4211 //----------------------------------------------------------------------
4213 #if defined(DEBUG_rbs) || defined(DEBUG_bzbarsky)
4214 static void
4215 VerifyNotDirty(nsFrameState state)
4217 bool isZero = state & NS_FRAME_FIRST_REFLOW;
4218 bool isDirty = state & NS_FRAME_IS_DIRTY;
4219 if (!isZero && isDirty)
4220 NS_WARNING("internal offsets may be out-of-sync");
4222 #define DEBUG_VERIFY_NOT_DIRTY(state) \
4223 VerifyNotDirty(state)
4224 #else
4225 #define DEBUG_VERIFY_NOT_DIRTY(state)
4226 #endif
4228 nsIFrame*
4229 NS_NewTextFrame(nsIPresShell* aPresShell, nsStyleContext* aContext)
4231 return new (aPresShell) nsTextFrame(aContext);
4234 NS_IMPL_FRAMEARENA_HELPERS(nsTextFrame)
4236 nsIFrame*
4237 NS_NewContinuingTextFrame(nsIPresShell* aPresShell, nsStyleContext* aContext)
4239 return new (aPresShell) nsContinuingTextFrame(aContext);
4242 NS_IMPL_FRAMEARENA_HELPERS(nsContinuingTextFrame)
4244 nsTextFrame::~nsTextFrame()
4248 nsresult
4249 nsTextFrame::GetCursor(const nsPoint& aPoint,
4250 nsIFrame::Cursor& aCursor)
4252 FillCursorInformationFromStyle(StyleUserInterface(), aCursor);
4253 if (NS_STYLE_CURSOR_AUTO == aCursor.mCursor) {
4254 aCursor.mCursor = GetWritingMode().IsVertical()
4255 ? NS_STYLE_CURSOR_VERTICAL_TEXT : NS_STYLE_CURSOR_TEXT;
4256 // If this is editable, we should ignore tabindex value.
4257 if (mContent->IsEditable()) {
4258 return NS_OK;
4261 // If tabindex >= 0, use default cursor to indicate it's not selectable
4262 nsIFrame *ancestorFrame = this;
4263 while ((ancestorFrame = ancestorFrame->GetParent()) != nullptr) {
4264 nsIContent *ancestorContent = ancestorFrame->GetContent();
4265 if (ancestorContent && ancestorContent->HasAttr(kNameSpaceID_None, nsGkAtoms::tabindex)) {
4266 nsAutoString tabIndexStr;
4267 ancestorContent->GetAttr(kNameSpaceID_None, nsGkAtoms::tabindex, tabIndexStr);
4268 if (!tabIndexStr.IsEmpty()) {
4269 nsresult rv;
4270 int32_t tabIndexVal = tabIndexStr.ToInteger(&rv);
4271 if (NS_SUCCEEDED(rv) && tabIndexVal >= 0) {
4272 aCursor.mCursor = NS_STYLE_CURSOR_DEFAULT;
4273 break;
4278 return NS_OK;
4279 } else {
4280 return nsFrame::GetCursor(aPoint, aCursor);
4284 nsIFrame*
4285 nsTextFrame::LastInFlow() const
4287 nsTextFrame* lastInFlow = const_cast<nsTextFrame*>(this);
4288 while (lastInFlow->GetNextInFlow()) {
4289 lastInFlow = static_cast<nsTextFrame*>(lastInFlow->GetNextInFlow());
4291 MOZ_ASSERT(lastInFlow, "post-condition failed");
4292 return lastInFlow;
4295 nsIFrame*
4296 nsTextFrame::LastContinuation() const
4298 nsTextFrame* lastContinuation = const_cast<nsTextFrame*>(this);
4299 while (lastContinuation->mNextContinuation) {
4300 lastContinuation =
4301 static_cast<nsTextFrame*>(lastContinuation->mNextContinuation);
4303 MOZ_ASSERT(lastContinuation, "post-condition failed");
4304 return lastContinuation;
4307 void
4308 nsTextFrame::InvalidateFrame(uint32_t aDisplayItemKey)
4310 if (IsSVGText()) {
4311 nsIFrame* svgTextFrame =
4312 nsLayoutUtils::GetClosestFrameOfType(GetParent(),
4313 nsGkAtoms::svgTextFrame);
4314 svgTextFrame->InvalidateFrame();
4315 return;
4317 nsTextFrameBase::InvalidateFrame(aDisplayItemKey);
4320 void
4321 nsTextFrame::InvalidateFrameWithRect(const nsRect& aRect, uint32_t aDisplayItemKey)
4323 if (IsSVGText()) {
4324 nsIFrame* svgTextFrame =
4325 nsLayoutUtils::GetClosestFrameOfType(GetParent(),
4326 nsGkAtoms::svgTextFrame);
4327 svgTextFrame->InvalidateFrame();
4328 return;
4330 nsTextFrameBase::InvalidateFrameWithRect(aRect, aDisplayItemKey);
4333 gfxTextRun*
4334 nsTextFrame::GetUninflatedTextRun()
4336 return static_cast<gfxTextRun*>(
4337 Properties().Get(UninflatedTextRunProperty()));
4340 void
4341 nsTextFrame::SetTextRun(gfxTextRun* aTextRun, TextRunType aWhichTextRun,
4342 float aInflation)
4344 NS_ASSERTION(aTextRun, "must have text run");
4346 // Our inflated text run is always stored in mTextRun. In the cases
4347 // where our current inflation is not 1.0, however, we store two text
4348 // runs, and the uninflated one goes in a frame property. We never
4349 // store a single text run in both.
4350 if (aWhichTextRun == eInflated) {
4351 if (HasFontSizeInflation() && aInflation == 1.0f) {
4352 // FIXME: Probably shouldn't do this within each SetTextRun
4353 // method, but it doesn't hurt.
4354 ClearTextRun(nullptr, nsTextFrame::eNotInflated);
4356 SetFontSizeInflation(aInflation);
4357 } else {
4358 NS_ABORT_IF_FALSE(aInflation == 1.0f, "unexpected inflation");
4359 if (HasFontSizeInflation()) {
4360 Properties().Set(UninflatedTextRunProperty(), aTextRun);
4361 return;
4363 // fall through to setting mTextRun
4366 mTextRun = aTextRun;
4368 // FIXME: Add assertions testing the relationship between
4369 // GetFontSizeInflation() and whether we have an uninflated text run
4370 // (but be aware that text runs can go away).
4373 bool
4374 nsTextFrame::RemoveTextRun(gfxTextRun* aTextRun)
4376 if (aTextRun == mTextRun) {
4377 mTextRun = nullptr;
4378 return true;
4380 FrameProperties props = Properties();
4381 if ((GetStateBits() & TEXT_HAS_FONT_INFLATION) &&
4382 props.Get(UninflatedTextRunProperty()) == aTextRun) {
4383 props.Delete(UninflatedTextRunProperty());
4384 return true;
4386 return false;
4389 void
4390 nsTextFrame::ClearTextRun(nsTextFrame* aStartContinuation,
4391 TextRunType aWhichTextRun)
4393 gfxTextRun* textRun = GetTextRun(aWhichTextRun);
4394 if (!textRun) {
4395 return;
4398 DebugOnly<bool> checkmTextrun = textRun == mTextRun;
4399 UnhookTextRunFromFrames(textRun, aStartContinuation);
4400 MOZ_ASSERT(checkmTextrun ? !mTextRun
4401 : !Properties().Get(UninflatedTextRunProperty()));
4403 // see comments in BuildTextRunForFrames...
4404 // if (textRun->GetFlags() & gfxFontGroup::TEXT_IS_PERSISTENT) {
4405 // NS_ERROR("Shouldn't reach here for now...");
4406 // // the textrun's text may be referencing a DOM node that has changed,
4407 // // so we'd better kill this textrun now.
4408 // if (textRun->GetExpirationState()->IsTracked()) {
4409 // gTextRuns->RemoveFromCache(textRun);
4410 // }
4411 // delete textRun;
4412 // return;
4413 // }
4415 if (!textRun->GetUserData()) {
4416 // Remove it now because it's not doing anything useful
4417 gTextRuns->RemoveFromCache(textRun);
4418 delete textRun;
4422 void
4423 nsTextFrame::DisconnectTextRuns()
4425 MOZ_ASSERT(!IsInTextRunUserData(),
4426 "Textrun mentions this frame in its user data so we can't just disconnect");
4427 mTextRun = nullptr;
4428 if ((GetStateBits() & TEXT_HAS_FONT_INFLATION)) {
4429 Properties().Delete(UninflatedTextRunProperty());
4433 nsresult
4434 nsTextFrame::CharacterDataChanged(CharacterDataChangeInfo* aInfo)
4436 mContent->DeleteProperty(nsGkAtoms::newline);
4437 if (PresContext()->BidiEnabled()) {
4438 mContent->DeleteProperty(nsGkAtoms::flowlength);
4441 // Find the first frame whose text has changed. Frames that are entirely
4442 // before the text change are completely unaffected.
4443 nsTextFrame* next;
4444 nsTextFrame* textFrame = this;
4445 while (true) {
4446 next = static_cast<nsTextFrame*>(textFrame->GetNextContinuation());
4447 if (!next || next->GetContentOffset() > int32_t(aInfo->mChangeStart))
4448 break;
4449 textFrame = next;
4452 int32_t endOfChangedText = aInfo->mChangeStart + aInfo->mReplaceLength;
4453 nsTextFrame* lastDirtiedFrame = nullptr;
4455 nsIPresShell* shell = PresContext()->GetPresShell();
4456 do {
4457 // textFrame contained deleted text (or the insertion point,
4458 // if this was a pure insertion).
4459 textFrame->mState &= ~TEXT_WHITESPACE_FLAGS;
4460 textFrame->ClearTextRuns();
4461 if (!lastDirtiedFrame ||
4462 lastDirtiedFrame->GetParent() != textFrame->GetParent()) {
4463 // Ask the parent frame to reflow me.
4464 shell->FrameNeedsReflow(textFrame, nsIPresShell::eStyleChange,
4465 NS_FRAME_IS_DIRTY);
4466 lastDirtiedFrame = textFrame;
4467 } else {
4468 // if the parent is a block, we're cheating here because we should
4469 // be marking our line dirty, but we're not. nsTextFrame::SetLength
4470 // will do that when it gets called during reflow.
4471 textFrame->AddStateBits(NS_FRAME_IS_DIRTY);
4473 textFrame->InvalidateFrame();
4475 // Below, frames that start after the deleted text will be adjusted so that
4476 // their offsets move with the trailing unchanged text. If this change
4477 // deletes more text than it inserts, those frame offsets will decrease.
4478 // We need to maintain the invariant that mContentOffset is non-decreasing
4479 // along the continuation chain. So we need to ensure that frames that
4480 // started in the deleted text are all still starting before the
4481 // unchanged text.
4482 if (textFrame->mContentOffset > endOfChangedText) {
4483 textFrame->mContentOffset = endOfChangedText;
4486 textFrame = static_cast<nsTextFrame*>(textFrame->GetNextContinuation());
4487 } while (textFrame && textFrame->GetContentOffset() < int32_t(aInfo->mChangeEnd));
4489 // This is how much the length of the string changed by --- i.e.,
4490 // how much the trailing unchanged text moved.
4491 int32_t sizeChange =
4492 aInfo->mChangeStart + aInfo->mReplaceLength - aInfo->mChangeEnd;
4494 if (sizeChange) {
4495 // Fix the offsets of the text frames that start in the trailing
4496 // unchanged text.
4497 while (textFrame) {
4498 textFrame->mContentOffset += sizeChange;
4499 // XXX we could rescue some text runs by adjusting their user data
4500 // to reflect the change in DOM offsets
4501 textFrame->ClearTextRuns();
4502 textFrame = static_cast<nsTextFrame*>(textFrame->GetNextContinuation());
4506 return NS_OK;
4509 /* virtual */ void
4510 nsTextFrame::DidSetStyleContext(nsStyleContext* aOldStyleContext)
4512 // A belt-and-braces check just in case we never get the
4513 // MarkIntrinsicISizesDirty call from the style system.
4514 if (StyleText()->mTextTransform == NS_STYLE_TEXT_TRANSFORM_CAPITALIZE &&
4515 mTextRun &&
4516 !(mTextRun->GetFlags() & nsTextFrameUtils::TEXT_IS_TRANSFORMED)) {
4517 NS_ERROR("the current textrun doesn't match the style");
4518 // The current textrun is now of the wrong type.
4519 ClearTextRuns();
4521 nsFrame::DidSetStyleContext(aOldStyleContext);
4524 class nsDisplayTextGeometry : public nsCharClipGeometry
4526 public:
4527 nsDisplayTextGeometry(nsCharClipDisplayItem* aItem, nsDisplayListBuilder* aBuilder)
4528 : nsCharClipGeometry(aItem, aBuilder)
4530 nsTextFrame* f = static_cast<nsTextFrame*>(aItem->Frame());
4531 f->GetTextDecorations(f->PresContext(), nsTextFrame::eResolvedColors, mDecorations);
4535 * We store the computed text decorations here since they are
4536 * computed using style data from parent frames. Any changes to these
4537 * styles will only invalidate the parent frame and not this frame.
4539 nsTextFrame::TextDecorations mDecorations;
4542 class nsDisplayText : public nsCharClipDisplayItem {
4543 public:
4544 nsDisplayText(nsDisplayListBuilder* aBuilder, nsTextFrame* aFrame) :
4545 nsCharClipDisplayItem(aBuilder, aFrame),
4546 mDisableSubpixelAA(false) {
4547 MOZ_COUNT_CTOR(nsDisplayText);
4549 #ifdef NS_BUILD_REFCNT_LOGGING
4550 virtual ~nsDisplayText() {
4551 MOZ_COUNT_DTOR(nsDisplayText);
4553 #endif
4555 virtual nsRect GetBounds(nsDisplayListBuilder* aBuilder,
4556 bool* aSnap) MOZ_OVERRIDE {
4557 *aSnap = false;
4558 nsRect temp = mFrame->GetVisualOverflowRectRelativeToSelf() + ToReferenceFrame();
4559 // Bug 748228
4560 temp.Inflate(mFrame->PresContext()->AppUnitsPerDevPixel());
4561 return temp;
4563 virtual void HitTest(nsDisplayListBuilder* aBuilder, const nsRect& aRect,
4564 HitTestState* aState,
4565 nsTArray<nsIFrame*> *aOutFrames) MOZ_OVERRIDE {
4566 if (nsRect(ToReferenceFrame(), mFrame->GetSize()).Intersects(aRect)) {
4567 aOutFrames->AppendElement(mFrame);
4570 virtual void Paint(nsDisplayListBuilder* aBuilder,
4571 nsRenderingContext* aCtx) MOZ_OVERRIDE;
4572 NS_DISPLAY_DECL_NAME("Text", TYPE_TEXT)
4574 virtual nsRect GetComponentAlphaBounds(nsDisplayListBuilder* aBuilder) MOZ_OVERRIDE
4576 bool snap;
4577 return GetBounds(aBuilder, &snap);
4580 virtual nsDisplayItemGeometry* AllocateGeometry(nsDisplayListBuilder* aBuilder) MOZ_OVERRIDE
4582 return new nsDisplayTextGeometry(this, aBuilder);
4585 virtual void ComputeInvalidationRegion(nsDisplayListBuilder* aBuilder,
4586 const nsDisplayItemGeometry* aGeometry,
4587 nsRegion *aInvalidRegion) MOZ_OVERRIDE
4589 const nsDisplayTextGeometry* geometry = static_cast<const nsDisplayTextGeometry*>(aGeometry);
4590 nsTextFrame* f = static_cast<nsTextFrame*>(mFrame);
4592 nsTextFrame::TextDecorations decorations;
4593 f->GetTextDecorations(f->PresContext(), nsTextFrame::eResolvedColors, decorations);
4595 bool snap;
4596 nsRect newRect = geometry->mBounds;
4597 nsRect oldRect = GetBounds(aBuilder, &snap);
4598 if (decorations != geometry->mDecorations ||
4599 mLeftEdge != geometry->mLeftEdge ||
4600 mRightEdge != geometry->mRightEdge ||
4601 !oldRect.IsEqualInterior(newRect) ||
4602 !geometry->mBorderRect.IsEqualInterior(GetBorderRect())) {
4603 aInvalidRegion->Or(oldRect, newRect);
4607 virtual void DisableComponentAlpha() MOZ_OVERRIDE {
4608 mDisableSubpixelAA = true;
4611 bool mDisableSubpixelAA;
4614 void
4615 nsDisplayText::Paint(nsDisplayListBuilder* aBuilder,
4616 nsRenderingContext* aCtx) {
4617 PROFILER_LABEL("nsDisplayText", "Paint",
4618 js::ProfileEntry::Category::GRAPHICS);
4620 // Add 1 pixel of dirty area around mVisibleRect to allow us to paint
4621 // antialiased pixels beyond the measured text extents.
4622 // This is temporary until we do this in the actual calculation of text extents.
4623 nsRect extraVisible = mVisibleRect;
4624 nscoord appUnitsPerDevPixel = mFrame->PresContext()->AppUnitsPerDevPixel();
4625 extraVisible.Inflate(appUnitsPerDevPixel, appUnitsPerDevPixel);
4626 nsTextFrame* f = static_cast<nsTextFrame*>(mFrame);
4628 gfxContext* ctx = aCtx->ThebesContext();
4629 gfxContextAutoDisableSubpixelAntialiasing disable(ctx,
4630 mDisableSubpixelAA);
4631 gfxContextAutoSaveRestore save(ctx);
4633 gfxRect pixelVisible =
4634 nsLayoutUtils::RectToGfxRect(extraVisible, appUnitsPerDevPixel);
4635 pixelVisible.Inflate(2);
4636 pixelVisible.RoundOut();
4638 ctx->NewPath();
4639 ctx->Rectangle(pixelVisible);
4640 ctx->Clip();
4642 NS_ASSERTION(mLeftEdge >= 0, "illegal left edge");
4643 NS_ASSERTION(mRightEdge >= 0, "illegal right edge");
4644 f->PaintText(aCtx, ToReferenceFrame(), extraVisible, *this);
4647 void
4648 nsTextFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
4649 const nsRect& aDirtyRect,
4650 const nsDisplayListSet& aLists)
4652 if (!IsVisibleForPainting(aBuilder))
4653 return;
4655 DO_GLOBAL_REFLOW_COUNT_DSP("nsTextFrame");
4657 if (NS_GET_A(StyleColor()->mColor) == 0 &&
4658 !IsSVGText() && !IsSelected() && !StyleText()->HasTextShadow()) {
4659 TextDecorations textDecs;
4660 GetTextDecorations(PresContext(), eResolvedColors, textDecs);
4661 if (!textDecs.HasDecorationLines()) {
4662 return;
4666 aLists.Content()->AppendNewToTop(
4667 new (aBuilder) nsDisplayText(aBuilder, this));
4670 static nsIFrame*
4671 GetGeneratedContentOwner(nsIFrame* aFrame, bool* aIsBefore)
4673 *aIsBefore = false;
4674 while (aFrame && (aFrame->GetStateBits() & NS_FRAME_GENERATED_CONTENT)) {
4675 if (aFrame->StyleContext()->GetPseudo() == nsCSSPseudoElements::before) {
4676 *aIsBefore = true;
4678 aFrame = aFrame->GetParent();
4680 return aFrame;
4683 SelectionDetails*
4684 nsTextFrame::GetSelectionDetails()
4686 const nsFrameSelection* frameSelection = GetConstFrameSelection();
4687 if (frameSelection->GetTableCellSelection()) {
4688 return nullptr;
4690 if (!(GetStateBits() & NS_FRAME_GENERATED_CONTENT)) {
4691 SelectionDetails* details =
4692 frameSelection->LookUpSelection(mContent, GetContentOffset(),
4693 GetContentLength(), false);
4694 SelectionDetails* sd;
4695 for (sd = details; sd; sd = sd->mNext) {
4696 sd->mStart += mContentOffset;
4697 sd->mEnd += mContentOffset;
4699 return details;
4702 // Check if the beginning or end of the element is selected, depending on
4703 // whether we're :before content or :after content.
4704 bool isBefore;
4705 nsIFrame* owner = GetGeneratedContentOwner(this, &isBefore);
4706 if (!owner || !owner->GetContent())
4707 return nullptr;
4709 SelectionDetails* details =
4710 frameSelection->LookUpSelection(owner->GetContent(),
4711 isBefore ? 0 : owner->GetContent()->GetChildCount(), 0, false);
4712 SelectionDetails* sd;
4713 for (sd = details; sd; sd = sd->mNext) {
4714 // The entire text is selected!
4715 sd->mStart = GetContentOffset();
4716 sd->mEnd = GetContentEnd();
4718 return details;
4721 static void
4722 PaintSelectionBackground(gfxContext* aCtx, nsPresContext* aPresContext,
4723 nscolor aColor, const gfxRect& aDirtyRect,
4724 const gfxRect& aRect,
4725 nsTextFrame::DrawPathCallbacks* aCallbacks)
4727 if (aCallbacks) {
4728 aCallbacks->NotifyBeforeSelectionBackground(aColor);
4731 gfxRect r = aRect.Intersect(aDirtyRect);
4732 // For now, we need to put this in pixel coordinates
4733 int32_t app = aPresContext->AppUnitsPerDevPixel();
4734 aCtx->NewPath();
4735 // pixel-snap
4736 aCtx->Rectangle(gfxRect(r.X() / app, r.Y() / app,
4737 r.Width() / app, r.Height() / app), true);
4739 if (aCallbacks) {
4740 aCallbacks->NotifySelectionBackgroundPathEmitted();
4741 } else {
4742 aCtx->SetColor(gfxRGBA(aColor));
4743 aCtx->Fill();
4747 // Attempt to get the LineBaselineOffset property of aChildFrame
4748 // If not set, calculate this value for all child frames of aBlockFrame
4749 static nscoord
4750 LazyGetLineBaselineOffset(nsIFrame* aChildFrame, nsBlockFrame* aBlockFrame)
4752 bool offsetFound;
4753 nscoord offset = NS_PTR_TO_INT32(
4754 aChildFrame->Properties().Get(nsIFrame::LineBaselineOffset(), &offsetFound)
4757 if (!offsetFound) {
4758 for (nsBlockFrame::line_iterator line = aBlockFrame->begin_lines(),
4759 line_end = aBlockFrame->end_lines();
4760 line != line_end; line++) {
4761 if (line->IsInline()) {
4762 int32_t n = line->GetChildCount();
4763 nscoord lineBaseline = line->BStart() + line->GetLogicalAscent();
4764 for (nsIFrame* lineFrame = line->mFirstChild;
4765 n > 0; lineFrame = lineFrame->GetNextSibling(), --n) {
4766 offset = lineBaseline - lineFrame->GetNormalPosition().y;
4767 lineFrame->Properties().Set(nsIFrame::LineBaselineOffset(),
4768 NS_INT32_TO_PTR(offset));
4772 return NS_PTR_TO_INT32(
4773 aChildFrame->Properties().Get(nsIFrame::LineBaselineOffset(), &offsetFound)
4776 } else {
4777 return offset;
4781 void
4782 nsTextFrame::GetTextDecorations(
4783 nsPresContext* aPresContext,
4784 nsTextFrame::TextDecorationColorResolution aColorResolution,
4785 nsTextFrame::TextDecorations& aDecorations)
4787 const nsCompatibility compatMode = aPresContext->CompatibilityMode();
4789 bool useOverride = false;
4790 nscolor overrideColor = NS_RGBA(0, 0, 0, 0);
4792 // frameBStartOffset represents the offset to f's BStart from our baseline in our
4793 // coordinate space
4794 // baselineOffset represents the offset from our baseline to f's baseline or
4795 // the nearest block's baseline, in our coordinate space, whichever is closest
4796 // during the particular iteration
4797 nscoord frameBStartOffset = mAscent,
4798 baselineOffset = 0;
4800 bool nearestBlockFound = false;
4801 bool vertical = GetWritingMode().IsVertical();
4803 for (nsIFrame* f = this, *fChild = nullptr;
4805 fChild = f,
4806 f = nsLayoutUtils::GetParentOrPlaceholderFor(f))
4808 nsStyleContext *const context = f->StyleContext();
4809 if (!context->HasTextDecorationLines()) {
4810 break;
4813 const nsStyleTextReset *const styleText = context->StyleTextReset();
4814 const uint8_t textDecorations = styleText->mTextDecorationLine;
4816 if (!useOverride &&
4817 (NS_STYLE_TEXT_DECORATION_LINE_OVERRIDE_ALL & textDecorations)) {
4818 // This handles the <a href="blah.html"><font color="green">La
4819 // la la</font></a> case. The link underline should be green.
4820 useOverride = true;
4821 overrideColor =
4822 nsLayoutUtils::GetColor(f, eCSSProperty_text_decoration_color);
4825 nsBlockFrame* fBlock = nsLayoutUtils::GetAsBlock(f);
4826 const bool firstBlock = !nearestBlockFound && fBlock;
4828 // Not updating positions once we hit a parent block is equivalent to
4829 // the CSS 2.1 spec that blocks should propagate decorations down to their
4830 // children (albeit the style should be preserved)
4831 // However, if we're vertically aligned within a block, then we need to
4832 // recover the correct baseline from the line by querying the FrameProperty
4833 // that should be set (see nsLineLayout::VerticalAlignLine).
4834 if (firstBlock) {
4835 // At this point, fChild can't be null since TextFrames can't be blocks
4836 if (fChild->VerticalAlignEnum() != NS_STYLE_VERTICAL_ALIGN_BASELINE) {
4838 // Since offset is the offset in the child's coordinate space, we have
4839 // to undo the accumulation to bring the transform out of the block's
4840 // coordinate space
4841 const nscoord lineBaselineOffset = LazyGetLineBaselineOffset(fChild,
4842 fBlock);
4844 baselineOffset = frameBStartOffset - lineBaselineOffset -
4845 (vertical ? fChild->GetNormalPosition().x
4846 : fChild->GetNormalPosition().y);
4849 else if (!nearestBlockFound) {
4850 // use a dummy WritingMode, because nsTextFrame::GetLogicalBaseLine
4851 // doesn't use it anyway
4852 baselineOffset = frameBStartOffset - f->GetLogicalBaseline(WritingMode());
4855 nearestBlockFound = nearestBlockFound || firstBlock;
4856 frameBStartOffset +=
4857 vertical ? f->GetNormalPosition().x : f->GetNormalPosition().y;
4859 const uint8_t style = styleText->GetDecorationStyle();
4860 if (textDecorations) {
4861 nscolor color;
4862 if (useOverride) {
4863 color = overrideColor;
4864 } else if (IsSVGText()) {
4865 // XXX We might want to do something with text-decoration-color when
4866 // painting SVG text, but it's not clear what we should do. We
4867 // at least need SVG text decorations to paint with 'fill' if
4868 // text-decoration-color has its initial value currentColor.
4869 // We could choose to interpret currentColor as "currentFill"
4870 // for SVG text, and have e.g. text-decoration-color:red to
4871 // override the fill paint of the decoration.
4872 color = aColorResolution == eResolvedColors ?
4873 nsLayoutUtils::GetColor(f, eCSSProperty_fill) :
4874 NS_SAME_AS_FOREGROUND_COLOR;
4875 } else {
4876 color = nsLayoutUtils::GetColor(f, eCSSProperty_text_decoration_color);
4879 if (textDecorations & NS_STYLE_TEXT_DECORATION_LINE_UNDERLINE) {
4880 aDecorations.mUnderlines.AppendElement(
4881 nsTextFrame::LineDecoration(f, baselineOffset, color, style));
4883 if (textDecorations & NS_STYLE_TEXT_DECORATION_LINE_OVERLINE) {
4884 aDecorations.mOverlines.AppendElement(
4885 nsTextFrame::LineDecoration(f, baselineOffset, color, style));
4887 if (textDecorations & NS_STYLE_TEXT_DECORATION_LINE_LINE_THROUGH) {
4888 aDecorations.mStrikes.AppendElement(
4889 nsTextFrame::LineDecoration(f, baselineOffset, color, style));
4893 // In all modes, if we're on an inline-block or inline-table (or
4894 // inline-stack, inline-box, inline-grid), we're done.
4895 // If we're on a ruby frame other than ruby text container, we
4896 // should continue.
4897 uint8_t display = f->GetDisplay();
4898 if (display != NS_STYLE_DISPLAY_INLINE &&
4899 (!nsStyleDisplay::IsRubyDisplayType(display) ||
4900 display == NS_STYLE_DISPLAY_RUBY_TEXT_CONTAINER) &&
4901 nsStyleDisplay::IsDisplayTypeInlineOutside(display)) {
4902 break;
4905 if (compatMode == eCompatibility_NavQuirks) {
4906 // In quirks mode, if we're on an HTML table element, we're done.
4907 if (f->GetContent()->IsHTML(nsGkAtoms::table)) {
4908 break;
4910 } else {
4911 // In standards/almost-standards mode, if we're on an
4912 // absolutely-positioned element or a floating element, we're done.
4913 if (f->IsFloating() || f->IsAbsolutelyPositioned()) {
4914 break;
4920 static float
4921 GetInflationForTextDecorations(nsIFrame* aFrame, nscoord aInflationMinFontSize)
4923 if (aFrame->IsSVGText()) {
4924 const nsIFrame* container = aFrame;
4925 while (container->GetType() != nsGkAtoms::svgTextFrame) {
4926 container = container->GetParent();
4928 NS_ASSERTION(container, "expected to find an ancestor SVGTextFrame");
4929 return
4930 static_cast<const SVGTextFrame*>(container)->GetFontSizeScaleFactor();
4932 return nsLayoutUtils::FontSizeInflationInner(aFrame, aInflationMinFontSize);
4935 void
4936 nsTextFrame::UnionAdditionalOverflow(nsPresContext* aPresContext,
4937 nsIFrame* aBlock,
4938 PropertyProvider& aProvider,
4939 nsRect* aVisualOverflowRect,
4940 bool aIncludeTextDecorations)
4942 // Text-shadow overflows
4943 nsRect shadowRect =
4944 nsLayoutUtils::GetTextShadowRectsUnion(*aVisualOverflowRect, this);
4945 aVisualOverflowRect->UnionRect(*aVisualOverflowRect, shadowRect);
4946 bool verticalRun = mTextRun->IsVertical();
4947 bool useVerticalMetrics = verticalRun && mTextRun->UseCenterBaseline();
4948 bool inverted = GetWritingMode().IsLineInverted();
4950 if (IsFloatingFirstLetterChild()) {
4951 // The underline/overline drawable area must be contained in the overflow
4952 // rect when this is in floating first letter frame at *both* modes.
4953 // In this case, aBlock is the ::first-letter frame.
4954 uint8_t decorationStyle = aBlock->StyleContext()->
4955 StyleTextReset()->GetDecorationStyle();
4956 // If the style is none, let's include decoration line rect as solid style
4957 // since changing the style from none to solid/dotted/dashed doesn't cause
4958 // reflow.
4959 if (decorationStyle == NS_STYLE_TEXT_DECORATION_STYLE_NONE) {
4960 decorationStyle = NS_STYLE_TEXT_DECORATION_STYLE_SOLID;
4962 nsFontMetrics* fontMetrics = aProvider.GetFontMetrics();
4963 nscoord underlineOffset, underlineSize;
4964 fontMetrics->GetUnderline(underlineOffset, underlineSize);
4965 nscoord maxAscent = inverted ? fontMetrics->MaxDescent()
4966 : fontMetrics->MaxAscent();
4968 gfxFloat appUnitsPerDevUnit = aPresContext->AppUnitsPerDevPixel();
4969 gfxFloat gfxWidth =
4970 (verticalRun ? aVisualOverflowRect->height
4971 : aVisualOverflowRect->width) /
4972 appUnitsPerDevUnit;
4973 gfxFloat gfxAscent = gfxFloat(mAscent) / appUnitsPerDevUnit;
4974 gfxFloat gfxMaxAscent = maxAscent / appUnitsPerDevUnit;
4975 gfxFloat gfxUnderlineSize = underlineSize / appUnitsPerDevUnit;
4976 gfxFloat gfxUnderlineOffset = underlineOffset / appUnitsPerDevUnit;
4977 nsRect underlineRect =
4978 nsCSSRendering::GetTextDecorationRect(aPresContext,
4979 gfxSize(gfxWidth, gfxUnderlineSize), gfxAscent, gfxUnderlineOffset,
4980 NS_STYLE_TEXT_DECORATION_LINE_UNDERLINE, decorationStyle, verticalRun);
4981 nsRect overlineRect =
4982 nsCSSRendering::GetTextDecorationRect(aPresContext,
4983 gfxSize(gfxWidth, gfxUnderlineSize), gfxAscent, gfxMaxAscent,
4984 NS_STYLE_TEXT_DECORATION_LINE_OVERLINE, decorationStyle, verticalRun);
4986 aVisualOverflowRect->UnionRect(*aVisualOverflowRect, underlineRect);
4987 aVisualOverflowRect->UnionRect(*aVisualOverflowRect, overlineRect);
4989 // XXX If strikeoutSize is much thicker than the underlineSize, it may
4990 // cause overflowing from the overflow rect. However, such case
4991 // isn't realistic, we don't need to compute it now.
4993 if (aIncludeTextDecorations) {
4994 // Since CSS 2.1 requires that text-decoration defined on ancestors maintain
4995 // style and position, they can be drawn at virtually any y-offset, so
4996 // maxima and minima are required to reliably generate the rectangle for
4997 // them
4998 TextDecorations textDecs;
4999 GetTextDecorations(aPresContext, eResolvedColors, textDecs);
5000 if (textDecs.HasDecorationLines()) {
5001 nscoord inflationMinFontSize =
5002 nsLayoutUtils::InflationMinFontSizeFor(aBlock);
5004 const nscoord measure = verticalRun ? GetSize().height : GetSize().width;
5005 const gfxFloat appUnitsPerDevUnit = aPresContext->AppUnitsPerDevPixel(),
5006 gfxWidth = measure / appUnitsPerDevUnit;
5007 gfxFloat ascent = gfxFloat(mAscent) / appUnitsPerDevUnit;
5008 const WritingMode wm = GetWritingMode();
5009 if (wm.IsVerticalRL()) {
5010 ascent = -ascent;
5013 nscoord topOrLeft(nscoord_MAX), bottomOrRight(nscoord_MIN);
5014 // Below we loop through all text decorations and compute the rectangle
5015 // containing all of them, in this frame's coordinate space
5016 for (uint32_t i = 0; i < textDecs.mUnderlines.Length(); ++i) {
5017 const LineDecoration& dec = textDecs.mUnderlines[i];
5018 uint8_t decorationStyle = dec.mStyle;
5019 // If the style is solid, let's include decoration line rect of solid
5020 // style since changing the style from none to solid/dotted/dashed
5021 // doesn't cause reflow.
5022 if (decorationStyle == NS_STYLE_TEXT_DECORATION_STYLE_NONE) {
5023 decorationStyle = NS_STYLE_TEXT_DECORATION_STYLE_SOLID;
5026 float inflation =
5027 GetInflationForTextDecorations(dec.mFrame, inflationMinFontSize);
5028 const gfxFont::Metrics metrics =
5029 GetFirstFontMetrics(GetFontGroupForFrame(dec.mFrame, inflation),
5030 useVerticalMetrics);
5032 const nsRect decorationRect =
5033 nsCSSRendering::GetTextDecorationRect(aPresContext,
5034 gfxSize(gfxWidth, metrics.underlineSize),
5035 ascent, metrics.underlineOffset,
5036 NS_STYLE_TEXT_DECORATION_LINE_UNDERLINE, decorationStyle,
5037 verticalRun) +
5038 nsPoint(0, -dec.mBaselineOffset);
5040 if (verticalRun) {
5041 topOrLeft = std::min(decorationRect.x, topOrLeft);
5042 bottomOrRight = std::max(decorationRect.XMost(), bottomOrRight);
5043 } else {
5044 topOrLeft = std::min(decorationRect.y, topOrLeft);
5045 bottomOrRight = std::max(decorationRect.YMost(), bottomOrRight);
5048 for (uint32_t i = 0; i < textDecs.mOverlines.Length(); ++i) {
5049 const LineDecoration& dec = textDecs.mOverlines[i];
5050 uint8_t decorationStyle = dec.mStyle;
5051 // If the style is solid, let's include decoration line rect of solid
5052 // style since changing the style from none to solid/dotted/dashed
5053 // doesn't cause reflow.
5054 if (decorationStyle == NS_STYLE_TEXT_DECORATION_STYLE_NONE) {
5055 decorationStyle = NS_STYLE_TEXT_DECORATION_STYLE_SOLID;
5058 float inflation =
5059 GetInflationForTextDecorations(dec.mFrame, inflationMinFontSize);
5060 const gfxFont::Metrics metrics =
5061 GetFirstFontMetrics(GetFontGroupForFrame(dec.mFrame, inflation),
5062 useVerticalMetrics);
5064 const nsRect decorationRect =
5065 nsCSSRendering::GetTextDecorationRect(aPresContext,
5066 gfxSize(gfxWidth, metrics.underlineSize),
5067 ascent, metrics.maxAscent,
5068 NS_STYLE_TEXT_DECORATION_LINE_OVERLINE, decorationStyle,
5069 verticalRun) +
5070 nsPoint(0, -dec.mBaselineOffset);
5072 if (verticalRun) {
5073 topOrLeft = std::min(decorationRect.x, topOrLeft);
5074 bottomOrRight = std::max(decorationRect.XMost(), bottomOrRight);
5075 } else {
5076 topOrLeft = std::min(decorationRect.y, topOrLeft);
5077 bottomOrRight = std::max(decorationRect.YMost(), bottomOrRight);
5080 for (uint32_t i = 0; i < textDecs.mStrikes.Length(); ++i) {
5081 const LineDecoration& dec = textDecs.mStrikes[i];
5082 uint8_t decorationStyle = dec.mStyle;
5083 // If the style is solid, let's include decoration line rect of solid
5084 // style since changing the style from none to solid/dotted/dashed
5085 // doesn't cause reflow.
5086 if (decorationStyle == NS_STYLE_TEXT_DECORATION_STYLE_NONE) {
5087 decorationStyle = NS_STYLE_TEXT_DECORATION_STYLE_SOLID;
5090 float inflation =
5091 GetInflationForTextDecorations(dec.mFrame, inflationMinFontSize);
5092 const gfxFont::Metrics metrics =
5093 GetFirstFontMetrics(GetFontGroupForFrame(dec.mFrame, inflation),
5094 useVerticalMetrics);
5096 const nsRect decorationRect =
5097 nsCSSRendering::GetTextDecorationRect(aPresContext,
5098 gfxSize(gfxWidth, metrics.strikeoutSize),
5099 ascent, metrics.strikeoutOffset,
5100 NS_STYLE_TEXT_DECORATION_LINE_LINE_THROUGH, decorationStyle,
5101 verticalRun) +
5102 nsPoint(0, -dec.mBaselineOffset);
5104 if (verticalRun) {
5105 topOrLeft = std::min(decorationRect.x, topOrLeft);
5106 bottomOrRight = std::max(decorationRect.XMost(), bottomOrRight);
5107 } else {
5108 topOrLeft = std::min(decorationRect.y, topOrLeft);
5109 bottomOrRight = std::max(decorationRect.YMost(), bottomOrRight);
5113 aVisualOverflowRect->UnionRect(
5114 *aVisualOverflowRect,
5115 verticalRun ? nsRect(topOrLeft, 0, bottomOrRight - topOrLeft, measure)
5116 : nsRect(0, topOrLeft, measure, bottomOrRight - topOrLeft));
5119 // When this frame is not selected, the text-decoration area must be in
5120 // frame bounds.
5121 if (!IsSelected() ||
5122 !CombineSelectionUnderlineRect(aPresContext, *aVisualOverflowRect))
5123 return;
5124 AddStateBits(TEXT_SELECTION_UNDERLINE_OVERFLOWED);
5127 static gfxFloat
5128 ComputeDescentLimitForSelectionUnderline(nsPresContext* aPresContext,
5129 nsTextFrame* aFrame,
5130 const gfxFont::Metrics& aFontMetrics)
5132 gfxFloat app = aPresContext->AppUnitsPerDevPixel();
5133 nscoord lineHeightApp =
5134 nsHTMLReflowState::CalcLineHeight(aFrame->GetContent(),
5135 aFrame->StyleContext(), NS_AUTOHEIGHT,
5136 aFrame->GetFontSizeInflation());
5137 gfxFloat lineHeight = gfxFloat(lineHeightApp) / app;
5138 if (lineHeight <= aFontMetrics.maxHeight) {
5139 return aFontMetrics.maxDescent;
5141 return aFontMetrics.maxDescent + (lineHeight - aFontMetrics.maxHeight) / 2;
5145 // Make sure this stays in sync with DrawSelectionDecorations below
5146 static const SelectionType SelectionTypesWithDecorations =
5147 nsISelectionController::SELECTION_SPELLCHECK |
5148 nsISelectionController::SELECTION_IME_RAWINPUT |
5149 nsISelectionController::SELECTION_IME_SELECTEDRAWTEXT |
5150 nsISelectionController::SELECTION_IME_CONVERTEDTEXT |
5151 nsISelectionController::SELECTION_IME_SELECTEDCONVERTEDTEXT;
5153 static gfxFloat
5154 ComputeSelectionUnderlineHeight(nsPresContext* aPresContext,
5155 const gfxFont::Metrics& aFontMetrics,
5156 SelectionType aSelectionType)
5158 switch (aSelectionType) {
5159 case nsISelectionController::SELECTION_IME_RAWINPUT:
5160 case nsISelectionController::SELECTION_IME_SELECTEDRAWTEXT:
5161 case nsISelectionController::SELECTION_IME_CONVERTEDTEXT:
5162 case nsISelectionController::SELECTION_IME_SELECTEDCONVERTEDTEXT:
5163 return aFontMetrics.underlineSize;
5164 case nsISelectionController::SELECTION_SPELLCHECK: {
5165 // The thickness of the spellchecker underline shouldn't honor the font
5166 // metrics. It should be constant pixels value which is decided from the
5167 // default font size. Note that if the actual font size is smaller than
5168 // the default font size, we should use the actual font size because the
5169 // computed value from the default font size can be too thick for the
5170 // current font size.
5171 int32_t defaultFontSize =
5172 aPresContext->AppUnitsToDevPixels(nsStyleFont(aPresContext).mFont.size);
5173 gfxFloat fontSize = std::min(gfxFloat(defaultFontSize),
5174 aFontMetrics.emHeight);
5175 fontSize = std::max(fontSize, 1.0);
5176 return ceil(fontSize / 20);
5178 default:
5179 NS_WARNING("Requested underline style is not valid");
5180 return aFontMetrics.underlineSize;
5184 enum DecorationType {
5185 eNormalDecoration,
5186 eSelectionDecoration
5189 static void
5190 PaintDecorationLine(nsIFrame* aFrame,
5191 gfxContext* const aCtx,
5192 const gfxRect& aDirtyRect,
5193 nscolor aColor,
5194 const nscolor* aOverrideColor,
5195 const gfxPoint& aPt,
5196 gfxFloat aICoordInFrame,
5197 const gfxSize& aLineSize,
5198 gfxFloat aAscent,
5199 gfxFloat aOffset,
5200 uint8_t aDecoration,
5201 uint8_t aStyle,
5202 DecorationType aDecorationType,
5203 nsTextFrame::DrawPathCallbacks* aCallbacks,
5204 bool aVertical,
5205 gfxFloat aDescentLimit = -1.0)
5207 nscolor lineColor = aOverrideColor ? *aOverrideColor : aColor;
5208 if (aCallbacks) {
5209 if (aDecorationType == eNormalDecoration) {
5210 aCallbacks->NotifyBeforeDecorationLine(lineColor);
5211 } else {
5212 aCallbacks->NotifyBeforeSelectionDecorationLine(lineColor);
5214 nsCSSRendering::DecorationLineToPath(aFrame, aCtx, aDirtyRect, lineColor,
5215 aPt, aICoordInFrame, aLineSize, aAscent, aOffset, aDecoration, aStyle,
5216 aVertical, aDescentLimit);
5217 if (aDecorationType == eNormalDecoration) {
5218 aCallbacks->NotifyDecorationLinePathEmitted();
5219 } else {
5220 aCallbacks->NotifySelectionDecorationLinePathEmitted();
5222 } else {
5223 nsCSSRendering::PaintDecorationLine(aFrame, *aCtx->GetDrawTarget(),
5224 ToRect(aDirtyRect), lineColor,
5225 aPt, Float(aICoordInFrame), aLineSize, aAscent, aOffset, aDecoration, aStyle,
5226 aVertical, aDescentLimit);
5231 * This, plus SelectionTypesWithDecorations, encapsulates all knowledge about
5232 * drawing text decoration for selections.
5234 static void DrawSelectionDecorations(gfxContext* aContext,
5235 const gfxRect& aDirtyRect,
5236 SelectionType aType,
5237 nsTextFrame* aFrame,
5238 nsTextPaintStyle& aTextPaintStyle,
5239 const TextRangeStyle &aRangeStyle,
5240 const gfxPoint& aPt, gfxFloat aICoordInFrame, gfxFloat aWidth,
5241 gfxFloat aAscent, const gfxFont::Metrics& aFontMetrics,
5242 nsTextFrame::DrawPathCallbacks* aCallbacks,
5243 bool aVertical)
5245 gfxPoint pt(aPt);
5246 gfxSize size(aWidth,
5247 ComputeSelectionUnderlineHeight(aTextPaintStyle.PresContext(),
5248 aFontMetrics, aType));
5249 gfxFloat descentLimit =
5250 ComputeDescentLimitForSelectionUnderline(aTextPaintStyle.PresContext(),
5251 aFrame, aFontMetrics);
5253 float relativeSize;
5254 uint8_t style;
5255 nscolor color;
5256 int32_t index =
5257 nsTextPaintStyle::GetUnderlineStyleIndexForSelectionType(aType);
5258 bool weDefineSelectionUnderline =
5259 aTextPaintStyle.GetSelectionUnderlineForPaint(index, &color,
5260 &relativeSize, &style);
5262 switch (aType) {
5263 case nsISelectionController::SELECTION_IME_RAWINPUT:
5264 case nsISelectionController::SELECTION_IME_SELECTEDRAWTEXT:
5265 case nsISelectionController::SELECTION_IME_CONVERTEDTEXT:
5266 case nsISelectionController::SELECTION_IME_SELECTEDCONVERTEDTEXT: {
5267 // IME decoration lines should not be drawn on the both ends, i.e., we
5268 // need to cut both edges of the decoration lines. Because same style
5269 // IME selections can adjoin, but the users need to be able to know
5270 // where are the boundaries of the selections.
5272 // X: underline
5274 // IME selection #1 IME selection #2 IME selection #3
5275 // | | |
5276 // | XXXXXXXXXXXXXXXXXXX | XXXXXXXXXXXXXXXXXXXX | XXXXXXXXXXXXXXXXXXX
5277 // +---------------------+----------------------+--------------------
5278 // ^ ^ ^ ^ ^
5279 // gap gap gap
5280 pt.x += 1.0;
5281 size.width -= 2.0;
5282 if (aRangeStyle.IsDefined()) {
5283 // If IME defines the style, that should override our definition.
5284 if (aRangeStyle.IsLineStyleDefined()) {
5285 if (aRangeStyle.mLineStyle == TextRangeStyle::LINESTYLE_NONE) {
5286 return;
5288 style = aRangeStyle.mLineStyle;
5289 relativeSize = aRangeStyle.mIsBoldLine ? 2.0f : 1.0f;
5290 } else if (!weDefineSelectionUnderline) {
5291 // There is no underline style definition.
5292 return;
5294 if (aRangeStyle.IsUnderlineColorDefined()) {
5295 color = aRangeStyle.mUnderlineColor;
5296 } else if (aRangeStyle.IsForegroundColorDefined()) {
5297 color = aRangeStyle.mForegroundColor;
5298 } else {
5299 NS_ASSERTION(!aRangeStyle.IsBackgroundColorDefined(),
5300 "Only the background color is defined");
5301 color = aTextPaintStyle.GetTextColor();
5303 } else if (!weDefineSelectionUnderline) {
5304 // IME doesn't specify the selection style and we don't define selection
5305 // underline.
5306 return;
5308 break;
5310 case nsISelectionController::SELECTION_SPELLCHECK:
5311 if (!weDefineSelectionUnderline)
5312 return;
5313 break;
5314 default:
5315 NS_WARNING("Requested selection decorations when there aren't any");
5316 return;
5318 size.height *= relativeSize;
5319 PaintDecorationLine(aFrame, aContext, aDirtyRect, color, nullptr, pt,
5320 (aVertical ? (pt.y - aPt.y) : (pt.x - aPt.x)) + aICoordInFrame,
5321 size, aAscent, aFontMetrics.underlineOffset,
5322 NS_STYLE_TEXT_DECORATION_LINE_UNDERLINE, style, eSelectionDecoration,
5323 aCallbacks, aVertical, descentLimit);
5327 * This function encapsulates all knowledge of how selections affect foreground
5328 * and background colors.
5329 * @return true if the selection affects colors, false otherwise
5330 * @param aForeground the foreground color to use
5331 * @param aBackground the background color to use, or RGBA(0,0,0,0) if no
5332 * background should be painted
5334 static bool GetSelectionTextColors(SelectionType aType,
5335 nsTextPaintStyle& aTextPaintStyle,
5336 const TextRangeStyle &aRangeStyle,
5337 nscolor* aForeground, nscolor* aBackground)
5339 switch (aType) {
5340 case nsISelectionController::SELECTION_NORMAL:
5341 return aTextPaintStyle.GetSelectionColors(aForeground, aBackground);
5342 case nsISelectionController::SELECTION_FIND:
5343 aTextPaintStyle.GetHighlightColors(aForeground, aBackground);
5344 return true;
5345 case nsISelectionController::SELECTION_URLSECONDARY:
5346 aTextPaintStyle.GetURLSecondaryColor(aForeground);
5347 *aBackground = NS_RGBA(0,0,0,0);
5348 return true;
5349 case nsISelectionController::SELECTION_IME_RAWINPUT:
5350 case nsISelectionController::SELECTION_IME_SELECTEDRAWTEXT:
5351 case nsISelectionController::SELECTION_IME_CONVERTEDTEXT:
5352 case nsISelectionController::SELECTION_IME_SELECTEDCONVERTEDTEXT:
5353 if (aRangeStyle.IsDefined()) {
5354 *aForeground = aTextPaintStyle.GetTextColor();
5355 *aBackground = NS_RGBA(0,0,0,0);
5356 if (!aRangeStyle.IsForegroundColorDefined() &&
5357 !aRangeStyle.IsBackgroundColorDefined()) {
5358 return false;
5360 if (aRangeStyle.IsForegroundColorDefined()) {
5361 *aForeground = aRangeStyle.mForegroundColor;
5363 if (aRangeStyle.IsBackgroundColorDefined()) {
5364 *aBackground = aRangeStyle.mBackgroundColor;
5366 return true;
5368 aTextPaintStyle.GetIMESelectionColors(
5369 nsTextPaintStyle::GetUnderlineStyleIndexForSelectionType(aType),
5370 aForeground, aBackground);
5371 return true;
5372 default:
5373 *aForeground = aTextPaintStyle.GetTextColor();
5374 *aBackground = NS_RGBA(0,0,0,0);
5375 return false;
5380 * This sets *aShadow to the appropriate shadow, if any, for the given
5381 * type of selection. Returns true if *aShadow was set.
5382 * If text-shadow was not specified, *aShadow is left untouched
5383 * (NOT reset to null), and the function returns false.
5385 static bool GetSelectionTextShadow(nsIFrame* aFrame,
5386 SelectionType aType,
5387 nsTextPaintStyle& aTextPaintStyle,
5388 nsCSSShadowArray** aShadow)
5390 switch (aType) {
5391 case nsISelectionController::SELECTION_NORMAL:
5392 return aTextPaintStyle.GetSelectionShadow(aShadow);
5393 default:
5394 return false;
5399 * This class lets us iterate over chunks of text in a uniform selection state,
5400 * observing cluster boundaries, in content order, maintaining the current
5401 * x-offset as we go, and telling whether the text chunk has a hyphen after
5402 * it or not. The caller is responsible for actually computing the advance
5403 * width of each chunk.
5405 class SelectionIterator {
5406 public:
5408 * aStart and aLength are in the original string. aSelectionDetails is
5409 * according to the original string.
5410 * @param aXOffset the offset from the origin of the frame to the start
5411 * of the text (the left baseline origin for LTR, the right baseline origin
5412 * for RTL)
5414 SelectionIterator(SelectionDetails** aSelectionDetails,
5415 int32_t aStart, int32_t aLength,
5416 PropertyProvider& aProvider, gfxTextRun* aTextRun,
5417 gfxFloat aXOffset);
5420 * Returns the next segment of uniformly selected (or not) text.
5421 * @param aXOffset the offset from the origin of the frame to the start
5422 * of the text (the left baseline origin for LTR, the right baseline origin
5423 * for RTL)
5424 * @param aOffset the transformed string offset of the text for this segment
5425 * @param aLength the transformed string length of the text for this segment
5426 * @param aHyphenWidth if a hyphen is to be rendered after the text, the
5427 * width of the hyphen, otherwise zero
5428 * @param aType the selection type for this segment
5429 * @param aStyle the selection style for this segment
5430 * @return false if there are no more segments
5432 bool GetNextSegment(gfxFloat* aXOffset, uint32_t* aOffset, uint32_t* aLength,
5433 gfxFloat* aHyphenWidth, SelectionType* aType,
5434 TextRangeStyle* aStyle);
5435 void UpdateWithAdvance(gfxFloat aAdvance) {
5436 mXOffset += aAdvance*mTextRun->GetDirection();
5439 private:
5440 SelectionDetails** mSelectionDetails;
5441 PropertyProvider& mProvider;
5442 gfxTextRun* mTextRun;
5443 gfxSkipCharsIterator mIterator;
5444 int32_t mOriginalStart;
5445 int32_t mOriginalEnd;
5446 gfxFloat mXOffset;
5449 SelectionIterator::SelectionIterator(SelectionDetails** aSelectionDetails,
5450 int32_t aStart, int32_t aLength, PropertyProvider& aProvider,
5451 gfxTextRun* aTextRun, gfxFloat aXOffset)
5452 : mSelectionDetails(aSelectionDetails), mProvider(aProvider),
5453 mTextRun(aTextRun), mIterator(aProvider.GetStart()),
5454 mOriginalStart(aStart), mOriginalEnd(aStart + aLength),
5455 mXOffset(aXOffset)
5457 mIterator.SetOriginalOffset(aStart);
5460 bool SelectionIterator::GetNextSegment(gfxFloat* aXOffset,
5461 uint32_t* aOffset, uint32_t* aLength, gfxFloat* aHyphenWidth,
5462 SelectionType* aType, TextRangeStyle* aStyle)
5464 if (mIterator.GetOriginalOffset() >= mOriginalEnd)
5465 return false;
5467 // save offset into transformed string now
5468 uint32_t runOffset = mIterator.GetSkippedOffset();
5470 int32_t index = mIterator.GetOriginalOffset() - mOriginalStart;
5471 SelectionDetails* sdptr = mSelectionDetails[index];
5472 SelectionType type =
5473 sdptr ? sdptr->mType : nsISelectionController::SELECTION_NONE;
5474 TextRangeStyle style;
5475 if (sdptr) {
5476 style = sdptr->mTextRangeStyle;
5478 for (++index; mOriginalStart + index < mOriginalEnd; ++index) {
5479 if (sdptr != mSelectionDetails[index])
5480 break;
5482 mIterator.SetOriginalOffset(index + mOriginalStart);
5484 // Advance to the next cluster boundary
5485 while (mIterator.GetOriginalOffset() < mOriginalEnd &&
5486 !mIterator.IsOriginalCharSkipped() &&
5487 !mTextRun->IsClusterStart(mIterator.GetSkippedOffset())) {
5488 mIterator.AdvanceOriginal(1);
5491 bool haveHyphenBreak =
5492 (mProvider.GetFrame()->GetStateBits() & TEXT_HYPHEN_BREAK) != 0;
5493 *aOffset = runOffset;
5494 *aLength = mIterator.GetSkippedOffset() - runOffset;
5495 *aXOffset = mXOffset;
5496 *aHyphenWidth = 0;
5497 if (mIterator.GetOriginalOffset() == mOriginalEnd && haveHyphenBreak) {
5498 *aHyphenWidth = mProvider.GetHyphenWidth();
5500 *aType = type;
5501 *aStyle = style;
5502 return true;
5505 static void
5506 AddHyphenToMetrics(nsTextFrame* aTextFrame, gfxTextRun* aBaseTextRun,
5507 gfxTextRun::Metrics* aMetrics,
5508 gfxFont::BoundingBoxType aBoundingBoxType,
5509 gfxContext* aContext)
5511 // Fix up metrics to include hyphen
5512 nsAutoPtr<gfxTextRun> hyphenTextRun(
5513 GetHyphenTextRun(aBaseTextRun, aContext, aTextFrame));
5514 if (!hyphenTextRun.get())
5515 return;
5517 gfxTextRun::Metrics hyphenMetrics =
5518 hyphenTextRun->MeasureText(0, hyphenTextRun->GetLength(),
5519 aBoundingBoxType, aContext, nullptr);
5520 aMetrics->CombineWith(hyphenMetrics, aBaseTextRun->IsRightToLeft());
5523 void
5524 nsTextFrame::PaintOneShadow(uint32_t aOffset, uint32_t aLength,
5525 nsCSSShadowItem* aShadowDetails,
5526 PropertyProvider* aProvider, const nsRect& aDirtyRect,
5527 const gfxPoint& aFramePt, const gfxPoint& aTextBaselinePt,
5528 gfxContext* aCtx, const nscolor& aForegroundColor,
5529 const nsCharClipDisplayItem::ClipEdges& aClipEdges,
5530 nscoord aLeftSideOffset, gfxRect& aBoundingBox)
5532 PROFILER_LABEL("nsTextFrame", "PaintOneShadow",
5533 js::ProfileEntry::Category::GRAPHICS);
5535 gfxPoint shadowOffset(aShadowDetails->mXOffset, aShadowDetails->mYOffset);
5536 nscoord blurRadius = std::max(aShadowDetails->mRadius, 0);
5538 // This rect is the box which is equivalent to where the shadow will be painted.
5539 // The origin of aBoundingBox is the text baseline left, so we must translate it by
5540 // that much in order to make the origin the top-left corner of the text bounding box.
5541 gfxRect shadowGfxRect = aBoundingBox +
5542 gfxPoint(aFramePt.x + aLeftSideOffset, aTextBaselinePt.y) + shadowOffset;
5543 nsRect shadowRect(NSToCoordRound(shadowGfxRect.X()),
5544 NSToCoordRound(shadowGfxRect.Y()),
5545 NSToCoordRound(shadowGfxRect.Width()),
5546 NSToCoordRound(shadowGfxRect.Height()));
5548 nsContextBoxBlur contextBoxBlur;
5549 gfxContext* shadowContext = contextBoxBlur.Init(shadowRect, 0, blurRadius,
5550 PresContext()->AppUnitsPerDevPixel(),
5551 aCtx, aDirtyRect, nullptr);
5552 if (!shadowContext)
5553 return;
5555 nscolor shadowColor;
5556 const nscolor* decorationOverrideColor;
5557 if (aShadowDetails->mHasColor) {
5558 shadowColor = aShadowDetails->mColor;
5559 decorationOverrideColor = &shadowColor;
5560 } else {
5561 shadowColor = aForegroundColor;
5562 decorationOverrideColor = nullptr;
5565 aCtx->Save();
5566 aCtx->NewPath();
5567 aCtx->SetColor(gfxRGBA(shadowColor));
5569 // Draw the text onto our alpha-only surface to capture the alpha values.
5570 // Remember that the box blur context has a device offset on it, so we don't need to
5571 // translate any coordinates to fit on the surface.
5572 gfxFloat advanceWidth;
5573 gfxRect dirtyRect(aDirtyRect.x, aDirtyRect.y,
5574 aDirtyRect.width, aDirtyRect.height);
5575 DrawText(shadowContext, dirtyRect, aFramePt + shadowOffset,
5576 aTextBaselinePt + shadowOffset, aOffset, aLength, *aProvider,
5577 nsTextPaintStyle(this),
5578 aCtx == shadowContext ? shadowColor : NS_RGB(0, 0, 0), aClipEdges,
5579 advanceWidth, (GetStateBits() & TEXT_HYPHEN_BREAK) != 0,
5580 decorationOverrideColor);
5582 contextBoxBlur.DoPaint();
5583 aCtx->Restore();
5586 // Paints selection backgrounds and text in the correct colors. Also computes
5587 // aAllTypes, the union of all selection types that are applying to this text.
5588 bool
5589 nsTextFrame::PaintTextWithSelectionColors(gfxContext* aCtx,
5590 const gfxPoint& aFramePt, const gfxPoint& aTextBaselinePt,
5591 const gfxRect& aDirtyRect,
5592 PropertyProvider& aProvider,
5593 uint32_t aContentOffset, uint32_t aContentLength,
5594 nsTextPaintStyle& aTextPaintStyle, SelectionDetails* aDetails,
5595 SelectionType* aAllTypes,
5596 const nsCharClipDisplayItem::ClipEdges& aClipEdges,
5597 nsTextFrame::DrawPathCallbacks* aCallbacks)
5599 // Figure out which selections control the colors to use for each character.
5600 AutoFallibleTArray<SelectionDetails*,BIG_TEXT_NODE_SIZE> prevailingSelectionsBuffer;
5601 SelectionDetails** prevailingSelections =
5602 prevailingSelectionsBuffer.AppendElements(aContentLength);
5603 if (!prevailingSelections) {
5604 return false;
5607 SelectionType allTypes = 0;
5608 for (uint32_t i = 0; i < aContentLength; ++i) {
5609 prevailingSelections[i] = nullptr;
5612 SelectionDetails *sdptr = aDetails;
5613 bool anyBackgrounds = false;
5614 while (sdptr) {
5615 int32_t start = std::max(0, sdptr->mStart - int32_t(aContentOffset));
5616 int32_t end = std::min(int32_t(aContentLength),
5617 sdptr->mEnd - int32_t(aContentOffset));
5618 SelectionType type = sdptr->mType;
5619 if (start < end) {
5620 allTypes |= type;
5621 // Ignore selections that don't set colors
5622 nscolor foreground, background;
5623 if (GetSelectionTextColors(type, aTextPaintStyle, sdptr->mTextRangeStyle,
5624 &foreground, &background)) {
5625 if (NS_GET_A(background) > 0) {
5626 anyBackgrounds = true;
5628 for (int32_t i = start; i < end; ++i) {
5629 // Favour normal selection over IME selections
5630 if (!prevailingSelections[i] ||
5631 type < prevailingSelections[i]->mType) {
5632 prevailingSelections[i] = sdptr;
5637 sdptr = sdptr->mNext;
5639 *aAllTypes = allTypes;
5641 if (!allTypes) {
5642 // Nothing is selected in the given text range. XXX can this still occur?
5643 return false;
5646 bool vertical = mTextRun->IsVertical();
5647 const gfxFloat startIOffset = vertical ?
5648 aTextBaselinePt.y - aFramePt.y : aTextBaselinePt.x - aFramePt.x;
5649 gfxFloat iOffset, hyphenWidth;
5650 uint32_t offset, length; // in transformed string
5651 SelectionType type;
5652 TextRangeStyle rangeStyle;
5653 // Draw background colors
5654 if (anyBackgrounds) {
5655 SelectionIterator iterator(prevailingSelections, aContentOffset, aContentLength,
5656 aProvider, mTextRun, startIOffset);
5657 while (iterator.GetNextSegment(&iOffset, &offset, &length, &hyphenWidth,
5658 &type, &rangeStyle)) {
5659 nscolor foreground, background;
5660 GetSelectionTextColors(type, aTextPaintStyle, rangeStyle,
5661 &foreground, &background);
5662 // Draw background color
5663 gfxFloat advance = hyphenWidth +
5664 mTextRun->GetAdvanceWidth(offset, length, &aProvider);
5665 if (NS_GET_A(background) > 0) {
5666 gfxRect bgRect;
5667 gfxFloat offs = iOffset - (mTextRun->IsRightToLeft() ? advance : 0);
5668 if (vertical) {
5669 bgRect = gfxRect(aFramePt.x, aFramePt.y + offs,
5670 GetSize().width, advance);
5671 } else {
5672 bgRect = gfxRect(aFramePt.x + offs, aFramePt.y,
5673 advance, GetSize().height);
5675 PaintSelectionBackground(aCtx, aTextPaintStyle.PresContext(),
5676 background, aDirtyRect,
5677 bgRect, aCallbacks);
5679 iterator.UpdateWithAdvance(advance);
5683 // Draw text
5684 const nsStyleText* textStyle = StyleText();
5685 nsRect dirtyRect(aDirtyRect.x, aDirtyRect.y,
5686 aDirtyRect.width, aDirtyRect.height);
5687 SelectionIterator iterator(prevailingSelections, aContentOffset, aContentLength,
5688 aProvider, mTextRun, startIOffset);
5689 while (iterator.GetNextSegment(&iOffset, &offset, &length, &hyphenWidth,
5690 &type, &rangeStyle)) {
5691 nscolor foreground, background;
5692 GetSelectionTextColors(type, aTextPaintStyle, rangeStyle,
5693 &foreground, &background);
5694 gfxPoint textBaselinePt = vertical ?
5695 gfxPoint(aTextBaselinePt.x, aFramePt.y + iOffset) :
5696 gfxPoint(aFramePt.x + iOffset, aTextBaselinePt.y);
5698 // Determine what shadow, if any, to draw - either from textStyle
5699 // or from the ::-moz-selection pseudo-class if specified there
5700 nsCSSShadowArray* shadow = textStyle->GetTextShadow();
5701 GetSelectionTextShadow(this, type, aTextPaintStyle, &shadow);
5703 // Draw shadows, if any
5704 if (shadow) {
5705 gfxTextRun::Metrics shadowMetrics =
5706 mTextRun->MeasureText(offset, length, gfxFont::LOOSE_INK_EXTENTS,
5707 nullptr, &aProvider);
5708 if (GetStateBits() & TEXT_HYPHEN_BREAK) {
5709 AddHyphenToMetrics(this, mTextRun, &shadowMetrics,
5710 gfxFont::LOOSE_INK_EXTENTS, aCtx);
5712 for (uint32_t i = shadow->Length(); i > 0; --i) {
5713 PaintOneShadow(offset, length,
5714 shadow->ShadowAt(i - 1), &aProvider,
5715 dirtyRect, aFramePt, textBaselinePt, aCtx,
5716 foreground, aClipEdges,
5717 iOffset - (mTextRun->IsRightToLeft() ?
5718 shadowMetrics.mBoundingBox.width : 0),
5719 shadowMetrics.mBoundingBox);
5723 // Draw text segment
5724 gfxFloat advance;
5726 DrawText(aCtx, aDirtyRect, aFramePt, textBaselinePt,
5727 offset, length, aProvider, aTextPaintStyle, foreground, aClipEdges,
5728 advance, hyphenWidth > 0, nullptr, nullptr, aCallbacks);
5729 if (hyphenWidth) {
5730 advance += hyphenWidth;
5732 iterator.UpdateWithAdvance(advance);
5734 return true;
5737 void
5738 nsTextFrame::PaintTextSelectionDecorations(gfxContext* aCtx,
5739 const gfxPoint& aFramePt,
5740 const gfxPoint& aTextBaselinePt, const gfxRect& aDirtyRect,
5741 PropertyProvider& aProvider,
5742 uint32_t aContentOffset, uint32_t aContentLength,
5743 nsTextPaintStyle& aTextPaintStyle, SelectionDetails* aDetails,
5744 SelectionType aSelectionType,
5745 nsTextFrame::DrawPathCallbacks* aCallbacks)
5747 // Hide text decorations if we're currently hiding @font-face fallback text
5748 if (aProvider.GetFontGroup()->ShouldSkipDrawing())
5749 return;
5751 // Figure out which characters will be decorated for this selection.
5752 AutoFallibleTArray<SelectionDetails*, BIG_TEXT_NODE_SIZE> selectedCharsBuffer;
5753 SelectionDetails** selectedChars =
5754 selectedCharsBuffer.AppendElements(aContentLength);
5755 if (!selectedChars) {
5756 return;
5758 for (uint32_t i = 0; i < aContentLength; ++i) {
5759 selectedChars[i] = nullptr;
5762 SelectionDetails *sdptr = aDetails;
5763 while (sdptr) {
5764 if (sdptr->mType == aSelectionType) {
5765 int32_t start = std::max(0, sdptr->mStart - int32_t(aContentOffset));
5766 int32_t end = std::min(int32_t(aContentLength),
5767 sdptr->mEnd - int32_t(aContentOffset));
5768 for (int32_t i = start; i < end; ++i) {
5769 selectedChars[i] = sdptr;
5772 sdptr = sdptr->mNext;
5775 gfxFont* firstFont = aProvider.GetFontGroup()->GetFirstValidFont();
5776 bool verticalRun = mTextRun->IsVertical();
5777 bool useVerticalMetrics = verticalRun && mTextRun->UseCenterBaseline();
5778 gfxFont::Metrics
5779 decorationMetrics(firstFont->GetMetrics(useVerticalMetrics ?
5780 gfxFont::eVertical : gfxFont::eHorizontal));
5781 if (!useVerticalMetrics) {
5782 // The potential adjustment from using gfxFontGroup::GetUnderlineOffset
5783 // is only valid for horizontal font metrics.
5784 decorationMetrics.underlineOffset =
5785 aProvider.GetFontGroup()->GetUnderlineOffset();
5788 gfxFloat startIOffset =
5789 verticalRun ? aTextBaselinePt.y - aFramePt.y : aTextBaselinePt.x - aFramePt.x;
5790 SelectionIterator iterator(selectedChars, aContentOffset, aContentLength,
5791 aProvider, mTextRun, startIOffset);
5792 gfxFloat iOffset, hyphenWidth;
5793 uint32_t offset, length;
5794 int32_t app = aTextPaintStyle.PresContext()->AppUnitsPerDevPixel();
5795 // XXX aTextBaselinePt is in AppUnits, shouldn't it be nsFloatPoint?
5796 gfxPoint pt;
5797 if (verticalRun) {
5798 pt.x = (aTextBaselinePt.x - mAscent) / app;
5799 } else {
5800 pt.y = (aTextBaselinePt.y - mAscent) / app;
5802 gfxRect dirtyRect(aDirtyRect.x / app, aDirtyRect.y / app,
5803 aDirtyRect.width / app, aDirtyRect.height / app);
5804 SelectionType type;
5805 TextRangeStyle selectedStyle;
5806 while (iterator.GetNextSegment(&iOffset, &offset, &length, &hyphenWidth,
5807 &type, &selectedStyle)) {
5808 gfxFloat advance = hyphenWidth +
5809 mTextRun->GetAdvanceWidth(offset, length, &aProvider);
5810 if (type == aSelectionType) {
5811 if (verticalRun) {
5812 pt.y = (aFramePt.y + iOffset -
5813 (mTextRun->IsRightToLeft() ? advance : 0)) / app;
5814 } else {
5815 pt.x = (aFramePt.x + iOffset -
5816 (mTextRun->IsRightToLeft() ? advance : 0)) / app;
5818 gfxFloat width = Abs(advance) / app;
5819 gfxFloat xInFrame = pt.x - (aFramePt.x / app);
5820 DrawSelectionDecorations(aCtx, dirtyRect, aSelectionType, this,
5821 aTextPaintStyle, selectedStyle, pt, xInFrame,
5822 width, mAscent / app, decorationMetrics,
5823 aCallbacks, verticalRun);
5825 iterator.UpdateWithAdvance(advance);
5829 bool
5830 nsTextFrame::PaintTextWithSelection(gfxContext* aCtx,
5831 const gfxPoint& aFramePt,
5832 const gfxPoint& aTextBaselinePt, const gfxRect& aDirtyRect,
5833 PropertyProvider& aProvider,
5834 uint32_t aContentOffset, uint32_t aContentLength,
5835 nsTextPaintStyle& aTextPaintStyle,
5836 const nsCharClipDisplayItem::ClipEdges& aClipEdges,
5837 gfxTextContextPaint* aContextPaint,
5838 nsTextFrame::DrawPathCallbacks* aCallbacks)
5840 NS_ASSERTION(GetContent()->IsSelectionDescendant(), "wrong paint path");
5842 SelectionDetails* details = GetSelectionDetails();
5843 if (!details) {
5844 return false;
5847 SelectionType allTypes;
5848 if (!PaintTextWithSelectionColors(aCtx, aFramePt, aTextBaselinePt, aDirtyRect,
5849 aProvider, aContentOffset, aContentLength,
5850 aTextPaintStyle, details, &allTypes,
5851 aClipEdges, aCallbacks)) {
5852 DestroySelectionDetails(details);
5853 return false;
5855 // Iterate through just the selection types that paint decorations and
5856 // paint decorations for any that actually occur in this frame. Paint
5857 // higher-numbered selection types below lower-numered ones on the
5858 // general principal that lower-numbered selections are higher priority.
5859 allTypes &= SelectionTypesWithDecorations;
5860 for (int32_t i = nsISelectionController::NUM_SELECTIONTYPES - 1;
5861 i >= 1; --i) {
5862 SelectionType type = 1 << (i - 1);
5863 if (allTypes & type) {
5864 // There is some selection of this type. Try to paint its decorations
5865 // (there might not be any for this type but that's OK,
5866 // PaintTextSelectionDecorations will exit early).
5867 PaintTextSelectionDecorations(aCtx, aFramePt, aTextBaselinePt, aDirtyRect,
5868 aProvider, aContentOffset, aContentLength,
5869 aTextPaintStyle, details, type,
5870 aCallbacks);
5874 DestroySelectionDetails(details);
5875 return true;
5878 nscolor
5879 nsTextFrame::GetCaretColorAt(int32_t aOffset)
5881 NS_PRECONDITION(aOffset >= 0, "aOffset must be positive");
5883 nscolor result = nsFrame::GetCaretColorAt(aOffset);
5884 gfxSkipCharsIterator iter = EnsureTextRun(nsTextFrame::eInflated);
5885 PropertyProvider provider(this, iter, nsTextFrame::eInflated);
5886 int32_t contentOffset = provider.GetStart().GetOriginalOffset();
5887 int32_t contentLength = provider.GetOriginalLength();
5888 NS_PRECONDITION(aOffset >= contentOffset &&
5889 aOffset <= contentOffset + contentLength,
5890 "aOffset must be in the frame's range");
5891 int32_t offsetInFrame = aOffset - contentOffset;
5892 if (offsetInFrame < 0 || offsetInFrame >= contentLength) {
5893 return result;
5896 bool isSolidTextColor = true;
5897 if (IsSVGText()) {
5898 const nsStyleSVG* style = StyleSVG();
5899 if (style->mFill.mType != eStyleSVGPaintType_None &&
5900 style->mFill.mType != eStyleSVGPaintType_Color) {
5901 isSolidTextColor = false;
5905 nsTextPaintStyle textPaintStyle(this);
5906 textPaintStyle.SetResolveColors(isSolidTextColor);
5907 SelectionDetails* details = GetSelectionDetails();
5908 SelectionDetails* sdptr = details;
5909 SelectionType type = 0;
5910 while (sdptr) {
5911 int32_t start = std::max(0, sdptr->mStart - contentOffset);
5912 int32_t end = std::min(contentLength, sdptr->mEnd - contentOffset);
5913 if (start <= offsetInFrame && offsetInFrame < end &&
5914 (type == 0 || sdptr->mType < type)) {
5915 nscolor foreground, background;
5916 if (GetSelectionTextColors(sdptr->mType, textPaintStyle,
5917 sdptr->mTextRangeStyle,
5918 &foreground, &background)) {
5919 if (!isSolidTextColor &&
5920 NS_IS_SELECTION_SPECIAL_COLOR(foreground)) {
5921 result = NS_RGBA(0, 0, 0, 255);
5922 } else {
5923 result = foreground;
5925 type = sdptr->mType;
5928 sdptr = sdptr->mNext;
5931 DestroySelectionDetails(details);
5932 return result;
5935 static uint32_t
5936 ComputeTransformedLength(PropertyProvider& aProvider)
5938 gfxSkipCharsIterator iter(aProvider.GetStart());
5939 uint32_t start = iter.GetSkippedOffset();
5940 iter.AdvanceOriginal(aProvider.GetOriginalLength());
5941 return iter.GetSkippedOffset() - start;
5944 bool
5945 nsTextFrame::MeasureCharClippedText(nscoord aLeftEdge, nscoord aRightEdge,
5946 nscoord* aSnappedLeftEdge,
5947 nscoord* aSnappedRightEdge)
5949 // We need a *reference* rendering context (not one that might have a
5950 // transform), so we don't have a rendering context argument.
5951 // XXX get the block and line passed to us somehow! This is slow!
5952 gfxSkipCharsIterator iter = EnsureTextRun(nsTextFrame::eInflated);
5953 if (!mTextRun)
5954 return false;
5956 PropertyProvider provider(this, iter, nsTextFrame::eInflated);
5957 // Trim trailing whitespace
5958 provider.InitializeForDisplay(true);
5960 uint32_t startOffset = provider.GetStart().GetSkippedOffset();
5961 uint32_t maxLength = ComputeTransformedLength(provider);
5962 return MeasureCharClippedText(provider, aLeftEdge, aRightEdge,
5963 &startOffset, &maxLength,
5964 aSnappedLeftEdge, aSnappedRightEdge);
5967 static uint32_t GetClusterLength(gfxTextRun* aTextRun,
5968 uint32_t aStartOffset,
5969 uint32_t aMaxLength,
5970 bool aIsRTL)
5972 uint32_t clusterLength = aIsRTL ? 0 : 1;
5973 while (clusterLength < aMaxLength) {
5974 if (aTextRun->IsClusterStart(aStartOffset + clusterLength)) {
5975 if (aIsRTL) {
5976 ++clusterLength;
5978 break;
5980 ++clusterLength;
5982 return clusterLength;
5985 bool
5986 nsTextFrame::MeasureCharClippedText(PropertyProvider& aProvider,
5987 nscoord aLeftEdge, nscoord aRightEdge,
5988 uint32_t* aStartOffset,
5989 uint32_t* aMaxLength,
5990 nscoord* aSnappedLeftEdge,
5991 nscoord* aSnappedRightEdge)
5993 *aSnappedLeftEdge = 0;
5994 *aSnappedRightEdge = 0;
5995 if (aLeftEdge <= 0 && aRightEdge <= 0) {
5996 return true;
5999 uint32_t offset = *aStartOffset;
6000 uint32_t maxLength = *aMaxLength;
6001 const nscoord frameWidth = GetSize().width;
6002 const bool rtl = mTextRun->IsRightToLeft();
6003 gfxFloat advanceWidth = 0;
6004 const nscoord startEdge = rtl ? aRightEdge : aLeftEdge;
6005 if (startEdge > 0) {
6006 const gfxFloat maxAdvance = gfxFloat(startEdge);
6007 while (maxLength > 0) {
6008 uint32_t clusterLength =
6009 GetClusterLength(mTextRun, offset, maxLength, rtl);
6010 advanceWidth +=
6011 mTextRun->GetAdvanceWidth(offset, clusterLength, &aProvider);
6012 maxLength -= clusterLength;
6013 offset += clusterLength;
6014 if (advanceWidth >= maxAdvance) {
6015 break;
6018 nscoord* snappedStartEdge = rtl ? aSnappedRightEdge : aSnappedLeftEdge;
6019 *snappedStartEdge = NSToCoordFloor(advanceWidth);
6020 *aStartOffset = offset;
6023 const nscoord endEdge = rtl ? aLeftEdge : aRightEdge;
6024 if (endEdge > 0) {
6025 const gfxFloat maxAdvance = gfxFloat(frameWidth - endEdge);
6026 while (maxLength > 0) {
6027 uint32_t clusterLength =
6028 GetClusterLength(mTextRun, offset, maxLength, rtl);
6029 gfxFloat nextAdvance = advanceWidth +
6030 mTextRun->GetAdvanceWidth(offset, clusterLength, &aProvider);
6031 if (nextAdvance > maxAdvance) {
6032 break;
6034 // This cluster fits, include it.
6035 advanceWidth = nextAdvance;
6036 maxLength -= clusterLength;
6037 offset += clusterLength;
6039 maxLength = offset - *aStartOffset;
6040 nscoord* snappedEndEdge = rtl ? aSnappedLeftEdge : aSnappedRightEdge;
6041 *snappedEndEdge = NSToCoordFloor(gfxFloat(frameWidth) - advanceWidth);
6043 *aMaxLength = maxLength;
6044 return maxLength != 0;
6047 void
6048 nsTextFrame::PaintText(nsRenderingContext* aRenderingContext, nsPoint aPt,
6049 const nsRect& aDirtyRect,
6050 const nsCharClipDisplayItem& aItem,
6051 gfxTextContextPaint* aContextPaint,
6052 nsTextFrame::DrawPathCallbacks* aCallbacks)
6054 // Don't pass in aRenderingContext here, because we need a *reference*
6055 // context and aRenderingContext might have some transform in it
6056 // XXX get the block and line passed to us somehow! This is slow!
6057 gfxSkipCharsIterator iter = EnsureTextRun(nsTextFrame::eInflated);
6058 if (!mTextRun)
6059 return;
6061 PropertyProvider provider(this, iter, nsTextFrame::eInflated);
6062 // Trim trailing whitespace
6063 provider.InitializeForDisplay(true);
6065 gfxContext* ctx = aRenderingContext->ThebesContext();
6066 const bool rtl = mTextRun->IsRightToLeft();
6067 const bool verticalRun = mTextRun->IsVertical();
6068 WritingMode wm = GetWritingMode();
6069 const nscoord frameWidth = GetSize().width;
6070 gfxPoint framePt(aPt.x, aPt.y);
6071 gfxPoint textBaselinePt;
6072 if (verticalRun) {
6073 textBaselinePt = // XXX sideways-left will need different handling here
6074 gfxPoint(aPt.x + (wm.IsVerticalLR() ? mAscent : frameWidth - mAscent),
6075 rtl ? aPt.y + GetSize().height : aPt.y);
6076 } else {
6077 textBaselinePt = gfxPoint(rtl ? gfxFloat(aPt.x + frameWidth) : framePt.x,
6078 nsLayoutUtils::GetSnappedBaselineY(this, ctx, aPt.y, mAscent));
6080 uint32_t startOffset = provider.GetStart().GetSkippedOffset();
6081 uint32_t maxLength = ComputeTransformedLength(provider);
6082 nscoord snappedLeftEdge, snappedRightEdge;
6083 if (!MeasureCharClippedText(provider, aItem.mLeftEdge, aItem.mRightEdge,
6084 &startOffset, &maxLength, &snappedLeftEdge, &snappedRightEdge)) {
6085 return;
6087 if (verticalRun) {
6088 textBaselinePt.y += rtl ? -snappedRightEdge : snappedLeftEdge;
6089 } else {
6090 textBaselinePt.x += rtl ? -snappedRightEdge : snappedLeftEdge;
6092 nsCharClipDisplayItem::ClipEdges clipEdges(aItem, snappedLeftEdge,
6093 snappedRightEdge);
6094 nsTextPaintStyle textPaintStyle(this);
6095 textPaintStyle.SetResolveColors(!aCallbacks);
6097 gfxRect dirtyRect(aDirtyRect.x, aDirtyRect.y,
6098 aDirtyRect.width, aDirtyRect.height);
6099 // Fork off to the (slower) paint-with-selection path if necessary.
6100 if (IsSelected()) {
6101 gfxSkipCharsIterator tmp(provider.GetStart());
6102 int32_t contentOffset = tmp.ConvertSkippedToOriginal(startOffset);
6103 int32_t contentLength =
6104 tmp.ConvertSkippedToOriginal(startOffset + maxLength) - contentOffset;
6105 if (PaintTextWithSelection(ctx, framePt, textBaselinePt, dirtyRect,
6106 provider, contentOffset, contentLength,
6107 textPaintStyle, clipEdges, aContextPaint,
6108 aCallbacks)) {
6109 return;
6113 nscolor foregroundColor = textPaintStyle.GetTextColor();
6114 if (!aCallbacks) {
6115 const nsStyleText* textStyle = StyleText();
6116 if (textStyle->HasTextShadow()) {
6117 // Text shadow happens with the last value being painted at the back,
6118 // ie. it is painted first.
6119 gfxTextRun::Metrics shadowMetrics =
6120 mTextRun->MeasureText(startOffset, maxLength, gfxFont::LOOSE_INK_EXTENTS,
6121 nullptr, &provider);
6122 for (uint32_t i = textStyle->mTextShadow->Length(); i > 0; --i) {
6123 PaintOneShadow(startOffset, maxLength,
6124 textStyle->mTextShadow->ShadowAt(i - 1), &provider,
6125 aDirtyRect, framePt, textBaselinePt, ctx,
6126 foregroundColor, clipEdges,
6127 snappedLeftEdge, shadowMetrics.mBoundingBox);
6132 gfxFloat advanceWidth;
6133 DrawText(ctx, dirtyRect, framePt, textBaselinePt, startOffset, maxLength, provider,
6134 textPaintStyle, foregroundColor, clipEdges, advanceWidth,
6135 (GetStateBits() & TEXT_HYPHEN_BREAK) != 0,
6136 nullptr, aContextPaint, aCallbacks);
6139 static void
6140 DrawTextRun(gfxTextRun* aTextRun,
6141 gfxContext* const aCtx,
6142 const gfxPoint& aTextBaselinePt,
6143 uint32_t aOffset, uint32_t aLength,
6144 PropertyProvider* aProvider,
6145 nscolor aTextColor,
6146 gfxFloat* aAdvanceWidth,
6147 gfxTextContextPaint* aContextPaint,
6148 nsTextFrame::DrawPathCallbacks* aCallbacks)
6150 DrawMode drawMode = aCallbacks ? DrawMode::GLYPH_PATH :
6151 DrawMode::GLYPH_FILL;
6152 if (aCallbacks) {
6153 aCallbacks->NotifyBeforeText(aTextColor);
6154 aTextRun->Draw(aCtx, aTextBaselinePt, drawMode, aOffset, aLength,
6155 aProvider, aAdvanceWidth, aContextPaint, aCallbacks);
6156 aCallbacks->NotifyAfterText();
6157 } else {
6158 aCtx->SetColor(gfxRGBA(aTextColor));
6159 aTextRun->Draw(aCtx, aTextBaselinePt, drawMode, aOffset, aLength,
6160 aProvider, aAdvanceWidth, aContextPaint);
6164 void
6165 nsTextFrame::DrawTextRun(gfxContext* const aCtx,
6166 const gfxPoint& aTextBaselinePt,
6167 uint32_t aOffset, uint32_t aLength,
6168 PropertyProvider& aProvider,
6169 nscolor aTextColor,
6170 gfxFloat& aAdvanceWidth,
6171 bool aDrawSoftHyphen,
6172 gfxTextContextPaint* aContextPaint,
6173 nsTextFrame::DrawPathCallbacks* aCallbacks)
6175 ::DrawTextRun(mTextRun, aCtx, aTextBaselinePt, aOffset, aLength, &aProvider,
6176 aTextColor, &aAdvanceWidth, aContextPaint, aCallbacks);
6178 if (aDrawSoftHyphen) {
6179 // Don't use ctx as the context, because we need a reference context here,
6180 // ctx may be transformed.
6181 nsAutoPtr<gfxTextRun> hyphenTextRun(GetHyphenTextRun(mTextRun, nullptr, this));
6182 if (hyphenTextRun.get()) {
6183 // For right-to-left text runs, the soft-hyphen is positioned at the left
6184 // of the text, minus its own width
6185 gfxFloat hyphenBaselineX = aTextBaselinePt.x + mTextRun->GetDirection() * aAdvanceWidth -
6186 (mTextRun->IsRightToLeft() ? hyphenTextRun->GetAdvanceWidth(0, hyphenTextRun->GetLength(), nullptr) : 0);
6187 ::DrawTextRun(hyphenTextRun.get(), aCtx,
6188 gfxPoint(hyphenBaselineX, aTextBaselinePt.y),
6189 0, hyphenTextRun->GetLength(),
6190 nullptr, aTextColor, nullptr, aContextPaint, aCallbacks);
6195 void
6196 nsTextFrame::DrawTextRunAndDecorations(
6197 gfxContext* const aCtx, const gfxRect& aDirtyRect,
6198 const gfxPoint& aFramePt, const gfxPoint& aTextBaselinePt,
6199 uint32_t aOffset, uint32_t aLength,
6200 PropertyProvider& aProvider,
6201 const nsTextPaintStyle& aTextStyle,
6202 nscolor aTextColor,
6203 const nsCharClipDisplayItem::ClipEdges& aClipEdges,
6204 gfxFloat& aAdvanceWidth,
6205 bool aDrawSoftHyphen,
6206 const TextDecorations& aDecorations,
6207 const nscolor* const aDecorationOverrideColor,
6208 gfxTextContextPaint* aContextPaint,
6209 nsTextFrame::DrawPathCallbacks* aCallbacks)
6211 const gfxFloat app = aTextStyle.PresContext()->AppUnitsPerDevPixel();
6212 bool verticalRun = mTextRun->IsVertical();
6213 bool useVerticalMetrics = verticalRun && mTextRun->UseCenterBaseline();
6215 // XXX aFramePt is in AppUnits, shouldn't it be nsFloatPoint?
6216 nscoord x = NSToCoordRound(aFramePt.x);
6217 nscoord y = NSToCoordRound(aFramePt.y);
6219 // 'measure' here is textrun-relative, so for a horizontal run it's the
6220 // width, while for a vertical run it's the height of the decoration
6221 const nsSize frameSize = GetSize();
6222 nscoord measure = verticalRun ? frameSize.height : frameSize.width;
6224 // XXX todo: probably should have a vertical version of this...
6225 if (!verticalRun) {
6226 aClipEdges.Intersect(&x, &measure);
6229 // decPt is the physical point where the decoration is to be drawn,
6230 // relative to the frame; one of its coordinates will be updated below.
6231 gfxPoint decPt(x / app, y / app);
6232 gfxFloat& bCoord = verticalRun ? decPt.x : decPt.y;
6234 // decSize is a textrun-relative size, so its 'width' field is actually
6235 // the run-relative measure, and 'height' will be the line thickness
6236 gfxSize decSize(measure / app, 0);
6237 gfxFloat ascent = gfxFloat(mAscent) / app;
6239 // The starting edge of the frame in block direction
6240 gfxFloat frameBStart = verticalRun ? aFramePt.x : aFramePt.y;
6242 // In vertical-rl mode, block coordinates are measured from the right,
6243 // so we need to adjust here.
6244 const WritingMode wm = GetWritingMode();
6245 if (wm.IsVerticalRL()) {
6246 frameBStart += frameSize.width;
6247 ascent = -ascent;
6250 gfxRect dirtyRect(aDirtyRect.x / app, aDirtyRect.y / app,
6251 aDirtyRect.Width() / app, aDirtyRect.Height() / app);
6253 nscoord inflationMinFontSize =
6254 nsLayoutUtils::InflationMinFontSizeFor(this);
6256 // Underlines
6257 for (uint32_t i = aDecorations.mUnderlines.Length(); i-- > 0; ) {
6258 const LineDecoration& dec = aDecorations.mUnderlines[i];
6259 if (dec.mStyle == NS_STYLE_TEXT_DECORATION_STYLE_NONE) {
6260 continue;
6263 float inflation =
6264 GetInflationForTextDecorations(dec.mFrame, inflationMinFontSize);
6265 const gfxFont::Metrics metrics =
6266 GetFirstFontMetrics(GetFontGroupForFrame(dec.mFrame, inflation),
6267 useVerticalMetrics);
6269 decSize.height = metrics.underlineSize;
6270 bCoord = (frameBStart - dec.mBaselineOffset) / app;
6272 PaintDecorationLine(this, aCtx, dirtyRect, dec.mColor,
6273 aDecorationOverrideColor, decPt, 0.0, decSize, ascent,
6274 metrics.underlineOffset, NS_STYLE_TEXT_DECORATION_LINE_UNDERLINE,
6275 dec.mStyle, eNormalDecoration, aCallbacks, verticalRun);
6277 // Overlines
6278 for (uint32_t i = aDecorations.mOverlines.Length(); i-- > 0; ) {
6279 const LineDecoration& dec = aDecorations.mOverlines[i];
6280 if (dec.mStyle == NS_STYLE_TEXT_DECORATION_STYLE_NONE) {
6281 continue;
6284 float inflation =
6285 GetInflationForTextDecorations(dec.mFrame, inflationMinFontSize);
6286 const gfxFont::Metrics metrics =
6287 GetFirstFontMetrics(GetFontGroupForFrame(dec.mFrame, inflation),
6288 useVerticalMetrics);
6290 decSize.height = metrics.underlineSize;
6291 bCoord = (frameBStart - dec.mBaselineOffset) / app;
6293 PaintDecorationLine(this, aCtx, dirtyRect, dec.mColor,
6294 aDecorationOverrideColor, decPt, 0.0, decSize, ascent,
6295 metrics.maxAscent, NS_STYLE_TEXT_DECORATION_LINE_OVERLINE, dec.mStyle,
6296 eNormalDecoration, aCallbacks, verticalRun);
6299 // CSS 2.1 mandates that text be painted after over/underlines, and *then*
6300 // line-throughs
6301 DrawTextRun(aCtx, aTextBaselinePt, aOffset, aLength, aProvider, aTextColor,
6302 aAdvanceWidth, aDrawSoftHyphen, aContextPaint, aCallbacks);
6304 // Line-throughs
6305 for (uint32_t i = aDecorations.mStrikes.Length(); i-- > 0; ) {
6306 const LineDecoration& dec = aDecorations.mStrikes[i];
6307 if (dec.mStyle == NS_STYLE_TEXT_DECORATION_STYLE_NONE) {
6308 continue;
6311 float inflation =
6312 GetInflationForTextDecorations(dec.mFrame, inflationMinFontSize);
6313 const gfxFont::Metrics metrics =
6314 GetFirstFontMetrics(GetFontGroupForFrame(dec.mFrame, inflation),
6315 useVerticalMetrics);
6317 decSize.height = metrics.strikeoutSize;
6318 bCoord = (frameBStart - dec.mBaselineOffset) / app;
6320 PaintDecorationLine(this, aCtx, dirtyRect, dec.mColor,
6321 aDecorationOverrideColor, decPt, 0.0, decSize, ascent,
6322 metrics.strikeoutOffset, NS_STYLE_TEXT_DECORATION_LINE_LINE_THROUGH,
6323 dec.mStyle, eNormalDecoration, aCallbacks, verticalRun);
6327 void
6328 nsTextFrame::DrawText(
6329 gfxContext* const aCtx, const gfxRect& aDirtyRect,
6330 const gfxPoint& aFramePt, const gfxPoint& aTextBaselinePt,
6331 uint32_t aOffset, uint32_t aLength,
6332 PropertyProvider& aProvider,
6333 const nsTextPaintStyle& aTextStyle,
6334 nscolor aTextColor,
6335 const nsCharClipDisplayItem::ClipEdges& aClipEdges,
6336 gfxFloat& aAdvanceWidth,
6337 bool aDrawSoftHyphen,
6338 const nscolor* const aDecorationOverrideColor,
6339 gfxTextContextPaint* aContextPaint,
6340 nsTextFrame::DrawPathCallbacks* aCallbacks)
6342 TextDecorations decorations;
6343 GetTextDecorations(aTextStyle.PresContext(),
6344 aCallbacks ? eUnresolvedColors : eResolvedColors,
6345 decorations);
6347 // Hide text decorations if we're currently hiding @font-face fallback text
6348 const bool drawDecorations = !aProvider.GetFontGroup()->ShouldSkipDrawing() &&
6349 decorations.HasDecorationLines();
6350 if (drawDecorations) {
6351 DrawTextRunAndDecorations(aCtx, aDirtyRect, aFramePt, aTextBaselinePt, aOffset, aLength,
6352 aProvider, aTextStyle, aTextColor, aClipEdges, aAdvanceWidth,
6353 aDrawSoftHyphen, decorations,
6354 aDecorationOverrideColor, aContextPaint, aCallbacks);
6355 } else {
6356 DrawTextRun(aCtx, aTextBaselinePt, aOffset, aLength, aProvider,
6357 aTextColor, aAdvanceWidth, aDrawSoftHyphen, aContextPaint, aCallbacks);
6361 int16_t
6362 nsTextFrame::GetSelectionStatus(int16_t* aSelectionFlags)
6364 // get the selection controller
6365 nsCOMPtr<nsISelectionController> selectionController;
6366 nsresult rv = GetSelectionController(PresContext(),
6367 getter_AddRefs(selectionController));
6368 if (NS_FAILED(rv) || !selectionController)
6369 return nsISelectionController::SELECTION_OFF;
6371 selectionController->GetSelectionFlags(aSelectionFlags);
6373 int16_t selectionValue;
6374 selectionController->GetDisplaySelection(&selectionValue);
6376 return selectionValue;
6379 bool
6380 nsTextFrame::IsVisibleInSelection(nsISelection* aSelection)
6382 // Check the quick way first
6383 if (!GetContent()->IsSelectionDescendant())
6384 return false;
6386 SelectionDetails* details = GetSelectionDetails();
6387 bool found = false;
6389 // where are the selection points "really"
6390 SelectionDetails *sdptr = details;
6391 while (sdptr) {
6392 if (sdptr->mEnd > GetContentOffset() &&
6393 sdptr->mStart < GetContentEnd() &&
6394 sdptr->mType == nsISelectionController::SELECTION_NORMAL) {
6395 found = true;
6396 break;
6398 sdptr = sdptr->mNext;
6400 DestroySelectionDetails(details);
6402 return found;
6406 * Compute the longest prefix of text whose width is <= aWidth. Return
6407 * the length of the prefix. Also returns the width of the prefix in aFitWidth.
6409 static uint32_t
6410 CountCharsFit(gfxTextRun* aTextRun, uint32_t aStart, uint32_t aLength,
6411 gfxFloat aWidth, PropertyProvider* aProvider,
6412 gfxFloat* aFitWidth)
6414 uint32_t last = 0;
6415 gfxFloat width = 0;
6416 for (uint32_t i = 1; i <= aLength; ++i) {
6417 if (i == aLength || aTextRun->IsClusterStart(aStart + i)) {
6418 gfxFloat nextWidth = width +
6419 aTextRun->GetAdvanceWidth(aStart + last, i - last, aProvider);
6420 if (nextWidth > aWidth)
6421 break;
6422 last = i;
6423 width = nextWidth;
6426 *aFitWidth = width;
6427 return last;
6430 nsIFrame::ContentOffsets
6431 nsTextFrame::CalcContentOffsetsFromFramePoint(nsPoint aPoint)
6433 return GetCharacterOffsetAtFramePointInternal(aPoint, true);
6436 nsIFrame::ContentOffsets
6437 nsTextFrame::GetCharacterOffsetAtFramePoint(const nsPoint &aPoint)
6439 return GetCharacterOffsetAtFramePointInternal(aPoint, false);
6442 nsIFrame::ContentOffsets
6443 nsTextFrame::GetCharacterOffsetAtFramePointInternal(nsPoint aPoint,
6444 bool aForInsertionPoint)
6446 ContentOffsets offsets;
6448 gfxSkipCharsIterator iter = EnsureTextRun(nsTextFrame::eInflated);
6449 if (!mTextRun)
6450 return offsets;
6452 PropertyProvider provider(this, iter, nsTextFrame::eInflated);
6453 // Trim leading but not trailing whitespace if possible
6454 provider.InitializeForDisplay(false);
6455 gfxFloat width = mTextRun->IsVertical() ?
6456 (mTextRun->IsRightToLeft() ? mRect.height - aPoint.y : aPoint.y) :
6457 (mTextRun->IsRightToLeft() ? mRect.width - aPoint.x : aPoint.x);
6458 gfxFloat fitWidth;
6459 uint32_t skippedLength = ComputeTransformedLength(provider);
6461 uint32_t charsFit = CountCharsFit(mTextRun,
6462 provider.GetStart().GetSkippedOffset(), skippedLength, width, &provider, &fitWidth);
6464 int32_t selectedOffset;
6465 if (charsFit < skippedLength) {
6466 // charsFit characters fitted, but no more could fit. See if we're
6467 // more than halfway through the cluster.. If we are, choose the next
6468 // cluster.
6469 gfxSkipCharsIterator extraCluster(provider.GetStart());
6470 extraCluster.AdvanceSkipped(charsFit);
6471 gfxSkipCharsIterator extraClusterLastChar(extraCluster);
6472 FindClusterEnd(mTextRun,
6473 provider.GetStart().GetOriginalOffset() + provider.GetOriginalLength(),
6474 &extraClusterLastChar);
6475 PropertyProvider::Spacing spacing;
6476 gfxFloat charWidth =
6477 mTextRun->GetAdvanceWidth(extraCluster.GetSkippedOffset(),
6478 GetSkippedDistance(extraCluster, extraClusterLastChar) + 1,
6479 &provider, &spacing);
6480 charWidth -= spacing.mBefore + spacing.mAfter;
6481 selectedOffset = !aForInsertionPoint ||
6482 width <= fitWidth + spacing.mBefore + charWidth/2
6483 ? extraCluster.GetOriginalOffset()
6484 : extraClusterLastChar.GetOriginalOffset() + 1;
6485 } else {
6486 // All characters fitted, we're at (or beyond) the end of the text.
6487 // XXX This could be some pathological situation where negative spacing
6488 // caused characters to move backwards. We can't really handle that
6489 // in the current frame system because frames can't have negative
6490 // intrinsic widths.
6491 selectedOffset =
6492 provider.GetStart().GetOriginalOffset() + provider.GetOriginalLength();
6493 // If we're at the end of a preformatted line which has a terminating
6494 // linefeed, we want to reduce the offset by one to make sure that the
6495 // selection is placed before the linefeed character.
6496 if (HasSignificantTerminalNewline()) {
6497 --selectedOffset;
6501 offsets.content = GetContent();
6502 offsets.offset = offsets.secondaryOffset = selectedOffset;
6503 offsets.associate =
6504 mContentOffset == offsets.offset ? CARET_ASSOCIATE_AFTER : CARET_ASSOCIATE_BEFORE;
6505 return offsets;
6508 bool
6509 nsTextFrame::CombineSelectionUnderlineRect(nsPresContext* aPresContext,
6510 nsRect& aRect)
6512 if (aRect.IsEmpty())
6513 return false;
6515 nsRect givenRect = aRect;
6517 nsRefPtr<nsFontMetrics> fm;
6518 nsLayoutUtils::GetFontMetricsForFrame(this, getter_AddRefs(fm),
6519 GetFontSizeInflation());
6520 gfxFontGroup* fontGroup = fm->GetThebesFontGroup();
6521 gfxFont* firstFont = fontGroup->GetFirstValidFont();
6522 WritingMode wm = GetWritingMode();
6523 bool verticalRun = wm.IsVertical();
6524 bool useVerticalMetrics = verticalRun && !wm.IsSideways();
6525 const gfxFont::Metrics& metrics =
6526 firstFont->GetMetrics(useVerticalMetrics ? gfxFont::eVertical
6527 : gfxFont::eHorizontal);
6528 gfxFloat underlineOffset = fontGroup->GetUnderlineOffset();
6529 gfxFloat ascent = aPresContext->AppUnitsToGfxUnits(mAscent);
6530 gfxFloat descentLimit =
6531 ComputeDescentLimitForSelectionUnderline(aPresContext, this, metrics);
6533 SelectionDetails *details = GetSelectionDetails();
6534 for (SelectionDetails *sd = details; sd; sd = sd->mNext) {
6535 if (sd->mStart == sd->mEnd || !(sd->mType & SelectionTypesWithDecorations))
6536 continue;
6538 uint8_t style;
6539 float relativeSize;
6540 int32_t index =
6541 nsTextPaintStyle::GetUnderlineStyleIndexForSelectionType(sd->mType);
6542 if (sd->mType == nsISelectionController::SELECTION_SPELLCHECK) {
6543 if (!nsTextPaintStyle::GetSelectionUnderline(aPresContext, index, nullptr,
6544 &relativeSize, &style)) {
6545 continue;
6547 } else {
6548 // IME selections
6549 TextRangeStyle& rangeStyle = sd->mTextRangeStyle;
6550 if (rangeStyle.IsDefined()) {
6551 if (!rangeStyle.IsLineStyleDefined() ||
6552 rangeStyle.mLineStyle == TextRangeStyle::LINESTYLE_NONE) {
6553 continue;
6555 style = rangeStyle.mLineStyle;
6556 relativeSize = rangeStyle.mIsBoldLine ? 2.0f : 1.0f;
6557 } else if (!nsTextPaintStyle::GetSelectionUnderline(aPresContext, index,
6558 nullptr, &relativeSize,
6559 &style)) {
6560 continue;
6563 nsRect decorationArea;
6564 gfxSize size(aPresContext->AppUnitsToGfxUnits(aRect.width),
6565 ComputeSelectionUnderlineHeight(aPresContext,
6566 metrics, sd->mType));
6567 relativeSize = std::max(relativeSize, 1.0f);
6568 size.height *= relativeSize;
6569 decorationArea =
6570 nsCSSRendering::GetTextDecorationRect(aPresContext, size,
6571 ascent, underlineOffset, NS_STYLE_TEXT_DECORATION_LINE_UNDERLINE,
6572 style, verticalRun, descentLimit);
6573 aRect.UnionRect(aRect, decorationArea);
6575 DestroySelectionDetails(details);
6577 return !aRect.IsEmpty() && !givenRect.Contains(aRect);
6580 bool
6581 nsTextFrame::IsFrameSelected() const
6583 NS_ASSERTION(!GetContent() || GetContent()->IsSelectionDescendant(),
6584 "use the public IsSelected() instead");
6585 return nsRange::IsNodeSelected(GetContent(), GetContentOffset(),
6586 GetContentEnd());
6589 void
6590 nsTextFrame::SetSelectedRange(uint32_t aStart, uint32_t aEnd, bool aSelected,
6591 SelectionType aType)
6593 NS_ASSERTION(!GetPrevContinuation(), "Should only be called for primary frame");
6594 DEBUG_VERIFY_NOT_DIRTY(mState);
6596 // Selection is collapsed, which can't affect text frame rendering
6597 if (aStart == aEnd)
6598 return;
6600 nsTextFrame* f = this;
6601 while (f && f->GetContentEnd() <= int32_t(aStart)) {
6602 f = static_cast<nsTextFrame*>(f->GetNextContinuation());
6605 nsPresContext* presContext = PresContext();
6606 while (f && f->GetContentOffset() < int32_t(aEnd)) {
6607 // We may need to reflow to recompute the overflow area for
6608 // spellchecking or IME underline if their underline is thicker than
6609 // the normal decoration line.
6610 if (aType & SelectionTypesWithDecorations) {
6611 bool didHaveOverflowingSelection =
6612 (f->GetStateBits() & TEXT_SELECTION_UNDERLINE_OVERFLOWED) != 0;
6613 nsRect r(nsPoint(0, 0), GetSize());
6614 bool willHaveOverflowingSelection =
6615 aSelected && f->CombineSelectionUnderlineRect(presContext, r);
6616 if (didHaveOverflowingSelection || willHaveOverflowingSelection) {
6617 presContext->PresShell()->FrameNeedsReflow(f,
6618 nsIPresShell::eStyleChange,
6619 NS_FRAME_IS_DIRTY);
6622 // Selection might change anything. Invalidate the overflow area.
6623 f->InvalidateFrame();
6625 f = static_cast<nsTextFrame*>(f->GetNextContinuation());
6629 nsresult
6630 nsTextFrame::GetPointFromOffset(int32_t inOffset,
6631 nsPoint* outPoint)
6633 if (!outPoint)
6634 return NS_ERROR_NULL_POINTER;
6636 outPoint->x = 0;
6637 outPoint->y = 0;
6639 DEBUG_VERIFY_NOT_DIRTY(mState);
6640 if (mState & NS_FRAME_IS_DIRTY)
6641 return NS_ERROR_UNEXPECTED;
6643 if (GetContentLength() <= 0) {
6644 return NS_OK;
6647 gfxSkipCharsIterator iter = EnsureTextRun(nsTextFrame::eInflated);
6648 if (!mTextRun)
6649 return NS_ERROR_FAILURE;
6651 PropertyProvider properties(this, iter, nsTextFrame::eInflated);
6652 // Don't trim trailing whitespace, we want the caret to appear in the right
6653 // place if it's positioned there
6654 properties.InitializeForDisplay(false);
6656 if (inOffset < GetContentOffset()){
6657 NS_WARNING("offset before this frame's content");
6658 inOffset = GetContentOffset();
6659 } else if (inOffset > GetContentEnd()) {
6660 NS_WARNING("offset after this frame's content");
6661 inOffset = GetContentEnd();
6663 int32_t trimmedOffset = properties.GetStart().GetOriginalOffset();
6664 int32_t trimmedEnd = trimmedOffset + properties.GetOriginalLength();
6665 inOffset = std::max(inOffset, trimmedOffset);
6666 inOffset = std::min(inOffset, trimmedEnd);
6668 iter.SetOriginalOffset(inOffset);
6670 if (inOffset < trimmedEnd &&
6671 !iter.IsOriginalCharSkipped() &&
6672 !mTextRun->IsClusterStart(iter.GetSkippedOffset())) {
6673 NS_WARNING("GetPointFromOffset called for non-cluster boundary");
6674 FindClusterStart(mTextRun, trimmedOffset, &iter);
6677 gfxFloat advance =
6678 mTextRun->GetAdvanceWidth(properties.GetStart().GetSkippedOffset(),
6679 GetSkippedDistance(properties.GetStart(), iter),
6680 &properties);
6681 nscoord iSize = NSToCoordCeilClamped(advance);
6683 if (mTextRun->IsVertical()) {
6684 if (mTextRun->IsRightToLeft()) {
6685 outPoint->y = mRect.height - iSize;
6686 } else {
6687 outPoint->y = iSize;
6689 } else {
6690 if (mTextRun->IsRightToLeft()) {
6691 outPoint->x = mRect.width - iSize;
6692 } else {
6693 outPoint->x = iSize;
6697 return NS_OK;
6700 nsresult
6701 nsTextFrame::GetChildFrameContainingOffset(int32_t aContentOffset,
6702 bool aHint,
6703 int32_t* aOutOffset,
6704 nsIFrame**aOutFrame)
6706 DEBUG_VERIFY_NOT_DIRTY(mState);
6707 #if 0 //XXXrbs disable due to bug 310227
6708 if (mState & NS_FRAME_IS_DIRTY)
6709 return NS_ERROR_UNEXPECTED;
6710 #endif
6712 NS_ASSERTION(aOutOffset && aOutFrame, "Bad out parameters");
6713 NS_ASSERTION(aContentOffset >= 0, "Negative content offset, existing code was very broken!");
6714 nsIFrame* primaryFrame = mContent->GetPrimaryFrame();
6715 if (this != primaryFrame) {
6716 // This call needs to happen on the primary frame
6717 return primaryFrame->GetChildFrameContainingOffset(aContentOffset, aHint,
6718 aOutOffset, aOutFrame);
6721 nsTextFrame* f = this;
6722 int32_t offset = mContentOffset;
6724 // Try to look up the offset to frame property
6725 nsTextFrame* cachedFrame = static_cast<nsTextFrame*>
6726 (Properties().Get(OffsetToFrameProperty()));
6728 if (cachedFrame) {
6729 f = cachedFrame;
6730 offset = f->GetContentOffset();
6732 f->RemoveStateBits(TEXT_IN_OFFSET_CACHE);
6735 if ((aContentOffset >= offset) &&
6736 (aHint || aContentOffset != offset)) {
6737 while (true) {
6738 nsTextFrame* next = static_cast<nsTextFrame*>(f->GetNextContinuation());
6739 if (!next || aContentOffset < next->GetContentOffset())
6740 break;
6741 if (aContentOffset == next->GetContentOffset()) {
6742 if (aHint) {
6743 f = next;
6744 if (f->GetContentLength() == 0) {
6745 continue; // use the last of the empty frames with this offset
6748 break;
6750 f = next;
6752 } else {
6753 while (true) {
6754 nsTextFrame* prev = static_cast<nsTextFrame*>(f->GetPrevContinuation());
6755 if (!prev || aContentOffset > f->GetContentOffset())
6756 break;
6757 if (aContentOffset == f->GetContentOffset()) {
6758 if (!aHint) {
6759 f = prev;
6760 if (f->GetContentLength() == 0) {
6761 continue; // use the first of the empty frames with this offset
6764 break;
6766 f = prev;
6770 *aOutOffset = aContentOffset - f->GetContentOffset();
6771 *aOutFrame = f;
6773 // cache the frame we found
6774 Properties().Set(OffsetToFrameProperty(), f);
6775 f->AddStateBits(TEXT_IN_OFFSET_CACHE);
6777 return NS_OK;
6780 nsIFrame::FrameSearchResult
6781 nsTextFrame::PeekOffsetNoAmount(bool aForward, int32_t* aOffset)
6783 NS_ASSERTION(aOffset && *aOffset <= GetContentLength(), "aOffset out of range");
6785 gfxSkipCharsIterator iter = EnsureTextRun(nsTextFrame::eInflated);
6786 if (!mTextRun)
6787 return CONTINUE_EMPTY;
6789 TrimmedOffsets trimmed = GetTrimmedOffsets(mContent->GetText(), true);
6790 // Check whether there are nonskipped characters in the trimmmed range
6791 return (iter.ConvertOriginalToSkipped(trimmed.GetEnd()) >
6792 iter.ConvertOriginalToSkipped(trimmed.mStart)) ? FOUND : CONTINUE;
6796 * This class iterates through the clusters before or after the given
6797 * aPosition (which is a content offset). You can test each cluster
6798 * to see if it's whitespace (as far as selection/caret movement is concerned),
6799 * or punctuation, or if there is a word break before the cluster. ("Before"
6800 * is interpreted according to aDirection, so if aDirection is -1, "before"
6801 * means actually *after* the cluster content.)
6803 class MOZ_STACK_CLASS ClusterIterator {
6804 public:
6805 ClusterIterator(nsTextFrame* aTextFrame, int32_t aPosition, int32_t aDirection,
6806 nsString& aContext);
6808 bool NextCluster();
6809 bool IsWhitespace();
6810 bool IsPunctuation();
6811 bool HaveWordBreakBefore() { return mHaveWordBreak; }
6812 int32_t GetAfterOffset();
6813 int32_t GetBeforeOffset();
6815 private:
6816 gfxSkipCharsIterator mIterator;
6817 const nsTextFragment* mFrag;
6818 nsTextFrame* mTextFrame;
6819 int32_t mDirection;
6820 int32_t mCharIndex;
6821 nsTextFrame::TrimmedOffsets mTrimmed;
6822 nsTArray<bool> mWordBreaks;
6823 bool mHaveWordBreak;
6826 static bool
6827 IsAcceptableCaretPosition(const gfxSkipCharsIterator& aIter,
6828 bool aRespectClusters,
6829 gfxTextRun* aTextRun,
6830 nsIFrame* aFrame)
6832 if (aIter.IsOriginalCharSkipped())
6833 return false;
6834 uint32_t index = aIter.GetSkippedOffset();
6835 if (aRespectClusters && !aTextRun->IsClusterStart(index))
6836 return false;
6837 if (index > 0) {
6838 // Check whether the proposed position is in between the two halves of a
6839 // surrogate pair; if so, this is not a valid character boundary.
6840 // (In the case where we are respecting clusters, we won't actually get
6841 // this far because the low surrogate is also marked as non-clusterStart
6842 // so we'll return FALSE above.)
6843 if (aTextRun->CharIsLowSurrogate(index)) {
6844 return false;
6847 return true;
6850 nsIFrame::FrameSearchResult
6851 nsTextFrame::PeekOffsetCharacter(bool aForward, int32_t* aOffset,
6852 bool aRespectClusters)
6854 int32_t contentLength = GetContentLength();
6855 NS_ASSERTION(aOffset && *aOffset <= contentLength, "aOffset out of range");
6857 bool selectable;
6858 uint8_t selectStyle;
6859 IsSelectable(&selectable, &selectStyle);
6860 if (selectStyle == NS_STYLE_USER_SELECT_ALL)
6861 return CONTINUE_UNSELECTABLE;
6863 gfxSkipCharsIterator iter = EnsureTextRun(nsTextFrame::eInflated);
6864 if (!mTextRun)
6865 return CONTINUE_EMPTY;
6867 TrimmedOffsets trimmed = GetTrimmedOffsets(mContent->GetText(), false);
6869 // A negative offset means "end of frame".
6870 int32_t startOffset = GetContentOffset() + (*aOffset < 0 ? contentLength : *aOffset);
6872 if (!aForward) {
6873 // If at the beginning of the line, look at the previous continuation
6874 for (int32_t i = std::min(trimmed.GetEnd(), startOffset) - 1;
6875 i >= trimmed.mStart; --i) {
6876 iter.SetOriginalOffset(i);
6877 if (IsAcceptableCaretPosition(iter, aRespectClusters, mTextRun, this)) {
6878 *aOffset = i - mContentOffset;
6879 return FOUND;
6882 *aOffset = 0;
6883 } else {
6884 // If we're at the end of a line, look at the next continuation
6885 iter.SetOriginalOffset(startOffset);
6886 if (startOffset <= trimmed.GetEnd() &&
6887 !(startOffset < trimmed.GetEnd() &&
6888 StyleText()->NewlineIsSignificant() &&
6889 iter.GetSkippedOffset() < mTextRun->GetLength() &&
6890 mTextRun->CharIsNewline(iter.GetSkippedOffset()))) {
6891 for (int32_t i = startOffset + 1; i <= trimmed.GetEnd(); ++i) {
6892 iter.SetOriginalOffset(i);
6893 if (i == trimmed.GetEnd() ||
6894 IsAcceptableCaretPosition(iter, aRespectClusters, mTextRun, this)) {
6895 *aOffset = i - mContentOffset;
6896 return FOUND;
6900 *aOffset = contentLength;
6903 return CONTINUE;
6906 bool
6907 ClusterIterator::IsWhitespace()
6909 NS_ASSERTION(mCharIndex >= 0, "No cluster selected");
6910 return IsSelectionSpace(mFrag, mCharIndex);
6913 bool
6914 ClusterIterator::IsPunctuation()
6916 NS_ASSERTION(mCharIndex >= 0, "No cluster selected");
6917 // Return true for all Punctuation categories (Unicode general category P?),
6918 // and also for Symbol categories (S?) except for Modifier Symbol, which is
6919 // kept together with any adjacent letter/number. (Bug 1066756)
6920 uint8_t cat = unicode::GetGeneralCategory(mFrag->CharAt(mCharIndex));
6921 switch (cat) {
6922 case HB_UNICODE_GENERAL_CATEGORY_CONNECT_PUNCTUATION: /* Pc */
6923 case HB_UNICODE_GENERAL_CATEGORY_DASH_PUNCTUATION: /* Pd */
6924 case HB_UNICODE_GENERAL_CATEGORY_CLOSE_PUNCTUATION: /* Pe */
6925 case HB_UNICODE_GENERAL_CATEGORY_FINAL_PUNCTUATION: /* Pf */
6926 case HB_UNICODE_GENERAL_CATEGORY_INITIAL_PUNCTUATION: /* Pi */
6927 case HB_UNICODE_GENERAL_CATEGORY_OTHER_PUNCTUATION: /* Po */
6928 case HB_UNICODE_GENERAL_CATEGORY_OPEN_PUNCTUATION: /* Ps */
6929 case HB_UNICODE_GENERAL_CATEGORY_CURRENCY_SYMBOL: /* Sc */
6930 // Deliberately omitted:
6931 // case HB_UNICODE_GENERAL_CATEGORY_MODIFIER_SYMBOL: /* Sk */
6932 case HB_UNICODE_GENERAL_CATEGORY_MATH_SYMBOL: /* Sm */
6933 case HB_UNICODE_GENERAL_CATEGORY_OTHER_SYMBOL: /* So */
6934 return true;
6935 default:
6936 return false;
6940 int32_t
6941 ClusterIterator::GetBeforeOffset()
6943 NS_ASSERTION(mCharIndex >= 0, "No cluster selected");
6944 return mCharIndex + (mDirection > 0 ? 0 : 1);
6947 int32_t
6948 ClusterIterator::GetAfterOffset()
6950 NS_ASSERTION(mCharIndex >= 0, "No cluster selected");
6951 return mCharIndex + (mDirection > 0 ? 1 : 0);
6954 bool
6955 ClusterIterator::NextCluster()
6957 if (!mDirection)
6958 return false;
6959 gfxTextRun* textRun = mTextFrame->GetTextRun(nsTextFrame::eInflated);
6961 mHaveWordBreak = false;
6962 while (true) {
6963 bool keepGoing = false;
6964 if (mDirection > 0) {
6965 if (mIterator.GetOriginalOffset() >= mTrimmed.GetEnd())
6966 return false;
6967 keepGoing = mIterator.IsOriginalCharSkipped() ||
6968 mIterator.GetOriginalOffset() < mTrimmed.mStart ||
6969 !textRun->IsClusterStart(mIterator.GetSkippedOffset());
6970 mCharIndex = mIterator.GetOriginalOffset();
6971 mIterator.AdvanceOriginal(1);
6972 } else {
6973 if (mIterator.GetOriginalOffset() <= mTrimmed.mStart)
6974 return false;
6975 mIterator.AdvanceOriginal(-1);
6976 keepGoing = mIterator.IsOriginalCharSkipped() ||
6977 mIterator.GetOriginalOffset() >= mTrimmed.GetEnd() ||
6978 !textRun->IsClusterStart(mIterator.GetSkippedOffset());
6979 mCharIndex = mIterator.GetOriginalOffset();
6982 if (mWordBreaks[GetBeforeOffset() - mTextFrame->GetContentOffset()]) {
6983 mHaveWordBreak = true;
6985 if (!keepGoing)
6986 return true;
6990 ClusterIterator::ClusterIterator(nsTextFrame* aTextFrame, int32_t aPosition,
6991 int32_t aDirection, nsString& aContext)
6992 : mTextFrame(aTextFrame), mDirection(aDirection), mCharIndex(-1)
6994 mIterator = aTextFrame->EnsureTextRun(nsTextFrame::eInflated);
6995 if (!aTextFrame->GetTextRun(nsTextFrame::eInflated)) {
6996 mDirection = 0; // signal failure
6997 return;
6999 mIterator.SetOriginalOffset(aPosition);
7001 mFrag = aTextFrame->GetContent()->GetText();
7002 mTrimmed = aTextFrame->GetTrimmedOffsets(mFrag, true);
7004 int32_t textOffset = aTextFrame->GetContentOffset();
7005 int32_t textLen = aTextFrame->GetContentLength();
7006 if (!mWordBreaks.AppendElements(textLen + 1)) {
7007 mDirection = 0; // signal failure
7008 return;
7010 memset(mWordBreaks.Elements(), false, (textLen + 1)*sizeof(bool));
7011 int32_t textStart;
7012 if (aDirection > 0) {
7013 if (aContext.IsEmpty()) {
7014 // No previous context, so it must be the start of a line or text run
7015 mWordBreaks[0] = true;
7017 textStart = aContext.Length();
7018 mFrag->AppendTo(aContext, textOffset, textLen);
7019 } else {
7020 if (aContext.IsEmpty()) {
7021 // No following context, so it must be the end of a line or text run
7022 mWordBreaks[textLen] = true;
7024 textStart = 0;
7025 nsAutoString str;
7026 mFrag->AppendTo(str, textOffset, textLen);
7027 aContext.Insert(str, 0);
7029 nsIWordBreaker* wordBreaker = nsContentUtils::WordBreaker();
7030 for (int32_t i = 0; i <= textLen; ++i) {
7031 int32_t indexInText = i + textStart;
7032 mWordBreaks[i] |=
7033 wordBreaker->BreakInBetween(aContext.get(), indexInText,
7034 aContext.get() + indexInText,
7035 aContext.Length() - indexInText);
7039 nsIFrame::FrameSearchResult
7040 nsTextFrame::PeekOffsetWord(bool aForward, bool aWordSelectEatSpace, bool aIsKeyboardSelect,
7041 int32_t* aOffset, PeekWordState* aState)
7043 int32_t contentLength = GetContentLength();
7044 NS_ASSERTION (aOffset && *aOffset <= contentLength, "aOffset out of range");
7046 bool selectable;
7047 uint8_t selectStyle;
7048 IsSelectable(&selectable, &selectStyle);
7049 if (selectStyle == NS_STYLE_USER_SELECT_ALL)
7050 return CONTINUE_UNSELECTABLE;
7052 int32_t offset = GetContentOffset() + (*aOffset < 0 ? contentLength : *aOffset);
7053 ClusterIterator cIter(this, offset, aForward ? 1 : -1, aState->mContext);
7055 if (!cIter.NextCluster())
7056 return CONTINUE_EMPTY;
7058 do {
7059 bool isPunctuation = cIter.IsPunctuation();
7060 bool isWhitespace = cIter.IsWhitespace();
7061 bool isWordBreakBefore = cIter.HaveWordBreakBefore();
7062 if (aWordSelectEatSpace == isWhitespace && !aState->mSawBeforeType) {
7063 aState->SetSawBeforeType();
7064 aState->Update(isPunctuation, isWhitespace);
7065 continue;
7067 // See if we can break before the current cluster
7068 if (!aState->mAtStart) {
7069 bool canBreak;
7070 if (isPunctuation != aState->mLastCharWasPunctuation) {
7071 canBreak = BreakWordBetweenPunctuation(aState, aForward,
7072 isPunctuation, isWhitespace, aIsKeyboardSelect);
7073 } else if (!aState->mLastCharWasWhitespace &&
7074 !isWhitespace && !isPunctuation && isWordBreakBefore) {
7075 // if both the previous and the current character are not white
7076 // space but this can be word break before, we don't need to eat
7077 // a white space in this case. This case happens in some languages
7078 // that their words are not separated by white spaces. E.g.,
7079 // Japanese and Chinese.
7080 canBreak = true;
7081 } else {
7082 canBreak = isWordBreakBefore && aState->mSawBeforeType &&
7083 (aWordSelectEatSpace != isWhitespace);
7085 if (canBreak) {
7086 *aOffset = cIter.GetBeforeOffset() - mContentOffset;
7087 return FOUND;
7090 aState->Update(isPunctuation, isWhitespace);
7091 } while (cIter.NextCluster());
7093 *aOffset = cIter.GetAfterOffset() - mContentOffset;
7094 return CONTINUE;
7097 // TODO this needs to be deCOMtaminated with the interface fixed in
7098 // nsIFrame.h, but we won't do that until the old textframe is gone.
7099 nsresult
7100 nsTextFrame::CheckVisibility(nsPresContext* aContext, int32_t aStartIndex,
7101 int32_t aEndIndex, bool aRecurse, bool *aFinished, bool *aRetval)
7103 if (!aRetval)
7104 return NS_ERROR_NULL_POINTER;
7106 // Text in the range is visible if there is at least one character in the range
7107 // that is not skipped and is mapped by this frame (which is the primary frame)
7108 // or one of its continuations.
7109 for (nsTextFrame* f = this; f;
7110 f = static_cast<nsTextFrame*>(GetNextContinuation())) {
7111 int32_t dummyOffset = 0;
7112 if (f->PeekOffsetNoAmount(true, &dummyOffset) == FOUND) {
7113 *aRetval = true;
7114 return NS_OK;
7118 *aRetval = false;
7119 return NS_OK;
7122 nsresult
7123 nsTextFrame::GetOffsets(int32_t &start, int32_t &end) const
7125 start = GetContentOffset();
7126 end = GetContentEnd();
7127 return NS_OK;
7130 static int32_t
7131 FindEndOfPunctuationRun(const nsTextFragment* aFrag,
7132 gfxTextRun* aTextRun,
7133 gfxSkipCharsIterator* aIter,
7134 int32_t aOffset,
7135 int32_t aStart,
7136 int32_t aEnd)
7138 int32_t i;
7140 for (i = aStart; i < aEnd - aOffset; ++i) {
7141 if (nsContentUtils::IsFirstLetterPunctuationAt(aFrag, aOffset + i)) {
7142 aIter->SetOriginalOffset(aOffset + i);
7143 FindClusterEnd(aTextRun, aEnd, aIter);
7144 i = aIter->GetOriginalOffset() - aOffset;
7145 } else {
7146 break;
7149 return i;
7153 * Returns true if this text frame completes the first-letter, false
7154 * if it does not contain a true "letter".
7155 * If returns true, then it also updates aLength to cover just the first-letter
7156 * text.
7158 * XXX :first-letter should be handled during frame construction
7159 * (and it has a good bit in common with nextBidi)
7161 * @param aLength an in/out parameter: on entry contains the maximum length to
7162 * return, on exit returns length of the first-letter fragment (which may
7163 * include leading and trailing punctuation, for example)
7165 static bool
7166 FindFirstLetterRange(const nsTextFragment* aFrag,
7167 gfxTextRun* aTextRun,
7168 int32_t aOffset, const gfxSkipCharsIterator& aIter,
7169 int32_t* aLength)
7171 int32_t i;
7172 int32_t length = *aLength;
7173 int32_t endOffset = aOffset + length;
7174 gfxSkipCharsIterator iter(aIter);
7176 // skip leading whitespace, then consume clusters that start with punctuation
7177 i = FindEndOfPunctuationRun(aFrag, aTextRun, &iter, aOffset,
7178 GetTrimmableWhitespaceCount(aFrag, aOffset, length, 1),
7179 endOffset);
7180 if (i == length)
7181 return false;
7183 // If the next character is not a letter or number, there is no first-letter.
7184 // Return true so that we don't go on looking, but set aLength to 0.
7185 if (!nsContentUtils::IsAlphanumericAt(aFrag, aOffset + i)) {
7186 *aLength = 0;
7187 return true;
7190 // consume another cluster (the actual first letter)
7192 // For complex scripts such as Indic and SEAsian, where first-letter
7193 // should extend to entire orthographic "syllable" clusters, we don't
7194 // want to allow this to split a ligature.
7195 bool allowSplitLigature;
7197 switch (unicode::GetScriptCode(aFrag->CharAt(aOffset + i))) {
7198 default:
7199 allowSplitLigature = true;
7200 break;
7202 // For now, lacking any definitive specification of when to apply this
7203 // behavior, we'll base the decision on the HarfBuzz shaping engine
7204 // used for each script: those that are handled by the Indic, Tibetan,
7205 // Myanmar and SEAsian shapers will apply the "don't split ligatures"
7206 // rule.
7208 // Indic
7209 case MOZ_SCRIPT_BENGALI:
7210 case MOZ_SCRIPT_DEVANAGARI:
7211 case MOZ_SCRIPT_GUJARATI:
7212 case MOZ_SCRIPT_GURMUKHI:
7213 case MOZ_SCRIPT_KANNADA:
7214 case MOZ_SCRIPT_MALAYALAM:
7215 case MOZ_SCRIPT_ORIYA:
7216 case MOZ_SCRIPT_TAMIL:
7217 case MOZ_SCRIPT_TELUGU:
7218 case MOZ_SCRIPT_SINHALA:
7219 case MOZ_SCRIPT_BALINESE:
7220 case MOZ_SCRIPT_LEPCHA:
7221 case MOZ_SCRIPT_REJANG:
7222 case MOZ_SCRIPT_SUNDANESE:
7223 case MOZ_SCRIPT_JAVANESE:
7224 case MOZ_SCRIPT_KAITHI:
7225 case MOZ_SCRIPT_MEETEI_MAYEK:
7226 case MOZ_SCRIPT_CHAKMA:
7227 case MOZ_SCRIPT_SHARADA:
7228 case MOZ_SCRIPT_TAKRI:
7229 case MOZ_SCRIPT_KHMER:
7231 // Tibetan
7232 case MOZ_SCRIPT_TIBETAN:
7234 // Myanmar
7235 case MOZ_SCRIPT_MYANMAR:
7237 // Other SEAsian
7238 case MOZ_SCRIPT_BUGINESE:
7239 case MOZ_SCRIPT_NEW_TAI_LUE:
7240 case MOZ_SCRIPT_CHAM:
7241 case MOZ_SCRIPT_TAI_THAM:
7243 // What about Thai/Lao - any special handling needed?
7244 // Should we special-case Arabic lam-alef?
7246 allowSplitLigature = false;
7247 break;
7250 iter.SetOriginalOffset(aOffset + i);
7251 FindClusterEnd(aTextRun, endOffset, &iter, allowSplitLigature);
7253 i = iter.GetOriginalOffset() - aOffset;
7254 if (i + 1 == length)
7255 return true;
7257 // consume clusters that start with punctuation
7258 i = FindEndOfPunctuationRun(aFrag, aTextRun, &iter, aOffset, i + 1, endOffset);
7259 if (i < length)
7260 *aLength = i;
7261 return true;
7264 static uint32_t
7265 FindStartAfterSkippingWhitespace(PropertyProvider* aProvider,
7266 nsIFrame::InlineIntrinsicISizeData* aData,
7267 const nsStyleText* aTextStyle,
7268 gfxSkipCharsIterator* aIterator,
7269 uint32_t aFlowEndInTextRun)
7271 if (aData->skipWhitespace) {
7272 while (aIterator->GetSkippedOffset() < aFlowEndInTextRun &&
7273 IsTrimmableSpace(aProvider->GetFragment(), aIterator->GetOriginalOffset(), aTextStyle)) {
7274 aIterator->AdvanceOriginal(1);
7277 return aIterator->GetSkippedOffset();
7280 union VoidPtrOrFloat {
7281 VoidPtrOrFloat() : p(nullptr) {}
7283 void *p;
7284 float f;
7287 float
7288 nsTextFrame::GetFontSizeInflation() const
7290 if (!HasFontSizeInflation()) {
7291 return 1.0f;
7293 VoidPtrOrFloat u;
7294 u.p = Properties().Get(FontSizeInflationProperty());
7295 return u.f;
7298 void
7299 nsTextFrame::SetFontSizeInflation(float aInflation)
7301 if (aInflation == 1.0f) {
7302 if (HasFontSizeInflation()) {
7303 RemoveStateBits(TEXT_HAS_FONT_INFLATION);
7304 Properties().Delete(FontSizeInflationProperty());
7306 return;
7309 AddStateBits(TEXT_HAS_FONT_INFLATION);
7310 VoidPtrOrFloat u;
7311 u.f = aInflation;
7312 Properties().Set(FontSizeInflationProperty(), u.p);
7315 /* virtual */
7316 void nsTextFrame::MarkIntrinsicISizesDirty()
7318 ClearTextRuns();
7319 nsFrame::MarkIntrinsicISizesDirty();
7322 // XXX this doesn't handle characters shaped by line endings. We need to
7323 // temporarily override the "current line ending" settings.
7324 void
7325 nsTextFrame::AddInlineMinISizeForFlow(nsRenderingContext *aRenderingContext,
7326 nsIFrame::InlineMinISizeData *aData,
7327 TextRunType aTextRunType)
7329 uint32_t flowEndInTextRun;
7330 gfxContext* ctx = aRenderingContext->ThebesContext();
7331 gfxSkipCharsIterator iter =
7332 EnsureTextRun(aTextRunType, ctx, aData->lineContainer,
7333 aData->line, &flowEndInTextRun);
7334 gfxTextRun *textRun = GetTextRun(aTextRunType);
7335 if (!textRun)
7336 return;
7338 // Pass null for the line container. This will disable tab spacing, but that's
7339 // OK since we can't really handle tabs for intrinsic sizing anyway.
7340 const nsStyleText* textStyle = StyleText();
7341 const nsTextFragment* frag = mContent->GetText();
7343 // If we're hyphenating, the PropertyProvider needs the actual length;
7344 // otherwise we can just pass INT32_MAX to mean "all the text"
7345 int32_t len = INT32_MAX;
7346 bool hyphenating = frag->GetLength() > 0 &&
7347 (textStyle->mHyphens == NS_STYLE_HYPHENS_AUTO ||
7348 (textStyle->mHyphens == NS_STYLE_HYPHENS_MANUAL &&
7349 (textRun->GetFlags() & gfxTextRunFactory::TEXT_ENABLE_HYPHEN_BREAKS) != 0));
7350 if (hyphenating) {
7351 gfxSkipCharsIterator tmp(iter);
7352 len = std::min<int32_t>(GetContentOffset() + GetInFlowContentLength(),
7353 tmp.ConvertSkippedToOriginal(flowEndInTextRun)) - iter.GetOriginalOffset();
7355 PropertyProvider provider(textRun, textStyle, frag, this,
7356 iter, len, nullptr, 0, aTextRunType);
7358 bool collapseWhitespace = !textStyle->WhiteSpaceIsSignificant();
7359 bool preformatNewlines = textStyle->NewlineIsSignificant();
7360 bool preformatTabs = textStyle->WhiteSpaceIsSignificant();
7361 gfxFloat tabWidth = -1;
7362 uint32_t start =
7363 FindStartAfterSkippingWhitespace(&provider, aData, textStyle, &iter, flowEndInTextRun);
7365 AutoFallibleTArray<bool,BIG_TEXT_NODE_SIZE> hyphBuffer;
7366 bool *hyphBreakBefore = nullptr;
7367 if (hyphenating) {
7368 hyphBreakBefore = hyphBuffer.AppendElements(flowEndInTextRun - start);
7369 if (hyphBreakBefore) {
7370 provider.GetHyphenationBreaks(start, flowEndInTextRun - start,
7371 hyphBreakBefore);
7375 for (uint32_t i = start, wordStart = start; i <= flowEndInTextRun; ++i) {
7376 bool preformattedNewline = false;
7377 bool preformattedTab = false;
7378 if (i < flowEndInTextRun) {
7379 // XXXldb Shouldn't we be including the newline as part of the
7380 // segment that it ends rather than part of the segment that it
7381 // starts?
7382 preformattedNewline = preformatNewlines && textRun->CharIsNewline(i);
7383 preformattedTab = preformatTabs && textRun->CharIsTab(i);
7384 if (!textRun->CanBreakLineBefore(i) &&
7385 !preformattedNewline &&
7386 !preformattedTab &&
7387 (!hyphBreakBefore || !hyphBreakBefore[i - start]))
7389 // we can't break here (and it's not the end of the flow)
7390 continue;
7394 if (i > wordStart) {
7395 nscoord width =
7396 NSToCoordCeilClamped(textRun->GetAdvanceWidth(wordStart, i - wordStart, &provider));
7397 aData->currentLine = NSCoordSaturatingAdd(aData->currentLine, width);
7398 aData->atStartOfLine = false;
7400 if (collapseWhitespace) {
7401 uint32_t trimStart = GetEndOfTrimmedText(frag, textStyle, wordStart, i, &iter);
7402 if (trimStart == start) {
7403 // This is *all* trimmable whitespace, so whatever trailingWhitespace
7404 // we saw previously is still trailing...
7405 aData->trailingWhitespace += width;
7406 } else {
7407 // Some non-whitespace so the old trailingWhitespace is no longer trailing
7408 aData->trailingWhitespace =
7409 NSToCoordCeilClamped(textRun->GetAdvanceWidth(trimStart, i - trimStart, &provider));
7411 } else {
7412 aData->trailingWhitespace = 0;
7416 if (preformattedTab) {
7417 PropertyProvider::Spacing spacing;
7418 provider.GetSpacing(i, 1, &spacing);
7419 aData->currentLine += nscoord(spacing.mBefore);
7420 gfxFloat afterTab =
7421 AdvanceToNextTab(aData->currentLine, this,
7422 textRun, &tabWidth);
7423 aData->currentLine = nscoord(afterTab + spacing.mAfter);
7424 wordStart = i + 1;
7425 } else if (i < flowEndInTextRun ||
7426 (i == textRun->GetLength() &&
7427 (textRun->GetFlags() & nsTextFrameUtils::TEXT_HAS_TRAILING_BREAK))) {
7428 if (preformattedNewline) {
7429 aData->ForceBreak(aRenderingContext);
7430 } else if (i < flowEndInTextRun && hyphBreakBefore &&
7431 hyphBreakBefore[i - start])
7433 aData->OptionallyBreak(aRenderingContext,
7434 NSToCoordRound(provider.GetHyphenWidth()));
7435 } else {
7436 aData->OptionallyBreak(aRenderingContext);
7438 wordStart = i;
7442 if (start < flowEndInTextRun) {
7443 // Check if we have collapsible whitespace at the end
7444 aData->skipWhitespace =
7445 IsTrimmableSpace(provider.GetFragment(),
7446 iter.ConvertSkippedToOriginal(flowEndInTextRun - 1),
7447 textStyle);
7451 bool nsTextFrame::IsCurrentFontInflation(float aInflation) const {
7452 return fabsf(aInflation - GetFontSizeInflation()) < 1e-6;
7455 // XXX Need to do something here to avoid incremental reflow bugs due to
7456 // first-line and first-letter changing min-width
7457 /* virtual */ void
7458 nsTextFrame::AddInlineMinISize(nsRenderingContext *aRenderingContext,
7459 nsIFrame::InlineMinISizeData *aData)
7461 float inflation = nsLayoutUtils::FontSizeInflationFor(this);
7462 TextRunType trtype = (inflation == 1.0f) ? eNotInflated : eInflated;
7464 if (trtype == eInflated && !IsCurrentFontInflation(inflation)) {
7465 // FIXME: Ideally, if we already have a text run, we'd move it to be
7466 // the uninflated text run.
7467 ClearTextRun(nullptr, nsTextFrame::eInflated);
7470 nsTextFrame* f;
7471 gfxTextRun* lastTextRun = nullptr;
7472 // nsContinuingTextFrame does nothing for AddInlineMinISize; all text frames
7473 // in the flow are handled right here.
7474 for (f = this; f; f = static_cast<nsTextFrame*>(f->GetNextContinuation())) {
7475 // f->GetTextRun(nsTextFrame::eNotInflated) could be null if we
7476 // haven't set up textruns yet for f. Except in OOM situations,
7477 // lastTextRun will only be null for the first text frame.
7478 if (f == this || f->GetTextRun(trtype) != lastTextRun) {
7479 nsIFrame* lc;
7480 if (aData->lineContainer &&
7481 aData->lineContainer != (lc = FindLineContainer(f))) {
7482 NS_ASSERTION(f != this, "wrong InlineMinISizeData container"
7483 " for first continuation");
7484 aData->line = nullptr;
7485 aData->lineContainer = lc;
7488 // This will process all the text frames that share the same textrun as f.
7489 f->AddInlineMinISizeForFlow(aRenderingContext, aData, trtype);
7490 lastTextRun = f->GetTextRun(trtype);
7495 // XXX this doesn't handle characters shaped by line endings. We need to
7496 // temporarily override the "current line ending" settings.
7497 void
7498 nsTextFrame::AddInlinePrefISizeForFlow(nsRenderingContext *aRenderingContext,
7499 nsIFrame::InlinePrefISizeData *aData,
7500 TextRunType aTextRunType)
7502 uint32_t flowEndInTextRun;
7503 gfxContext* ctx = aRenderingContext->ThebesContext();
7504 gfxSkipCharsIterator iter =
7505 EnsureTextRun(aTextRunType, ctx, aData->lineContainer,
7506 aData->line, &flowEndInTextRun);
7507 gfxTextRun *textRun = GetTextRun(aTextRunType);
7508 if (!textRun)
7509 return;
7511 // Pass null for the line container. This will disable tab spacing, but that's
7512 // OK since we can't really handle tabs for intrinsic sizing anyway.
7514 const nsStyleText* textStyle = StyleText();
7515 const nsTextFragment* frag = mContent->GetText();
7516 PropertyProvider provider(textRun, textStyle, frag, this,
7517 iter, INT32_MAX, nullptr, 0, aTextRunType);
7519 bool collapseWhitespace = !textStyle->WhiteSpaceIsSignificant();
7520 bool preformatNewlines = textStyle->NewlineIsSignificant();
7521 bool preformatTabs = textStyle->TabIsSignificant();
7522 gfxFloat tabWidth = -1;
7523 uint32_t start =
7524 FindStartAfterSkippingWhitespace(&provider, aData, textStyle, &iter, flowEndInTextRun);
7526 // XXX Should we consider hyphenation here?
7527 // If newlines and tabs aren't preformatted, nothing to do inside
7528 // the loop so make i skip to the end
7529 uint32_t loopStart = (preformatNewlines || preformatTabs) ? start : flowEndInTextRun;
7530 for (uint32_t i = loopStart, lineStart = start; i <= flowEndInTextRun; ++i) {
7531 bool preformattedNewline = false;
7532 bool preformattedTab = false;
7533 if (i < flowEndInTextRun) {
7534 // XXXldb Shouldn't we be including the newline as part of the
7535 // segment that it ends rather than part of the segment that it
7536 // starts?
7537 NS_ASSERTION(preformatNewlines,
7538 "We can't be here unless newlines are hard breaks");
7539 preformattedNewline = preformatNewlines && textRun->CharIsNewline(i);
7540 preformattedTab = preformatTabs && textRun->CharIsTab(i);
7541 if (!preformattedNewline && !preformattedTab) {
7542 // we needn't break here (and it's not the end of the flow)
7543 continue;
7547 if (i > lineStart) {
7548 nscoord width =
7549 NSToCoordCeilClamped(textRun->GetAdvanceWidth(lineStart, i - lineStart, &provider));
7550 aData->currentLine = NSCoordSaturatingAdd(aData->currentLine, width);
7552 if (collapseWhitespace) {
7553 uint32_t trimStart = GetEndOfTrimmedText(frag, textStyle, lineStart, i, &iter);
7554 if (trimStart == start) {
7555 // This is *all* trimmable whitespace, so whatever trailingWhitespace
7556 // we saw previously is still trailing...
7557 aData->trailingWhitespace += width;
7558 } else {
7559 // Some non-whitespace so the old trailingWhitespace is no longer trailing
7560 aData->trailingWhitespace =
7561 NSToCoordCeilClamped(textRun->GetAdvanceWidth(trimStart, i - trimStart, &provider));
7563 } else {
7564 aData->trailingWhitespace = 0;
7568 if (preformattedTab) {
7569 PropertyProvider::Spacing spacing;
7570 provider.GetSpacing(i, 1, &spacing);
7571 aData->currentLine += nscoord(spacing.mBefore);
7572 gfxFloat afterTab =
7573 AdvanceToNextTab(aData->currentLine, this,
7574 textRun, &tabWidth);
7575 aData->currentLine = nscoord(afterTab + spacing.mAfter);
7576 lineStart = i + 1;
7577 } else if (preformattedNewline) {
7578 aData->ForceBreak(aRenderingContext);
7579 lineStart = i;
7583 // Check if we have collapsible whitespace at the end
7584 if (start < flowEndInTextRun) {
7585 aData->skipWhitespace =
7586 IsTrimmableSpace(provider.GetFragment(),
7587 iter.ConvertSkippedToOriginal(flowEndInTextRun - 1),
7588 textStyle);
7592 // XXX Need to do something here to avoid incremental reflow bugs due to
7593 // first-line and first-letter changing pref-width
7594 /* virtual */ void
7595 nsTextFrame::AddInlinePrefISize(nsRenderingContext *aRenderingContext,
7596 nsIFrame::InlinePrefISizeData *aData)
7598 float inflation = nsLayoutUtils::FontSizeInflationFor(this);
7599 TextRunType trtype = (inflation == 1.0f) ? eNotInflated : eInflated;
7601 if (trtype == eInflated && !IsCurrentFontInflation(inflation)) {
7602 // FIXME: Ideally, if we already have a text run, we'd move it to be
7603 // the uninflated text run.
7604 ClearTextRun(nullptr, nsTextFrame::eInflated);
7607 nsTextFrame* f;
7608 gfxTextRun* lastTextRun = nullptr;
7609 // nsContinuingTextFrame does nothing for AddInlineMinISize; all text frames
7610 // in the flow are handled right here.
7611 for (f = this; f; f = static_cast<nsTextFrame*>(f->GetNextContinuation())) {
7612 // f->GetTextRun(nsTextFrame::eNotInflated) could be null if we
7613 // haven't set up textruns yet for f. Except in OOM situations,
7614 // lastTextRun will only be null for the first text frame.
7615 if (f == this || f->GetTextRun(trtype) != lastTextRun) {
7616 nsIFrame* lc;
7617 if (aData->lineContainer &&
7618 aData->lineContainer != (lc = FindLineContainer(f))) {
7619 NS_ASSERTION(f != this, "wrong InlinePrefISizeData container"
7620 " for first continuation");
7621 aData->line = nullptr;
7622 aData->lineContainer = lc;
7625 // This will process all the text frames that share the same textrun as f.
7626 f->AddInlinePrefISizeForFlow(aRenderingContext, aData, trtype);
7627 lastTextRun = f->GetTextRun(trtype);
7632 /* virtual */
7633 LogicalSize
7634 nsTextFrame::ComputeSize(nsRenderingContext *aRenderingContext,
7635 WritingMode aWM,
7636 const LogicalSize& aCBSize,
7637 nscoord aAvailableISize,
7638 const LogicalSize& aMargin,
7639 const LogicalSize& aBorder,
7640 const LogicalSize& aPadding,
7641 ComputeSizeFlags aFlags)
7643 // Inlines and text don't compute size before reflow.
7644 return LogicalSize(aWM, NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE);
7647 static nsRect
7648 RoundOut(const gfxRect& aRect)
7650 nsRect r;
7651 r.x = NSToCoordFloor(aRect.X());
7652 r.y = NSToCoordFloor(aRect.Y());
7653 r.width = NSToCoordCeil(aRect.XMost()) - r.x;
7654 r.height = NSToCoordCeil(aRect.YMost()) - r.y;
7655 return r;
7658 nsRect
7659 nsTextFrame::ComputeTightBounds(gfxContext* aContext) const
7661 if (StyleContext()->HasTextDecorationLines() ||
7662 (GetStateBits() & TEXT_HYPHEN_BREAK)) {
7663 // This is conservative, but OK.
7664 return GetVisualOverflowRect();
7667 gfxSkipCharsIterator iter =
7668 const_cast<nsTextFrame*>(this)->EnsureTextRun(nsTextFrame::eInflated);
7669 if (!mTextRun)
7670 return nsRect(0, 0, 0, 0);
7672 PropertyProvider provider(const_cast<nsTextFrame*>(this), iter,
7673 nsTextFrame::eInflated);
7674 // Trim trailing whitespace
7675 provider.InitializeForDisplay(true);
7677 gfxTextRun::Metrics metrics =
7678 mTextRun->MeasureText(provider.GetStart().GetSkippedOffset(),
7679 ComputeTransformedLength(provider),
7680 gfxFont::TIGHT_HINTED_OUTLINE_EXTENTS,
7681 aContext, &provider);
7682 // mAscent should be the same as metrics.mAscent, but it's what we use to
7683 // paint so that's the one we'll use.
7684 nsRect boundingBox = RoundOut(metrics.mBoundingBox);
7685 if (GetWritingMode().IsLineInverted()) {
7686 boundingBox.y = -boundingBox.YMost();
7688 boundingBox += nsPoint(0, mAscent);
7689 if (mTextRun->IsVertical()) {
7690 // Swap line-relative textMetrics dimensions to physical coordinates.
7691 Swap(boundingBox.x, boundingBox.y);
7692 Swap(boundingBox.width, boundingBox.height);
7694 return boundingBox;
7697 /* virtual */ nsresult
7698 nsTextFrame::GetPrefWidthTightBounds(nsRenderingContext* aContext,
7699 nscoord* aX,
7700 nscoord* aXMost)
7702 gfxSkipCharsIterator iter =
7703 const_cast<nsTextFrame*>(this)->EnsureTextRun(nsTextFrame::eInflated);
7704 if (!mTextRun)
7705 return NS_ERROR_FAILURE;
7707 PropertyProvider provider(const_cast<nsTextFrame*>(this), iter,
7708 nsTextFrame::eInflated);
7709 provider.InitializeForMeasure();
7711 gfxTextRun::Metrics metrics =
7712 mTextRun->MeasureText(provider.GetStart().GetSkippedOffset(),
7713 ComputeTransformedLength(provider),
7714 gfxFont::TIGHT_HINTED_OUTLINE_EXTENTS,
7715 aContext->ThebesContext(), &provider);
7716 // Round it like nsTextFrame::ComputeTightBounds() to ensure consistency.
7717 *aX = NSToCoordFloor(metrics.mBoundingBox.x);
7718 *aXMost = NSToCoordCeil(metrics.mBoundingBox.XMost());
7720 return NS_OK;
7723 static bool
7724 HasSoftHyphenBefore(const nsTextFragment* aFrag, gfxTextRun* aTextRun,
7725 int32_t aStartOffset, const gfxSkipCharsIterator& aIter)
7727 if (aIter.GetSkippedOffset() < aTextRun->GetLength() &&
7728 aTextRun->CanHyphenateBefore(aIter.GetSkippedOffset())) {
7729 return true;
7731 if (!(aTextRun->GetFlags() & nsTextFrameUtils::TEXT_HAS_SHY))
7732 return false;
7733 gfxSkipCharsIterator iter = aIter;
7734 while (iter.GetOriginalOffset() > aStartOffset) {
7735 iter.AdvanceOriginal(-1);
7736 if (!iter.IsOriginalCharSkipped())
7737 break;
7738 if (aFrag->CharAt(iter.GetOriginalOffset()) == CH_SHY)
7739 return true;
7741 return false;
7745 * Removes all frames from aFrame up to (but not including) aFirstToNotRemove,
7746 * because their text has all been taken and reflowed by earlier frames.
7748 static void
7749 RemoveEmptyInFlows(nsTextFrame* aFrame, nsTextFrame* aFirstToNotRemove)
7751 NS_PRECONDITION(aFrame != aFirstToNotRemove, "This will go very badly");
7752 // We have to be careful here, because some RemoveFrame implementations
7753 // remove and destroy not only the passed-in frame but also all its following
7754 // in-flows (and sometimes all its following continuations in general). So
7755 // we remove |f| and everything up to but not including firstToNotRemove from
7756 // the flow first, to make sure that only the things we want destroyed are
7757 // destroyed.
7759 // This sadly duplicates some of the logic from
7760 // nsSplittableFrame::RemoveFromFlow. We can get away with not duplicating
7761 // all of it, because we know that the prev-continuation links of
7762 // firstToNotRemove and f are fluid, and non-null.
7763 NS_ASSERTION(aFirstToNotRemove->GetPrevContinuation() ==
7764 aFirstToNotRemove->GetPrevInFlow() &&
7765 aFirstToNotRemove->GetPrevInFlow() != nullptr,
7766 "aFirstToNotRemove should have a fluid prev continuation");
7767 NS_ASSERTION(aFrame->GetPrevContinuation() ==
7768 aFrame->GetPrevInFlow() &&
7769 aFrame->GetPrevInFlow() != nullptr,
7770 "aFrame should have a fluid prev continuation");
7772 nsIFrame* prevContinuation = aFrame->GetPrevContinuation();
7773 nsIFrame* lastRemoved = aFirstToNotRemove->GetPrevContinuation();
7775 for (nsTextFrame* f = aFrame; f != aFirstToNotRemove;
7776 f = static_cast<nsTextFrame*>(f->GetNextContinuation())) {
7777 // f is going to be destroyed soon, after it is unlinked from the
7778 // continuation chain. If its textrun is going to be destroyed we need to
7779 // do it now, before we unlink the frames to remove from the flow,
7780 // because DestroyFrom calls ClearTextRuns() and that will start at the
7781 // first frame with the text run and walk the continuations.
7782 if (f->IsInTextRunUserData()) {
7783 f->ClearTextRuns();
7784 } else {
7785 f->DisconnectTextRuns();
7789 prevContinuation->SetNextInFlow(aFirstToNotRemove);
7790 aFirstToNotRemove->SetPrevInFlow(prevContinuation);
7792 aFrame->SetPrevInFlow(nullptr);
7793 lastRemoved->SetNextInFlow(nullptr);
7795 nsContainerFrame* parent = aFrame->GetParent();
7796 nsBlockFrame* parentBlock = nsLayoutUtils::GetAsBlock(parent);
7797 if (parentBlock) {
7798 // Manually call DoRemoveFrame so we can tell it that we're
7799 // removing empty frames; this will keep it from blowing away
7800 // text runs.
7801 parentBlock->DoRemoveFrame(aFrame, nsBlockFrame::FRAMES_ARE_EMPTY);
7802 } else {
7803 // Just remove it normally; use kNoReflowPrincipalList to avoid posting
7804 // new reflows.
7805 parent->RemoveFrame(nsIFrame::kNoReflowPrincipalList, aFrame);
7809 void
7810 nsTextFrame::SetLength(int32_t aLength, nsLineLayout* aLineLayout,
7811 uint32_t aSetLengthFlags)
7813 mContentLengthHint = aLength;
7814 int32_t end = GetContentOffset() + aLength;
7815 nsTextFrame* f = static_cast<nsTextFrame*>(GetNextInFlow());
7816 if (!f)
7817 return;
7819 // If our end offset is moving, then even if frames are not being pushed or
7820 // pulled, content is moving to or from the next line and the next line
7821 // must be reflowed.
7822 // If the next-continuation is dirty, then we should dirty the next line now
7823 // because we may have skipped doing it if we dirtied it in
7824 // CharacterDataChanged. This is ugly but teaching FrameNeedsReflow
7825 // and ChildIsDirty to handle a range of frames would be worse.
7826 if (aLineLayout &&
7827 (end != f->mContentOffset || (f->GetStateBits() & NS_FRAME_IS_DIRTY))) {
7828 aLineLayout->SetDirtyNextLine();
7831 if (end < f->mContentOffset) {
7832 // Our frame is shrinking. Give the text to our next in flow.
7833 if (aLineLayout &&
7834 HasSignificantTerminalNewline() &&
7835 GetParent()->GetType() != nsGkAtoms::letterFrame &&
7836 (aSetLengthFlags & ALLOW_FRAME_CREATION_AND_DESTRUCTION)) {
7837 // Whatever text we hand to our next-in-flow will end up in a frame all of
7838 // its own, since it ends in a forced linebreak. Might as well just put
7839 // it in a separate frame now. This is important to prevent text run
7840 // churn; if we did not do that, then we'd likely end up rebuilding
7841 // textruns for all our following continuations.
7842 // We skip this optimization when the parent is a first-letter frame
7843 // because it doesn't deal well with more than one child frame.
7844 // We also skip this optimization if we were called during bidi
7845 // resolution, so as not to create a new frame which doesn't appear in
7846 // the bidi resolver's list of frames
7847 nsPresContext* presContext = PresContext();
7848 nsIFrame* newFrame = presContext->PresShell()->FrameConstructor()->
7849 CreateContinuingFrame(presContext, this, GetParent());
7850 nsTextFrame* next = static_cast<nsTextFrame*>(newFrame);
7851 nsFrameList temp(next, next);
7852 GetParent()->InsertFrames(kNoReflowPrincipalList, this, temp);
7853 f = next;
7856 f->mContentOffset = end;
7857 if (f->GetTextRun(nsTextFrame::eInflated) != mTextRun) {
7858 ClearTextRuns();
7859 f->ClearTextRuns();
7861 return;
7863 // Our frame is growing. Take text from our in-flow(s).
7864 // We can take text from frames in lines beyond just the next line.
7865 // We don't dirty those lines. That's OK, because when we reflow
7866 // our empty next-in-flow, it will take text from its next-in-flow and
7867 // dirty that line.
7869 // Note that in the process we may end up removing some frames from
7870 // the flow if they end up empty.
7871 nsTextFrame* framesToRemove = nullptr;
7872 while (f && f->mContentOffset < end) {
7873 f->mContentOffset = end;
7874 if (f->GetTextRun(nsTextFrame::eInflated) != mTextRun) {
7875 ClearTextRuns();
7876 f->ClearTextRuns();
7878 nsTextFrame* next = static_cast<nsTextFrame*>(f->GetNextInFlow());
7879 // Note: the "f->GetNextSibling() == next" check below is to restrict
7880 // this optimization to the case where they are on the same child list.
7881 // Otherwise we might remove the only child of a nsFirstLetterFrame
7882 // for example and it can't handle that. See bug 597627 for details.
7883 if (next && next->mContentOffset <= end && f->GetNextSibling() == next &&
7884 (aSetLengthFlags & ALLOW_FRAME_CREATION_AND_DESTRUCTION)) {
7885 // |f| is now empty. We may as well remove it, instead of copying all
7886 // the text from |next| into it instead; the latter leads to use
7887 // rebuilding textruns for all following continuations.
7888 // We skip this optimization if we were called during bidi resolution,
7889 // since the bidi resolver may try to handle the destroyed frame later
7890 // and crash
7891 if (!framesToRemove) {
7892 // Remember that we have to remove this frame.
7893 framesToRemove = f;
7895 } else if (framesToRemove) {
7896 RemoveEmptyInFlows(framesToRemove, f);
7897 framesToRemove = nullptr;
7899 f = next;
7901 NS_POSTCONDITION(!framesToRemove || (f && f->mContentOffset == end),
7902 "How did we exit the loop if we null out framesToRemove if "
7903 "!next || next->mContentOffset > end ?");
7904 if (framesToRemove) {
7905 // We are guaranteed that we exited the loop with f not null, per the
7906 // postcondition above
7907 RemoveEmptyInFlows(framesToRemove, f);
7910 #ifdef DEBUG
7911 f = this;
7912 int32_t iterations = 0;
7913 while (f && iterations < 10) {
7914 f->GetContentLength(); // Assert if negative length
7915 f = static_cast<nsTextFrame*>(f->GetNextContinuation());
7916 ++iterations;
7918 f = this;
7919 iterations = 0;
7920 while (f && iterations < 10) {
7921 f->GetContentLength(); // Assert if negative length
7922 f = static_cast<nsTextFrame*>(f->GetPrevContinuation());
7923 ++iterations;
7925 #endif
7928 bool
7929 nsTextFrame::IsFloatingFirstLetterChild() const
7931 nsIFrame* frame = GetParent();
7932 return frame && frame->IsFloating() &&
7933 frame->GetType() == nsGkAtoms::letterFrame;
7936 struct NewlineProperty {
7937 int32_t mStartOffset;
7938 // The offset of the first \n after mStartOffset, or -1 if there is none
7939 int32_t mNewlineOffset;
7942 void
7943 nsTextFrame::Reflow(nsPresContext* aPresContext,
7944 nsHTMLReflowMetrics& aMetrics,
7945 const nsHTMLReflowState& aReflowState,
7946 nsReflowStatus& aStatus)
7948 DO_GLOBAL_REFLOW_COUNT("nsTextFrame");
7949 DISPLAY_REFLOW(aPresContext, this, aReflowState, aMetrics, aStatus);
7951 // XXX If there's no line layout, we shouldn't even have created this
7952 // frame. This may happen if, for example, this is text inside a table
7953 // but not inside a cell. For now, just don't reflow.
7954 if (!aReflowState.mLineLayout) {
7955 ClearMetrics(aMetrics);
7956 aStatus = NS_FRAME_COMPLETE;
7957 return;
7960 ReflowText(*aReflowState.mLineLayout, aReflowState.AvailableWidth(),
7961 aReflowState.rendContext, aMetrics, aStatus);
7963 NS_FRAME_SET_TRUNCATION(aStatus, aReflowState, aMetrics);
7966 #ifdef ACCESSIBILITY
7968 * Notifies accessibility about text reflow. Used by nsTextFrame::ReflowText.
7970 class MOZ_STACK_CLASS ReflowTextA11yNotifier
7972 public:
7973 ReflowTextA11yNotifier(nsPresContext* aPresContext, nsIContent* aContent) :
7974 mContent(aContent), mPresContext(aPresContext)
7977 ~ReflowTextA11yNotifier()
7979 nsAccessibilityService* accService = nsIPresShell::AccService();
7980 if (accService) {
7981 accService->UpdateText(mPresContext->PresShell(), mContent);
7984 private:
7985 ReflowTextA11yNotifier();
7986 ReflowTextA11yNotifier(const ReflowTextA11yNotifier&);
7987 ReflowTextA11yNotifier& operator =(const ReflowTextA11yNotifier&);
7989 nsIContent* mContent;
7990 nsPresContext* mPresContext;
7992 #endif
7994 void
7995 nsTextFrame::ReflowText(nsLineLayout& aLineLayout, nscoord aAvailableWidth,
7996 nsRenderingContext* aRenderingContext,
7997 nsHTMLReflowMetrics& aMetrics,
7998 nsReflowStatus& aStatus)
8000 #ifdef NOISY_REFLOW
8001 ListTag(stdout);
8002 printf(": BeginReflow: availableWidth=%d\n", aAvailableWidth);
8003 #endif
8005 nsPresContext* presContext = PresContext();
8007 #ifdef ACCESSIBILITY
8008 // Schedule the update of accessible tree since rendered text might be changed.
8009 ReflowTextA11yNotifier(presContext, mContent);
8010 #endif
8012 /////////////////////////////////////////////////////////////////////
8013 // Set up flags and clear out state
8014 /////////////////////////////////////////////////////////////////////
8016 // Clear out the reflow state flags in mState. We also clear the whitespace
8017 // flags because this can change whether the frame maps whitespace-only text
8018 // or not.
8019 RemoveStateBits(TEXT_REFLOW_FLAGS | TEXT_WHITESPACE_FLAGS);
8021 // Temporarily map all possible content while we construct our new textrun.
8022 // so that when doing reflow our styles prevail over any part of the
8023 // textrun we look at. Note that next-in-flows may be mapping the same
8024 // content; gfxTextRun construction logic will ensure that we take priority.
8025 int32_t maxContentLength = GetInFlowContentLength();
8027 // We don't need to reflow if there is no content.
8028 if (!maxContentLength) {
8029 ClearMetrics(aMetrics);
8030 aStatus = NS_FRAME_COMPLETE;
8031 return;
8034 #ifdef NOISY_BIDI
8035 printf("Reflowed textframe\n");
8036 #endif
8038 const nsStyleText* textStyle = StyleText();
8040 bool atStartOfLine = aLineLayout.LineAtStart();
8041 if (atStartOfLine) {
8042 AddStateBits(TEXT_START_OF_LINE);
8045 uint32_t flowEndInTextRun;
8046 nsIFrame* lineContainer = aLineLayout.LineContainerFrame();
8047 gfxContext* ctx = aRenderingContext->ThebesContext();
8048 const nsTextFragment* frag = mContent->GetText();
8050 // DOM offsets of the text range we need to measure, after trimming
8051 // whitespace, restricting to first-letter, and restricting preformatted text
8052 // to nearest newline
8053 int32_t length = maxContentLength;
8054 int32_t offset = GetContentOffset();
8056 // Restrict preformatted text to the nearest newline
8057 int32_t newLineOffset = -1; // this will be -1 or a content offset
8058 int32_t contentNewLineOffset = -1;
8059 // Pointer to the nsGkAtoms::newline set on this frame's element
8060 NewlineProperty* cachedNewlineOffset = nullptr;
8061 if (textStyle->NewlineIsSignificant()) {
8062 cachedNewlineOffset =
8063 static_cast<NewlineProperty*>(mContent->GetProperty(nsGkAtoms::newline));
8064 if (cachedNewlineOffset && cachedNewlineOffset->mStartOffset <= offset &&
8065 (cachedNewlineOffset->mNewlineOffset == -1 ||
8066 cachedNewlineOffset->mNewlineOffset >= offset)) {
8067 contentNewLineOffset = cachedNewlineOffset->mNewlineOffset;
8068 } else {
8069 contentNewLineOffset = FindChar(frag, offset,
8070 mContent->TextLength() - offset, '\n');
8072 if (contentNewLineOffset < offset + length) {
8074 The new line offset could be outside this frame if the frame has been
8075 split by bidi resolution. In that case we won't use it in this reflow
8076 (newLineOffset will remain -1), but we will still cache it in mContent
8078 newLineOffset = contentNewLineOffset;
8080 if (newLineOffset >= 0) {
8081 length = newLineOffset + 1 - offset;
8084 if ((atStartOfLine && !textStyle->WhiteSpaceIsSignificant()) ||
8085 (GetStateBits() & TEXT_IS_IN_TOKEN_MATHML)) {
8086 // Skip leading whitespace. Make sure we don't skip a 'pre-line'
8087 // newline if there is one.
8088 int32_t skipLength = newLineOffset >= 0 ? length - 1 : length;
8089 int32_t whitespaceCount =
8090 GetTrimmableWhitespaceCount(frag, offset, skipLength, 1);
8091 if (whitespaceCount) {
8092 offset += whitespaceCount;
8093 length -= whitespaceCount;
8094 // Make sure this frame maps the trimmable whitespace.
8095 if (MOZ_UNLIKELY(offset > GetContentEnd())) {
8096 SetLength(offset - GetContentOffset(), &aLineLayout,
8097 ALLOW_FRAME_CREATION_AND_DESTRUCTION);
8102 bool completedFirstLetter = false;
8103 // Layout dependent styles are a problem because we need to reconstruct
8104 // the gfxTextRun based on our layout.
8105 if (aLineLayout.GetInFirstLetter() || aLineLayout.GetInFirstLine()) {
8106 SetLength(maxContentLength, &aLineLayout,
8107 ALLOW_FRAME_CREATION_AND_DESTRUCTION);
8109 if (aLineLayout.GetInFirstLetter()) {
8110 // floating first-letter boundaries are significant in textrun
8111 // construction, so clear the textrun out every time we hit a first-letter
8112 // and have changed our length (which controls the first-letter boundary)
8113 ClearTextRuns();
8114 // Find the length of the first-letter. We need a textrun for this.
8115 // REVIEW: maybe-bogus inflation should be ok (fixed below)
8116 gfxSkipCharsIterator iter =
8117 EnsureTextRun(nsTextFrame::eInflated, ctx,
8118 lineContainer, aLineLayout.GetLine(),
8119 &flowEndInTextRun);
8121 if (mTextRun) {
8122 int32_t firstLetterLength = length;
8123 if (aLineLayout.GetFirstLetterStyleOK()) {
8124 completedFirstLetter =
8125 FindFirstLetterRange(frag, mTextRun, offset, iter, &firstLetterLength);
8126 if (newLineOffset >= 0) {
8127 // Don't allow a preformatted newline to be part of a first-letter.
8128 firstLetterLength = std::min(firstLetterLength, length - 1);
8129 if (length == 1) {
8130 // There is no text to be consumed by the first-letter before the
8131 // preformatted newline. Note that the first letter is therefore
8132 // complete (FindFirstLetterRange will have returned false).
8133 completedFirstLetter = true;
8136 } else {
8137 // We're in a first-letter frame's first in flow, so if there
8138 // was a first-letter, we'd be it. However, for one reason
8139 // or another (e.g., preformatted line break before this text),
8140 // we're not actually supposed to have first-letter style. So
8141 // just make a zero-length first-letter.
8142 firstLetterLength = 0;
8143 completedFirstLetter = true;
8145 length = firstLetterLength;
8146 if (length) {
8147 AddStateBits(TEXT_FIRST_LETTER);
8149 // Change this frame's length to the first-letter length right now
8150 // so that when we rebuild the textrun it will be built with the
8151 // right first-letter boundary
8152 SetLength(offset + length - GetContentOffset(), &aLineLayout,
8153 ALLOW_FRAME_CREATION_AND_DESTRUCTION);
8154 // Ensure that the textrun will be rebuilt
8155 ClearTextRuns();
8160 float fontSizeInflation = nsLayoutUtils::FontSizeInflationFor(this);
8162 if (!IsCurrentFontInflation(fontSizeInflation)) {
8163 // FIXME: Ideally, if we already have a text run, we'd move it to be
8164 // the uninflated text run.
8165 ClearTextRun(nullptr, nsTextFrame::eInflated);
8168 gfxSkipCharsIterator iter =
8169 EnsureTextRun(nsTextFrame::eInflated, ctx,
8170 lineContainer, aLineLayout.GetLine(), &flowEndInTextRun);
8172 NS_ASSERTION(IsCurrentFontInflation(fontSizeInflation),
8173 "EnsureTextRun should have set font size inflation");
8175 if (mTextRun && iter.GetOriginalEnd() < offset + length) {
8176 // The textrun does not map enough text for this frame. This can happen
8177 // when the textrun was ended in the middle of a text node because a
8178 // preformatted newline was encountered, and prev-in-flow frames have
8179 // consumed all the text of the textrun. We need a new textrun.
8180 ClearTextRuns();
8181 iter = EnsureTextRun(nsTextFrame::eInflated, ctx,
8182 lineContainer, aLineLayout.GetLine(),
8183 &flowEndInTextRun);
8186 if (!mTextRun) {
8187 ClearMetrics(aMetrics);
8188 aStatus = NS_FRAME_COMPLETE;
8189 return;
8192 NS_ASSERTION(gfxSkipCharsIterator(iter).ConvertOriginalToSkipped(offset + length)
8193 <= mTextRun->GetLength(),
8194 "Text run does not map enough text for our reflow");
8196 /////////////////////////////////////////////////////////////////////
8197 // See how much text should belong to this text frame, and measure it
8198 /////////////////////////////////////////////////////////////////////
8200 iter.SetOriginalOffset(offset);
8201 nscoord xOffsetForTabs = (mTextRun->GetFlags() & nsTextFrameUtils::TEXT_HAS_TAB) ?
8202 (aLineLayout.GetCurrentFrameInlineDistanceFromBlock() -
8203 lineContainer->GetUsedBorderAndPadding().left)
8204 : -1;
8205 PropertyProvider provider(mTextRun, textStyle, frag, this, iter, length,
8206 lineContainer, xOffsetForTabs, nsTextFrame::eInflated);
8208 uint32_t transformedOffset = provider.GetStart().GetSkippedOffset();
8210 // The metrics for the text go in here
8211 gfxTextRun::Metrics textMetrics;
8212 gfxFont::BoundingBoxType boundingBoxType = IsFloatingFirstLetterChild() ?
8213 gfxFont::TIGHT_HINTED_OUTLINE_EXTENTS :
8214 gfxFont::LOOSE_INK_EXTENTS;
8215 NS_ASSERTION(!(NS_REFLOW_CALC_BOUNDING_METRICS & aMetrics.mFlags),
8216 "We shouldn't be passed NS_REFLOW_CALC_BOUNDING_METRICS anymore");
8218 int32_t limitLength = length;
8219 int32_t forceBreak = aLineLayout.GetForcedBreakPosition(this);
8220 bool forceBreakAfter = false;
8221 if (forceBreak >= length) {
8222 forceBreakAfter = forceBreak == length;
8223 // The break is not within the text considered for this textframe.
8224 forceBreak = -1;
8226 if (forceBreak >= 0) {
8227 limitLength = forceBreak;
8229 // This is the heart of text reflow right here! We don't know where
8230 // to break, so we need to see how much text fits in the available width.
8231 uint32_t transformedLength;
8232 if (offset + limitLength >= int32_t(frag->GetLength())) {
8233 NS_ASSERTION(offset + limitLength == int32_t(frag->GetLength()),
8234 "Content offset/length out of bounds");
8235 NS_ASSERTION(flowEndInTextRun >= transformedOffset,
8236 "Negative flow length?");
8237 transformedLength = flowEndInTextRun - transformedOffset;
8238 } else {
8239 // we're not looking at all the content, so we need to compute the
8240 // length of the transformed substring we're looking at
8241 gfxSkipCharsIterator iter(provider.GetStart());
8242 iter.SetOriginalOffset(offset + limitLength);
8243 transformedLength = iter.GetSkippedOffset() - transformedOffset;
8245 uint32_t transformedLastBreak = 0;
8246 bool usedHyphenation;
8247 gfxFloat trimmedWidth = 0;
8248 gfxFloat availWidth = aAvailableWidth;
8249 bool canTrimTrailingWhitespace = !textStyle->WhiteSpaceIsSignificant() ||
8250 (GetStateBits() & TEXT_IS_IN_TOKEN_MATHML);
8251 int32_t unusedOffset;
8252 gfxBreakPriority breakPriority;
8253 aLineLayout.GetLastOptionalBreakPosition(&unusedOffset, &breakPriority);
8254 gfxTextRun::SuppressBreak suppressBreak = gfxTextRun::eNoSuppressBreak;
8255 if (StyleContext()->IsInlineDescendantOfRuby()) {
8256 suppressBreak = gfxTextRun::eSuppressAllBreaks;
8257 } else if (!aLineLayout.LineIsBreakable()) {
8258 suppressBreak = gfxTextRun::eSuppressInitialBreak;
8260 uint32_t transformedCharsFit =
8261 mTextRun->BreakAndMeasureText(transformedOffset, transformedLength,
8262 (GetStateBits() & TEXT_START_OF_LINE) != 0,
8263 availWidth,
8264 &provider, suppressBreak,
8265 canTrimTrailingWhitespace ? &trimmedWidth : nullptr,
8266 &textMetrics, boundingBoxType, ctx,
8267 &usedHyphenation, &transformedLastBreak,
8268 textStyle->WordCanWrap(this), &breakPriority);
8269 if (!length && !textMetrics.mAscent && !textMetrics.mDescent) {
8270 // If we're measuring a zero-length piece of text, update
8271 // the height manually.
8272 nsFontMetrics* fm = provider.GetFontMetrics();
8273 if (fm) {
8274 textMetrics.mAscent = gfxFloat(fm->MaxAscent());
8275 textMetrics.mDescent = gfxFloat(fm->MaxDescent());
8278 // The "end" iterator points to the first character after the string mapped
8279 // by this frame. Basically, its original-string offset is offset+charsFit
8280 // after we've computed charsFit.
8281 gfxSkipCharsIterator end(provider.GetEndHint());
8282 end.SetSkippedOffset(transformedOffset + transformedCharsFit);
8283 int32_t charsFit = end.GetOriginalOffset() - offset;
8284 if (offset + charsFit == newLineOffset) {
8285 // We broke before a trailing preformatted '\n'. The newline should
8286 // be assigned to this frame. Note that newLineOffset will be -1 if
8287 // there was no preformatted newline, so we wouldn't get here in that
8288 // case.
8289 ++charsFit;
8291 // That might have taken us beyond our assigned content range (because
8292 // we might have advanced over some skipped chars that extend outside
8293 // this frame), so get back in.
8294 int32_t lastBreak = -1;
8295 if (charsFit >= limitLength) {
8296 charsFit = limitLength;
8297 if (transformedLastBreak != UINT32_MAX) {
8298 // lastBreak is needed.
8299 // This may set lastBreak greater than 'length', but that's OK
8300 lastBreak = end.ConvertSkippedToOriginal(transformedOffset + transformedLastBreak);
8302 end.SetOriginalOffset(offset + charsFit);
8303 // If we were forced to fit, and the break position is after a soft hyphen,
8304 // note that this is a hyphenation break.
8305 if ((forceBreak >= 0 || forceBreakAfter) &&
8306 HasSoftHyphenBefore(frag, mTextRun, offset, end)) {
8307 usedHyphenation = true;
8310 if (usedHyphenation) {
8311 // Fix up metrics to include hyphen
8312 AddHyphenToMetrics(this, mTextRun, &textMetrics, boundingBoxType, ctx);
8313 AddStateBits(TEXT_HYPHEN_BREAK | TEXT_HAS_NONCOLLAPSED_CHARACTERS);
8316 gfxFloat trimmableWidth = 0;
8317 bool brokeText = forceBreak >= 0 || transformedCharsFit < transformedLength;
8318 if (canTrimTrailingWhitespace) {
8319 // Optimization: if we trimmed trailing whitespace, and we can be sure
8320 // this frame will be at the end of the line, then leave it trimmed off.
8321 // Otherwise we have to undo the trimming, in case we're not at the end of
8322 // the line. (If we actually do end up at the end of the line, we'll have
8323 // to trim it off again in TrimTrailingWhiteSpace, and we'd like to avoid
8324 // having to re-do it.)
8325 if (brokeText ||
8326 (GetStateBits() & TEXT_IS_IN_TOKEN_MATHML)) {
8327 // We're definitely going to break so our trailing whitespace should
8328 // definitely be trimmed. Record that we've already done it.
8329 AddStateBits(TEXT_TRIMMED_TRAILING_WHITESPACE);
8330 } else if (!(GetStateBits() & TEXT_IS_IN_TOKEN_MATHML)) {
8331 // We might not be at the end of the line. (Note that even if this frame
8332 // ends in breakable whitespace, it might not be at the end of the line
8333 // because it might be followed by breakable, but preformatted, whitespace.)
8334 // Undo the trimming.
8335 textMetrics.mAdvanceWidth += trimmedWidth;
8336 trimmableWidth = trimmedWidth;
8337 if (mTextRun->IsRightToLeft()) {
8338 // Space comes before text, so the bounding box is moved to the
8339 // right by trimmdWidth
8340 textMetrics.mBoundingBox.MoveBy(gfxPoint(trimmedWidth, 0));
8345 if (!brokeText && lastBreak >= 0) {
8346 // Since everything fit and no break was forced,
8347 // record the last break opportunity
8348 NS_ASSERTION(textMetrics.mAdvanceWidth - trimmableWidth <= aAvailableWidth,
8349 "If the text doesn't fit, and we have a break opportunity, why didn't MeasureText use it?");
8350 MOZ_ASSERT(lastBreak >= offset, "Strange break position");
8351 aLineLayout.NotifyOptionalBreakPosition(this, lastBreak - offset,
8352 true, breakPriority);
8355 int32_t contentLength = offset + charsFit - GetContentOffset();
8357 /////////////////////////////////////////////////////////////////////
8358 // Compute output metrics
8359 /////////////////////////////////////////////////////////////////////
8361 // first-letter frames should use the tight bounding box metrics for ascent/descent
8362 // for good drop-cap effects
8363 if (GetStateBits() & TEXT_FIRST_LETTER) {
8364 textMetrics.mAscent = std::max(gfxFloat(0.0), -textMetrics.mBoundingBox.Y());
8365 textMetrics.mDescent = std::max(gfxFloat(0.0), textMetrics.mBoundingBox.YMost());
8368 // Setup metrics for caller
8369 // Disallow negative widths
8370 WritingMode wm = GetWritingMode();
8371 LogicalSize finalSize(wm);
8372 finalSize.ISize(wm) = NSToCoordCeil(std::max(gfxFloat(0.0),
8373 textMetrics.mAdvanceWidth));
8375 if (transformedCharsFit == 0 && !usedHyphenation) {
8376 aMetrics.SetBlockStartAscent(0);
8377 finalSize.BSize(wm) = 0;
8378 } else if (boundingBoxType != gfxFont::LOOSE_INK_EXTENTS) {
8379 // Use actual text metrics for floating first letter frame.
8380 if (wm.IsLineInverted()) {
8381 aMetrics.SetBlockStartAscent(NSToCoordCeil(textMetrics.mDescent));
8382 finalSize.BSize(wm) = aMetrics.BlockStartAscent() +
8383 NSToCoordCeil(textMetrics.mAscent);
8384 } else {
8385 aMetrics.SetBlockStartAscent(NSToCoordCeil(textMetrics.mAscent));
8386 finalSize.BSize(wm) = aMetrics.BlockStartAscent() +
8387 NSToCoordCeil(textMetrics.mDescent);
8389 } else {
8390 // Otherwise, ascent should contain the overline drawable area.
8391 // And also descent should contain the underline drawable area.
8392 // nsFontMetrics::GetMaxAscent/GetMaxDescent contains them.
8393 nsFontMetrics* fm = provider.GetFontMetrics();
8394 nscoord fontAscent = fm->MaxAscent();
8395 nscoord fontDescent = fm->MaxDescent();
8396 if (wm.IsLineInverted()) {
8397 aMetrics.SetBlockStartAscent(std::max(NSToCoordCeil(textMetrics.mDescent), fontDescent));
8398 nscoord descent = std::max(NSToCoordCeil(textMetrics.mAscent), fontAscent);
8399 finalSize.BSize(wm) = aMetrics.BlockStartAscent() + descent;
8400 } else {
8401 aMetrics.SetBlockStartAscent(std::max(NSToCoordCeil(textMetrics.mAscent), fontAscent));
8402 nscoord descent = std::max(NSToCoordCeil(textMetrics.mDescent), fontDescent);
8403 finalSize.BSize(wm) = aMetrics.BlockStartAscent() + descent;
8406 aMetrics.SetSize(wm, finalSize);
8408 NS_ASSERTION(aMetrics.BlockStartAscent() >= 0,
8409 "Negative ascent???");
8410 NS_ASSERTION(aMetrics.BSize(aMetrics.GetWritingMode()) -
8411 aMetrics.BlockStartAscent() >= 0,
8412 "Negative descent???");
8414 mAscent = aMetrics.BlockStartAscent();
8416 // Handle text that runs outside its normal bounds.
8417 nsRect boundingBox = RoundOut(textMetrics.mBoundingBox);
8418 if (wm.IsLineInverted()) {
8419 boundingBox.y = -boundingBox.YMost();
8421 boundingBox += nsPoint(0, mAscent);
8422 if (mTextRun->IsVertical()) {
8423 // Swap line-relative textMetrics dimensions to physical coordinates.
8424 Swap(boundingBox.x, boundingBox.y);
8425 Swap(boundingBox.width, boundingBox.height);
8427 aMetrics.SetOverflowAreasToDesiredBounds();
8428 aMetrics.VisualOverflow().UnionRect(aMetrics.VisualOverflow(), boundingBox);
8430 // When we have text decorations, we don't need to compute their overflow now
8431 // because we're guaranteed to do it later
8432 // (see nsLineLayout::RelativePositionFrames)
8433 UnionAdditionalOverflow(presContext, aLineLayout.LineContainerRS()->frame,
8434 provider, &aMetrics.VisualOverflow(), false);
8436 /////////////////////////////////////////////////////////////////////
8437 // Clean up, update state
8438 /////////////////////////////////////////////////////////////////////
8440 // If all our characters are discarded or collapsed, then trimmable width
8441 // from the last textframe should be preserved. Otherwise the trimmable width
8442 // from this textframe overrides. (Currently in CSS trimmable width can be
8443 // at most one space so there's no way for trimmable width from a previous
8444 // frame to accumulate with trimmable width from this frame.)
8445 if (transformedCharsFit > 0) {
8446 aLineLayout.SetTrimmableISize(NSToCoordFloor(trimmableWidth));
8447 AddStateBits(TEXT_HAS_NONCOLLAPSED_CHARACTERS);
8449 if (charsFit > 0 && charsFit == length &&
8450 textStyle->mHyphens != NS_STYLE_HYPHENS_NONE &&
8451 HasSoftHyphenBefore(frag, mTextRun, offset, end)) {
8452 bool fits =
8453 textMetrics.mAdvanceWidth + provider.GetHyphenWidth() <= availWidth;
8454 // Record a potential break after final soft hyphen
8455 aLineLayout.NotifyOptionalBreakPosition(this, length, fits,
8456 gfxBreakPriority::eNormalBreak);
8458 bool breakAfter = forceBreakAfter;
8459 // length == 0 means either the text is empty or it's all collapsed away
8460 bool emptyTextAtStartOfLine = atStartOfLine && length == 0;
8461 if (!breakAfter && charsFit == length && !emptyTextAtStartOfLine &&
8462 transformedOffset + transformedLength == mTextRun->GetLength() &&
8463 (mTextRun->GetFlags() & nsTextFrameUtils::TEXT_HAS_TRAILING_BREAK)) {
8464 // We placed all the text in the textrun and we have a break opportunity at
8465 // the end of the textrun. We need to record it because the following
8466 // content may not care about nsLineBreaker.
8468 // Note that because we didn't break, we can be sure that (thanks to the
8469 // code up above) textMetrics.mAdvanceWidth includes the width of any
8470 // trailing whitespace. So we need to subtract trimmableWidth here
8471 // because if we did break at this point, that much width would be trimmed.
8472 if (textMetrics.mAdvanceWidth - trimmableWidth > availWidth) {
8473 breakAfter = true;
8474 } else {
8475 aLineLayout.NotifyOptionalBreakPosition(this, length, true,
8476 gfxBreakPriority::eNormalBreak);
8480 // Compute reflow status
8481 aStatus = contentLength == maxContentLength
8482 ? NS_FRAME_COMPLETE : NS_FRAME_NOT_COMPLETE;
8484 if (charsFit == 0 && length > 0 && !usedHyphenation) {
8485 // Couldn't place any text
8486 aStatus = NS_INLINE_LINE_BREAK_BEFORE();
8487 } else if (contentLength > 0 && mContentOffset + contentLength - 1 == newLineOffset) {
8488 // Ends in \n
8489 aStatus = NS_INLINE_LINE_BREAK_AFTER(aStatus);
8490 aLineLayout.SetLineEndsInBR(true);
8491 } else if (breakAfter) {
8492 aStatus = NS_INLINE_LINE_BREAK_AFTER(aStatus);
8494 if (completedFirstLetter) {
8495 aLineLayout.SetFirstLetterStyleOK(false);
8496 aStatus |= NS_INLINE_BREAK_FIRST_LETTER_COMPLETE;
8499 // Updated the cached NewlineProperty, or delete it.
8500 if (contentLength < maxContentLength &&
8501 textStyle->NewlineIsSignificant() &&
8502 (contentNewLineOffset < 0 ||
8503 mContentOffset + contentLength <= contentNewLineOffset)) {
8504 if (!cachedNewlineOffset) {
8505 cachedNewlineOffset = new NewlineProperty;
8506 if (NS_FAILED(mContent->SetProperty(nsGkAtoms::newline, cachedNewlineOffset,
8507 nsINode::DeleteProperty<NewlineProperty>))) {
8508 delete cachedNewlineOffset;
8509 cachedNewlineOffset = nullptr;
8512 if (cachedNewlineOffset) {
8513 cachedNewlineOffset->mStartOffset = offset;
8514 cachedNewlineOffset->mNewlineOffset = contentNewLineOffset;
8516 } else if (cachedNewlineOffset) {
8517 mContent->DeleteProperty(nsGkAtoms::newline);
8520 // Compute space and letter counts for justification, if required
8521 if (!textStyle->WhiteSpaceIsSignificant() &&
8522 (lineContainer->StyleText()->mTextAlign == NS_STYLE_TEXT_ALIGN_JUSTIFY ||
8523 lineContainer->StyleText()->mTextAlignLast == NS_STYLE_TEXT_ALIGN_JUSTIFY ||
8524 StyleContext()->IsInlineDescendantOfRuby()) &&
8525 !lineContainer->IsSVGText()) {
8526 AddStateBits(TEXT_JUSTIFICATION_ENABLED);
8527 provider.ComputeJustification(offset, charsFit);
8528 aLineLayout.SetJustificationInfo(provider.GetJustificationInfo());
8531 SetLength(contentLength, &aLineLayout, ALLOW_FRAME_CREATION_AND_DESTRUCTION);
8533 InvalidateFrame();
8535 #ifdef NOISY_REFLOW
8536 ListTag(stdout);
8537 printf(": desiredSize=%d,%d(b=%d) status=%x\n",
8538 aMetrics.Width(), aMetrics.Height(), aMetrics.BlockStartAscent(),
8539 aStatus);
8540 #endif
8543 /* virtual */ bool
8544 nsTextFrame::CanContinueTextRun() const
8546 // We can continue a text run through a text frame
8547 return true;
8550 nsTextFrame::TrimOutput
8551 nsTextFrame::TrimTrailingWhiteSpace(nsRenderingContext* aRC)
8553 TrimOutput result;
8554 result.mChanged = false;
8555 result.mDeltaWidth = 0;
8557 AddStateBits(TEXT_END_OF_LINE);
8559 int32_t contentLength = GetContentLength();
8560 if (!contentLength)
8561 return result;
8563 gfxContext* ctx = aRC->ThebesContext();
8564 gfxSkipCharsIterator start =
8565 EnsureTextRun(nsTextFrame::eInflated, ctx);
8566 NS_ENSURE_TRUE(mTextRun, result);
8568 uint32_t trimmedStart = start.GetSkippedOffset();
8570 const nsTextFragment* frag = mContent->GetText();
8571 TrimmedOffsets trimmed = GetTrimmedOffsets(frag, true);
8572 gfxSkipCharsIterator trimmedEndIter = start;
8573 const nsStyleText* textStyle = StyleText();
8574 gfxFloat delta = 0;
8575 uint32_t trimmedEnd = trimmedEndIter.ConvertOriginalToSkipped(trimmed.GetEnd());
8577 if (!(GetStateBits() & TEXT_TRIMMED_TRAILING_WHITESPACE) &&
8578 trimmed.GetEnd() < GetContentEnd()) {
8579 gfxSkipCharsIterator end = trimmedEndIter;
8580 uint32_t endOffset = end.ConvertOriginalToSkipped(GetContentOffset() + contentLength);
8581 if (trimmedEnd < endOffset) {
8582 // We can't be dealing with tabs here ... they wouldn't be trimmed. So it's
8583 // OK to pass null for the line container.
8584 PropertyProvider provider(mTextRun, textStyle, frag, this, start, contentLength,
8585 nullptr, 0, nsTextFrame::eInflated);
8586 delta = mTextRun->GetAdvanceWidth(trimmedEnd, endOffset - trimmedEnd, &provider);
8587 result.mChanged = true;
8591 gfxFloat advanceDelta;
8592 mTextRun->SetLineBreaks(trimmedStart, trimmedEnd - trimmedStart,
8593 (GetStateBits() & TEXT_START_OF_LINE) != 0, true,
8594 &advanceDelta, ctx);
8595 if (advanceDelta != 0) {
8596 result.mChanged = true;
8599 // aDeltaWidth is *subtracted* from our width.
8600 // If advanceDelta is positive then setting the line break made us longer,
8601 // so aDeltaWidth could go negative.
8602 result.mDeltaWidth = NSToCoordFloor(delta - advanceDelta);
8603 // If aDeltaWidth goes negative, that means this frame might not actually fit
8604 // anymore!!! We need higher level line layout to recover somehow.
8605 // If it's because the frame has a soft hyphen that is now being displayed,
8606 // this should actually be OK, because our reflow recorded the break
8607 // opportunity that allowed the soft hyphen to be used, and we wouldn't
8608 // have recorded the opportunity unless the hyphen fit (or was the first
8609 // opportunity on the line).
8610 // Otherwise this can/ really only happen when we have glyphs with special
8611 // shapes at the end of lines, I think. Breaking inside a kerning pair won't
8612 // do it because that would mean we broke inside this textrun, and
8613 // BreakAndMeasureText should make sure the resulting shaped substring fits.
8614 // Maybe if we passed a maxTextLength? But that only happens at direction
8615 // changes (so we wouldn't kern across the boundary) or for first-letter
8616 // (which always fits because it starts the line!).
8617 NS_WARN_IF_FALSE(result.mDeltaWidth >= 0,
8618 "Negative deltawidth, something odd is happening");
8620 #ifdef NOISY_TRIM
8621 ListTag(stdout);
8622 printf(": trim => %d\n", result.mDeltaWidth);
8623 #endif
8624 return result;
8627 nsOverflowAreas
8628 nsTextFrame::RecomputeOverflow(const nsHTMLReflowState& aBlockReflowState)
8630 nsRect bounds(nsPoint(0, 0), GetSize());
8631 nsOverflowAreas result(bounds, bounds);
8633 gfxSkipCharsIterator iter = EnsureTextRun(nsTextFrame::eInflated);
8634 if (!mTextRun)
8635 return result;
8637 PropertyProvider provider(this, iter, nsTextFrame::eInflated);
8638 provider.InitializeForDisplay(true);
8640 gfxTextRun::Metrics textMetrics =
8641 mTextRun->MeasureText(provider.GetStart().GetSkippedOffset(),
8642 ComputeTransformedLength(provider),
8643 gfxFont::LOOSE_INK_EXTENTS, nullptr,
8644 &provider);
8645 nsRect boundingBox = RoundOut(textMetrics.mBoundingBox);
8646 if (GetWritingMode().IsLineInverted()) {
8647 boundingBox.y = -boundingBox.YMost();
8649 boundingBox += nsPoint(0, mAscent);
8650 if (mTextRun->IsVertical()) {
8651 // Swap line-relative textMetrics dimensions to physical coordinates.
8652 Swap(boundingBox.x, boundingBox.y);
8653 Swap(boundingBox.width, boundingBox.height);
8655 nsRect &vis = result.VisualOverflow();
8656 vis.UnionRect(vis, boundingBox);
8657 UnionAdditionalOverflow(PresContext(), aBlockReflowState.frame, provider,
8658 &vis, true);
8659 return result;
8662 static char16_t TransformChar(const nsStyleText* aStyle, gfxTextRun* aTextRun,
8663 uint32_t aSkippedOffset, char16_t aChar)
8665 if (aChar == '\n') {
8666 return aStyle->NewlineIsSignificant() ? aChar : ' ';
8668 if (aChar == '\t') {
8669 return aStyle->TabIsSignificant() ? aChar : ' ';
8671 switch (aStyle->mTextTransform) {
8672 case NS_STYLE_TEXT_TRANSFORM_LOWERCASE:
8673 aChar = ToLowerCase(aChar);
8674 break;
8675 case NS_STYLE_TEXT_TRANSFORM_UPPERCASE:
8676 aChar = ToUpperCase(aChar);
8677 break;
8678 case NS_STYLE_TEXT_TRANSFORM_CAPITALIZE:
8679 if (aTextRun->CanBreakLineBefore(aSkippedOffset)) {
8680 aChar = ToTitleCase(aChar);
8682 break;
8685 return aChar;
8688 nsresult nsTextFrame::GetRenderedText(nsAString* aAppendToString,
8689 gfxSkipChars* aSkipChars,
8690 gfxSkipCharsIterator* aSkipIter,
8691 uint32_t aSkippedStartOffset,
8692 uint32_t aSkippedMaxLength)
8694 // The handling of aSkippedStartOffset and aSkippedMaxLength could be more efficient...
8695 gfxSkipChars skipChars;
8696 nsTextFrame* textFrame;
8697 const nsTextFragment* textFrag = mContent->GetText();
8698 uint32_t keptCharsLength = 0;
8699 uint32_t validCharsLength = 0;
8701 // Build skipChars and copy text, for each text frame in this continuation block
8702 for (textFrame = this; textFrame;
8703 textFrame = static_cast<nsTextFrame*>(textFrame->GetNextContinuation())) {
8704 // For each text frame continuation in this block ...
8706 if (textFrame->GetStateBits() & NS_FRAME_IS_DIRTY) {
8707 // We don't trust dirty frames, expecially when computing rendered text.
8708 break;
8711 // Ensure the text run and grab the gfxSkipCharsIterator for it
8712 gfxSkipCharsIterator iter =
8713 textFrame->EnsureTextRun(nsTextFrame::eInflated);
8714 if (!textFrame->mTextRun)
8715 return NS_ERROR_FAILURE;
8717 // Skip to the start of the text run, past ignored chars at start of line
8718 // XXX In the future we may decide to trim extra spaces before a hard line
8719 // break, in which case we need to accurately detect those sitations and
8720 // call GetTrimmedOffsets() with true to trim whitespace at the line's end
8721 TrimmedOffsets trimmedContentOffsets = textFrame->GetTrimmedOffsets(textFrag, false);
8722 int32_t startOfLineSkipChars = trimmedContentOffsets.mStart - textFrame->mContentOffset;
8723 if (startOfLineSkipChars > 0) {
8724 skipChars.SkipChars(startOfLineSkipChars);
8725 iter.SetOriginalOffset(trimmedContentOffsets.mStart);
8728 // Keep and copy the appropriate chars withing the caller's requested range
8729 const nsStyleText* textStyle = textFrame->StyleText();
8730 while (iter.GetOriginalOffset() < trimmedContentOffsets.GetEnd() &&
8731 keptCharsLength < aSkippedMaxLength) {
8732 // For each original char from content text
8733 if (iter.IsOriginalCharSkipped() || ++validCharsLength <= aSkippedStartOffset) {
8734 skipChars.SkipChar();
8735 } else {
8736 ++keptCharsLength;
8737 skipChars.KeepChar();
8738 if (aAppendToString) {
8739 aAppendToString->Append(
8740 TransformChar(textStyle, textFrame->mTextRun, iter.GetSkippedOffset(),
8741 textFrag->CharAt(iter.GetOriginalOffset())));
8744 iter.AdvanceOriginal(1);
8746 if (keptCharsLength >= aSkippedMaxLength) {
8747 break; // Already past the end, don't build string or gfxSkipCharsIter anymore
8751 if (aSkipChars) {
8752 aSkipChars->TakeFrom(&skipChars); // Copy skipChars into aSkipChars
8753 if (aSkipIter) {
8754 // Caller must provide both pointers in order to retrieve a gfxSkipCharsIterator,
8755 // because the gfxSkipCharsIterator holds a weak pointer to the gfxSkipChars.
8756 *aSkipIter = gfxSkipCharsIterator(*aSkipChars, GetContentLength());
8760 return NS_OK;
8763 nsIAtom*
8764 nsTextFrame::GetType() const
8766 return nsGkAtoms::textFrame;
8769 /* virtual */ bool
8770 nsTextFrame::IsEmpty()
8772 NS_ASSERTION(!(mState & TEXT_IS_ONLY_WHITESPACE) ||
8773 !(mState & TEXT_ISNOT_ONLY_WHITESPACE),
8774 "Invalid state");
8776 // XXXldb Should this check compatibility mode as well???
8777 const nsStyleText* textStyle = StyleText();
8778 if (textStyle->WhiteSpaceIsSignificant()) {
8779 // XXX shouldn't we return true if the length is zero?
8780 return false;
8783 if (mState & TEXT_ISNOT_ONLY_WHITESPACE) {
8784 return false;
8787 if (mState & TEXT_IS_ONLY_WHITESPACE) {
8788 return true;
8791 bool isEmpty =
8792 IsAllWhitespace(mContent->GetText(),
8793 textStyle->mWhiteSpace != NS_STYLE_WHITESPACE_PRE_LINE);
8794 mState |= (isEmpty ? TEXT_IS_ONLY_WHITESPACE : TEXT_ISNOT_ONLY_WHITESPACE);
8795 return isEmpty;
8798 #ifdef DEBUG_FRAME_DUMP
8799 // Translate the mapped content into a string that's printable
8800 void
8801 nsTextFrame::ToCString(nsCString& aBuf, int32_t* aTotalContentLength) const
8803 // Get the frames text content
8804 const nsTextFragment* frag = mContent->GetText();
8805 if (!frag) {
8806 return;
8809 // Compute the total length of the text content.
8810 *aTotalContentLength = frag->GetLength();
8812 int32_t contentLength = GetContentLength();
8813 // Set current fragment and current fragment offset
8814 if (0 == contentLength) {
8815 return;
8817 int32_t fragOffset = GetContentOffset();
8818 int32_t n = fragOffset + contentLength;
8819 while (fragOffset < n) {
8820 char16_t ch = frag->CharAt(fragOffset++);
8821 if (ch == '\r') {
8822 aBuf.AppendLiteral("\\r");
8823 } else if (ch == '\n') {
8824 aBuf.AppendLiteral("\\n");
8825 } else if (ch == '\t') {
8826 aBuf.AppendLiteral("\\t");
8827 } else if ((ch < ' ') || (ch >= 127)) {
8828 aBuf.Append(nsPrintfCString("\\u%04x", ch));
8829 } else {
8830 aBuf.Append(ch);
8835 nsresult
8836 nsTextFrame::GetFrameName(nsAString& aResult) const
8838 MakeFrameName(NS_LITERAL_STRING("Text"), aResult);
8839 int32_t totalContentLength;
8840 nsAutoCString tmp;
8841 ToCString(tmp, &totalContentLength);
8842 tmp.SetLength(std::min(tmp.Length(), 50u));
8843 aResult += NS_LITERAL_STRING("\"") + NS_ConvertASCIItoUTF16(tmp) + NS_LITERAL_STRING("\"");
8844 return NS_OK;
8847 void
8848 nsTextFrame::List(FILE* out, const char* aPrefix, uint32_t aFlags) const
8850 nsCString str;
8851 ListGeneric(str, aPrefix, aFlags);
8853 str += nsPrintfCString(" [run=%p]", static_cast<void*>(mTextRun));
8855 // Output the first/last content offset and prev/next in flow info
8856 bool isComplete = uint32_t(GetContentEnd()) == GetContent()->TextLength();
8857 str += nsPrintfCString("[%d,%d,%c] ", GetContentOffset(), GetContentLength(),
8858 isComplete ? 'T':'F');
8860 if (IsSelected()) {
8861 str += " SELECTED";
8863 fprintf_stderr(out, "%s\n", str.get());
8865 #endif
8867 #ifdef DEBUG
8868 nsFrameState
8869 nsTextFrame::GetDebugStateBits() const
8871 // mask out our emptystate flags; those are just caches
8872 return nsFrame::GetDebugStateBits() &
8873 ~(TEXT_WHITESPACE_FLAGS | TEXT_REFLOW_FLAGS);
8875 #endif
8877 void
8878 nsTextFrame::AdjustOffsetsForBidi(int32_t aStart, int32_t aEnd)
8880 AddStateBits(NS_FRAME_IS_BIDI);
8881 mContent->DeleteProperty(nsGkAtoms::flowlength);
8884 * After Bidi resolution we may need to reassign text runs.
8885 * This is called during bidi resolution from the block container, so we
8886 * shouldn't be holding a local reference to a textrun anywhere.
8888 ClearTextRuns();
8890 nsTextFrame* prev = static_cast<nsTextFrame*>(GetPrevContinuation());
8891 if (prev) {
8892 // the bidi resolver can be very evil when columns/pages are involved. Don't
8893 // let it violate our invariants.
8894 int32_t prevOffset = prev->GetContentOffset();
8895 aStart = std::max(aStart, prevOffset);
8896 aEnd = std::max(aEnd, prevOffset);
8897 prev->ClearTextRuns();
8900 mContentOffset = aStart;
8901 SetLength(aEnd - aStart, nullptr, 0);
8904 * After inserting text the caret Bidi level must be set to the level of the
8905 * inserted text.This is difficult, because we cannot know what the level is
8906 * until after the Bidi algorithm is applied to the whole paragraph.
8908 * So we set the caret Bidi level to UNDEFINED here, and the caret code will
8909 * set it correctly later
8911 nsRefPtr<nsFrameSelection> frameSelection = GetFrameSelection();
8912 if (frameSelection) {
8913 frameSelection->UndefineCaretBidiLevel();
8918 * @return true if this text frame ends with a newline character. It should return
8919 * false if it is not a text frame.
8921 bool
8922 nsTextFrame::HasSignificantTerminalNewline() const
8924 return ::HasTerminalNewline(this) && StyleText()->NewlineIsSignificant();
8927 bool
8928 nsTextFrame::IsAtEndOfLine() const
8930 return (GetStateBits() & TEXT_END_OF_LINE) != 0;
8933 nscoord
8934 nsTextFrame::GetLogicalBaseline(WritingMode aWritingMode ) const
8936 return mAscent;
8939 bool
8940 nsTextFrame::HasAnyNoncollapsedCharacters()
8942 gfxSkipCharsIterator iter = EnsureTextRun(nsTextFrame::eInflated);
8943 int32_t offset = GetContentOffset(),
8944 offsetEnd = GetContentEnd();
8945 int32_t skippedOffset = iter.ConvertOriginalToSkipped(offset);
8946 int32_t skippedOffsetEnd = iter.ConvertOriginalToSkipped(offsetEnd);
8947 return skippedOffset != skippedOffsetEnd;
8950 bool
8951 nsTextFrame::UpdateOverflow()
8953 const nsRect rect(nsPoint(0, 0), GetSize());
8954 nsOverflowAreas overflowAreas(rect, rect);
8956 if (GetStateBits() & NS_FRAME_FIRST_REFLOW) {
8957 return false;
8959 gfxSkipCharsIterator iter = EnsureTextRun(nsTextFrame::eInflated);
8960 if (!mTextRun) {
8961 return false;
8963 PropertyProvider provider(this, iter, nsTextFrame::eInflated);
8964 provider.InitializeForDisplay(true);
8966 nsIFrame* decorationsBlock;
8967 if (IsFloatingFirstLetterChild()) {
8968 decorationsBlock = GetParent();
8969 } else {
8970 nsIFrame* f = this;
8971 for (;;) {
8972 nsBlockFrame* fBlock = nsLayoutUtils::GetAsBlock(f);
8973 if (fBlock) {
8974 decorationsBlock = fBlock;
8975 break;
8978 f = f->GetParent();
8979 if (!f) {
8980 NS_ERROR("Couldn't find any block ancestor (for text decorations)");
8981 return false;
8986 UnionAdditionalOverflow(PresContext(), decorationsBlock, provider,
8987 &overflowAreas.VisualOverflow(), true);
8988 return FinishAndStoreOverflow(overflowAreas, GetSize());
8991 void
8992 nsTextFrame::AssignJustificationGaps(
8993 const mozilla::JustificationAssignment& aAssign)
8995 int32_t encoded = (aAssign.mGapsAtStart << 8) | aAssign.mGapsAtEnd;
8996 static_assert(sizeof(aAssign) == 1,
8997 "The encoding might be broken if JustificationAssignment "
8998 "is larger than 1 byte");
8999 Properties().Set(JustificationAssignment(), NS_INT32_TO_PTR(encoded));
9002 mozilla::JustificationAssignment
9003 nsTextFrame::GetJustificationAssignment() const
9005 int32_t encoded =
9006 NS_PTR_TO_INT32(Properties().Get(JustificationAssignment()));
9007 mozilla::JustificationAssignment result;
9008 result.mGapsAtStart = encoded >> 8;
9009 result.mGapsAtEnd = encoded & 0xFF;
9010 return result;