michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: /* rendering object for textual content of elements */ michael@0: michael@0: #include "nsTextFrame.h" michael@0: michael@0: #include "mozilla/Attributes.h" michael@0: #include "mozilla/DebugOnly.h" michael@0: #include "mozilla/Likely.h" michael@0: #include "mozilla/MathAlgorithms.h" michael@0: #include "mozilla/TextEvents.h" michael@0: michael@0: #include "nsCOMPtr.h" michael@0: #include "nsBlockFrame.h" michael@0: #include "nsCRT.h" michael@0: #include "nsSplittableFrame.h" michael@0: #include "nsLineLayout.h" michael@0: #include "nsString.h" michael@0: #include "nsUnicharUtils.h" michael@0: #include "nsPresContext.h" michael@0: #include "nsIContent.h" michael@0: #include "nsStyleConsts.h" michael@0: #include "nsStyleContext.h" michael@0: #include "nsStyleStruct.h" michael@0: #include "nsStyleStructInlines.h" michael@0: #include "SVGTextFrame.h" michael@0: #include "nsCoord.h" michael@0: #include "nsRenderingContext.h" michael@0: #include "nsIPresShell.h" michael@0: #include "nsTArray.h" michael@0: #include "nsCSSPseudoElements.h" michael@0: #include "nsCSSFrameConstructor.h" michael@0: #include "nsCompatibility.h" michael@0: #include "nsCSSColorUtils.h" michael@0: #include "nsLayoutUtils.h" michael@0: #include "nsDisplayList.h" michael@0: #include "nsFrame.h" michael@0: #include "nsIMathMLFrame.h" michael@0: #include "nsPlaceholderFrame.h" michael@0: #include "nsTextFrameUtils.h" michael@0: #include "nsTextRunTransformations.h" michael@0: #include "MathMLTextRunFactory.h" michael@0: #include "nsExpirationTracker.h" michael@0: #include "nsUnicodeProperties.h" michael@0: michael@0: #include "nsTextFragment.h" michael@0: #include "nsGkAtoms.h" michael@0: #include "nsFrameSelection.h" michael@0: #include "nsRange.h" michael@0: #include "nsCSSRendering.h" michael@0: #include "nsContentUtils.h" michael@0: #include "nsLineBreaker.h" michael@0: #include "nsIWordBreaker.h" michael@0: #include "nsGenericDOMDataNode.h" michael@0: #include "nsIFrameInlines.h" michael@0: michael@0: #include michael@0: #ifdef ACCESSIBILITY michael@0: #include "nsAccessibilityService.h" michael@0: #endif michael@0: #include "nsAutoPtr.h" michael@0: michael@0: #include "nsPrintfCString.h" michael@0: michael@0: #include "gfxFont.h" michael@0: #include "gfxContext.h" michael@0: michael@0: #include "mozilla/dom/Element.h" michael@0: #include "mozilla/LookAndFeel.h" michael@0: michael@0: #include "GeckoProfiler.h" michael@0: michael@0: #ifdef DEBUG michael@0: #undef NOISY_REFLOW michael@0: #undef NOISY_TRIM michael@0: #else michael@0: #undef NOISY_REFLOW michael@0: #undef NOISY_TRIM michael@0: #endif michael@0: michael@0: #ifdef DrawText michael@0: #undef DrawText michael@0: #endif michael@0: michael@0: using namespace mozilla; michael@0: using namespace mozilla::dom; michael@0: michael@0: struct TabWidth { michael@0: TabWidth(uint32_t aOffset, uint32_t aWidth) michael@0: : mOffset(aOffset), mWidth(float(aWidth)) michael@0: { } michael@0: michael@0: uint32_t mOffset; // DOM offset relative to the current frame's offset. michael@0: float mWidth; // extra space to be added at this position (in app units) michael@0: }; michael@0: michael@0: struct TabWidthStore { michael@0: TabWidthStore(int32_t aValidForContentOffset) michael@0: : mLimit(0) michael@0: , mValidForContentOffset(aValidForContentOffset) michael@0: { } michael@0: michael@0: // Apply tab widths to the aSpacing array, which corresponds to characters michael@0: // beginning at aOffset and has length aLength. (Width records outside this michael@0: // range will be ignored.) michael@0: void ApplySpacing(gfxTextRun::PropertyProvider::Spacing *aSpacing, michael@0: uint32_t aOffset, uint32_t aLength); michael@0: michael@0: // Offset up to which tabs have been measured; positions beyond this have not michael@0: // been calculated yet but may be appended if needed later. It's a DOM michael@0: // offset relative to the current frame's offset. michael@0: uint32_t mLimit; michael@0: michael@0: // Need to recalc tab offsets if frame content offset differs from this. michael@0: int32_t mValidForContentOffset; michael@0: michael@0: // A TabWidth record for each tab character measured so far. michael@0: nsTArray mWidths; michael@0: }; michael@0: michael@0: void michael@0: TabWidthStore::ApplySpacing(gfxTextRun::PropertyProvider::Spacing *aSpacing, michael@0: uint32_t aOffset, uint32_t aLength) michael@0: { michael@0: uint32_t i = 0, len = mWidths.Length(); michael@0: michael@0: // If aOffset is non-zero, do a binary search to find where to start michael@0: // processing the tab widths, in case the list is really long. (See bug michael@0: // 953247.) michael@0: // We need to start from the first entry where mOffset >= aOffset. michael@0: if (aOffset > 0) { michael@0: uint32_t lo = 0, hi = len; michael@0: while (lo < hi) { michael@0: i = (lo + hi) / 2; michael@0: const TabWidth& tw = mWidths[i]; michael@0: if (tw.mOffset < aOffset) { michael@0: // mWidths[i] precedes the target range; new search range michael@0: // will be [i+1, hi) michael@0: lo = ++i; michael@0: continue; michael@0: } michael@0: if (tw.mOffset > aOffset) { michael@0: // mWidths[i] is within (or beyond) the target range; michael@0: // new search range is [lo, i). If it turns out that michael@0: // mWidths[i] was the first entry within the range, michael@0: // we'll never move hi any further, and end up exiting michael@0: // when i == lo == this value of hi. michael@0: hi = i; michael@0: continue; michael@0: } michael@0: // Found an exact match for aOffset, so end search now michael@0: break; michael@0: } michael@0: } michael@0: michael@0: uint32_t limit = aOffset + aLength; michael@0: while (i < len) { michael@0: const TabWidth& tw = mWidths[i]; michael@0: if (tw.mOffset >= limit) { michael@0: break; michael@0: } michael@0: aSpacing[tw.mOffset - aOffset].mAfter += tw.mWidth; michael@0: i++; michael@0: } michael@0: } michael@0: michael@0: static void DestroyTabWidth(void* aPropertyValue) michael@0: { michael@0: delete static_cast(aPropertyValue); michael@0: } michael@0: michael@0: NS_DECLARE_FRAME_PROPERTY(TabWidthProperty, DestroyTabWidth) michael@0: michael@0: NS_DECLARE_FRAME_PROPERTY(OffsetToFrameProperty, nullptr) michael@0: michael@0: // text runs are destroyed by the text run cache michael@0: NS_DECLARE_FRAME_PROPERTY(UninflatedTextRunProperty, nullptr) michael@0: michael@0: NS_DECLARE_FRAME_PROPERTY(FontSizeInflationProperty, nullptr) michael@0: michael@0: class GlyphObserver : public gfxFont::GlyphChangeObserver { michael@0: public: michael@0: GlyphObserver(gfxFont* aFont, nsTextFrame* aFrame) michael@0: : gfxFont::GlyphChangeObserver(aFont), mFrame(aFrame) {} michael@0: virtual void NotifyGlyphsChanged() MOZ_OVERRIDE; michael@0: private: michael@0: nsTextFrame* mFrame; michael@0: }; michael@0: michael@0: static void DestroyGlyphObserverList(void* aPropertyValue) michael@0: { michael@0: delete static_cast >*>(aPropertyValue); michael@0: } michael@0: michael@0: /** michael@0: * This property is set on text frames with TEXT_IN_TEXTRUN_USER_DATA set that michael@0: * have potentially-animated glyphs. michael@0: * The only reason this list is in a property is to automatically destroy the michael@0: * list when the frame is deleted, unregistering the observers. michael@0: */ michael@0: NS_DECLARE_FRAME_PROPERTY(TextFrameGlyphObservers, DestroyGlyphObserverList); michael@0: michael@0: #define TEXT_REFLOW_FLAGS \ michael@0: (TEXT_FIRST_LETTER|TEXT_START_OF_LINE|TEXT_END_OF_LINE|TEXT_HYPHEN_BREAK| \ michael@0: TEXT_TRIMMED_TRAILING_WHITESPACE|TEXT_JUSTIFICATION_ENABLED| \ michael@0: TEXT_HAS_NONCOLLAPSED_CHARACTERS|TEXT_SELECTION_UNDERLINE_OVERFLOWED) michael@0: michael@0: #define TEXT_WHITESPACE_FLAGS (TEXT_IS_ONLY_WHITESPACE | \ michael@0: TEXT_ISNOT_ONLY_WHITESPACE) michael@0: michael@0: /* michael@0: * Some general notes michael@0: * michael@0: * Text frames delegate work to gfxTextRun objects. The gfxTextRun object michael@0: * transforms text to positioned glyphs. It can report the geometry of the michael@0: * glyphs and paint them. Text frames configure gfxTextRuns by providing text, michael@0: * spacing, language, and other information. michael@0: * michael@0: * A gfxTextRun can cover more than one DOM text node. This is necessary to michael@0: * get kerning, ligatures and shaping for text that spans multiple text nodes michael@0: * but is all the same font. The userdata for a gfxTextRun object is a michael@0: * TextRunUserData* or an nsIFrame*. michael@0: * michael@0: * We go to considerable effort to make sure things work even if in-flow michael@0: * siblings have different style contexts (i.e., first-letter and first-line). michael@0: * michael@0: * Our convention is that unsigned integer character offsets are offsets into michael@0: * the transformed string. Signed integer character offsets are offsets into michael@0: * the DOM string. michael@0: * michael@0: * XXX currently we don't handle hyphenated breaks between text frames where the michael@0: * hyphen occurs at the end of the first text frame, e.g. michael@0: * Kit­ty michael@0: */ michael@0: michael@0: /** michael@0: * We use an array of these objects to record which text frames michael@0: * are associated with the textrun. mStartFrame is the start of a list of michael@0: * text frames. Some sequence of its continuations are covered by the textrun. michael@0: * A content textnode can have at most one TextRunMappedFlow associated with it michael@0: * for a given textrun. michael@0: * michael@0: * mDOMOffsetToBeforeTransformOffset is added to DOM offsets for those frames to obtain michael@0: * the offset into the before-transformation text of the textrun. It can be michael@0: * positive (when a text node starts in the middle of a text run) or michael@0: * negative (when a text run starts in the middle of a text node). Of course michael@0: * it can also be zero. michael@0: */ michael@0: struct TextRunMappedFlow { michael@0: nsTextFrame* mStartFrame; michael@0: int32_t mDOMOffsetToBeforeTransformOffset; michael@0: // The text mapped starts at mStartFrame->GetContentOffset() and is this long michael@0: uint32_t mContentLength; michael@0: }; michael@0: michael@0: /** michael@0: * This is our user data for the textrun, when textRun->GetFlags() does not michael@0: * have TEXT_IS_SIMPLE_FLOW set. When TEXT_IS_SIMPLE_FLOW is set, there is michael@0: * just one flow, the textrun's user data pointer is a pointer to mStartFrame michael@0: * for that flow, mDOMOffsetToBeforeTransformOffset is zero, and mContentLength michael@0: * is the length of the text node. michael@0: */ michael@0: struct TextRunUserData { michael@0: TextRunMappedFlow* mMappedFlows; michael@0: uint32_t mMappedFlowCount; michael@0: uint32_t mLastFlowIndex; michael@0: }; michael@0: michael@0: /** michael@0: * This helper object computes colors used for painting, and also IME michael@0: * underline information. The data is computed lazily and cached as necessary. michael@0: * These live for just the duration of one paint operation. michael@0: */ michael@0: class nsTextPaintStyle { michael@0: public: michael@0: nsTextPaintStyle(nsTextFrame* aFrame); michael@0: michael@0: void SetResolveColors(bool aResolveColors) { michael@0: NS_ASSERTION(mFrame->IsSVGText() || aResolveColors, michael@0: "must resolve colors is frame is not for SVG text"); michael@0: mResolveColors = aResolveColors; michael@0: } michael@0: michael@0: nscolor GetTextColor(); michael@0: /** michael@0: * Compute the colors for normally-selected text. Returns false if michael@0: * the normal selection is not being displayed. michael@0: */ michael@0: bool GetSelectionColors(nscolor* aForeColor, michael@0: nscolor* aBackColor); michael@0: void GetHighlightColors(nscolor* aForeColor, michael@0: nscolor* aBackColor); michael@0: void GetURLSecondaryColor(nscolor* aForeColor); michael@0: void GetIMESelectionColors(int32_t aIndex, michael@0: nscolor* aForeColor, michael@0: nscolor* aBackColor); michael@0: // if this returns false, we don't need to draw underline. michael@0: bool GetSelectionUnderlineForPaint(int32_t aIndex, michael@0: nscolor* aLineColor, michael@0: float* aRelativeSize, michael@0: uint8_t* aStyle); michael@0: michael@0: // if this returns false, we don't need to draw underline. michael@0: static bool GetSelectionUnderline(nsPresContext* aPresContext, michael@0: int32_t aIndex, michael@0: nscolor* aLineColor, michael@0: float* aRelativeSize, michael@0: uint8_t* aStyle); michael@0: michael@0: // if this returns false, no text-shadow was specified for the selection michael@0: // and the *aShadow parameter was not modified. michael@0: bool GetSelectionShadow(nsCSSShadowArray** aShadow); michael@0: michael@0: nsPresContext* PresContext() const { return mPresContext; } michael@0: michael@0: enum { michael@0: eIndexRawInput = 0, michael@0: eIndexSelRawText, michael@0: eIndexConvText, michael@0: eIndexSelConvText, michael@0: eIndexSpellChecker michael@0: }; michael@0: michael@0: static int32_t GetUnderlineStyleIndexForSelectionType(int32_t aSelectionType) michael@0: { michael@0: switch (aSelectionType) { michael@0: case nsISelectionController::SELECTION_IME_RAWINPUT: michael@0: return eIndexRawInput; michael@0: case nsISelectionController::SELECTION_IME_SELECTEDRAWTEXT: michael@0: return eIndexSelRawText; michael@0: case nsISelectionController::SELECTION_IME_CONVERTEDTEXT: michael@0: return eIndexConvText; michael@0: case nsISelectionController::SELECTION_IME_SELECTEDCONVERTEDTEXT: michael@0: return eIndexSelConvText; michael@0: case nsISelectionController::SELECTION_SPELLCHECK: michael@0: return eIndexSpellChecker; michael@0: default: michael@0: NS_WARNING("non-IME selection type"); michael@0: return eIndexRawInput; michael@0: } michael@0: } michael@0: michael@0: protected: michael@0: nsTextFrame* mFrame; michael@0: nsPresContext* mPresContext; michael@0: bool mInitCommonColors; michael@0: bool mInitSelectionColorsAndShadow; michael@0: bool mResolveColors; michael@0: michael@0: // Selection data michael@0: michael@0: int16_t mSelectionStatus; // see nsIDocument.h SetDisplaySelection() michael@0: nscolor mSelectionTextColor; michael@0: nscolor mSelectionBGColor; michael@0: nsRefPtr mSelectionShadow; michael@0: bool mHasSelectionShadow; michael@0: michael@0: // Common data michael@0: michael@0: int32_t mSufficientContrast; michael@0: nscolor mFrameBackgroundColor; michael@0: michael@0: // selection colors and underline info, the colors are resolved colors if michael@0: // mResolveColors is true (which is the default), i.e., the foreground color michael@0: // and background color are swapped if it's needed. And also line color will michael@0: // be resolved from them. michael@0: struct nsSelectionStyle { michael@0: bool mInit; michael@0: nscolor mTextColor; michael@0: nscolor mBGColor; michael@0: nscolor mUnderlineColor; michael@0: uint8_t mUnderlineStyle; michael@0: float mUnderlineRelativeSize; michael@0: }; michael@0: nsSelectionStyle mSelectionStyle[5]; michael@0: michael@0: // Color initializations michael@0: void InitCommonColors(); michael@0: bool InitSelectionColorsAndShadow(); michael@0: michael@0: nsSelectionStyle* GetSelectionStyle(int32_t aIndex); michael@0: void InitSelectionStyle(int32_t aIndex); michael@0: michael@0: bool EnsureSufficientContrast(nscolor *aForeColor, nscolor *aBackColor); michael@0: michael@0: nscolor GetResolvedForeColor(nscolor aColor, nscolor aDefaultForeColor, michael@0: nscolor aBackColor); michael@0: }; michael@0: michael@0: static void michael@0: DestroyUserData(void* aUserData) michael@0: { michael@0: TextRunUserData* userData = static_cast(aUserData); michael@0: if (userData) { michael@0: nsMemory::Free(userData); michael@0: } michael@0: } michael@0: michael@0: /** michael@0: * Remove |aTextRun| from the frame continuation chain starting at michael@0: * |aStartContinuation| if non-null, otherwise starting at |aFrame|. michael@0: * Unmark |aFrame| as a text run owner if it's the frame we start at. michael@0: * Return true if |aStartContinuation| is non-null and was found michael@0: * in the next-continuation chain of |aFrame|. michael@0: */ michael@0: static bool michael@0: ClearAllTextRunReferences(nsTextFrame* aFrame, gfxTextRun* aTextRun, michael@0: nsTextFrame* aStartContinuation, michael@0: nsFrameState aWhichTextRunState) michael@0: { michael@0: NS_PRECONDITION(aFrame, ""); michael@0: NS_PRECONDITION(!aStartContinuation || michael@0: (!aStartContinuation->GetTextRun(nsTextFrame::eInflated) || michael@0: aStartContinuation->GetTextRun(nsTextFrame::eInflated) == aTextRun) || michael@0: (!aStartContinuation->GetTextRun(nsTextFrame::eNotInflated) || michael@0: aStartContinuation->GetTextRun(nsTextFrame::eNotInflated) == aTextRun), michael@0: "wrong aStartContinuation for this text run"); michael@0: michael@0: if (!aStartContinuation || aStartContinuation == aFrame) { michael@0: aFrame->RemoveStateBits(aWhichTextRunState); michael@0: } else { michael@0: do { michael@0: NS_ASSERTION(aFrame->GetType() == nsGkAtoms::textFrame, "Bad frame"); michael@0: aFrame = static_cast(aFrame->GetNextContinuation()); michael@0: } while (aFrame && aFrame != aStartContinuation); michael@0: } michael@0: bool found = aStartContinuation == aFrame; michael@0: while (aFrame) { michael@0: NS_ASSERTION(aFrame->GetType() == nsGkAtoms::textFrame, "Bad frame"); michael@0: if (!aFrame->RemoveTextRun(aTextRun)) { michael@0: break; michael@0: } michael@0: aFrame = static_cast(aFrame->GetNextContinuation()); michael@0: } michael@0: NS_POSTCONDITION(!found || aStartContinuation, "how did we find null?"); michael@0: return found; michael@0: } michael@0: michael@0: /** michael@0: * Kill all references to |aTextRun| starting at |aStartContinuation|. michael@0: * It could be referenced by any of its owners, and all their in-flows. michael@0: * If |aStartContinuation| is null then process all userdata frames michael@0: * and their continuations. michael@0: * @note the caller is expected to take care of possibly destroying the michael@0: * text run if all userdata frames were reset (userdata is deallocated michael@0: * by this function though). The caller can detect this has occured by michael@0: * checking |aTextRun->GetUserData() == nullptr|. michael@0: */ michael@0: static void michael@0: UnhookTextRunFromFrames(gfxTextRun* aTextRun, nsTextFrame* aStartContinuation) michael@0: { michael@0: if (!aTextRun->GetUserData()) michael@0: return; michael@0: michael@0: if (aTextRun->GetFlags() & nsTextFrameUtils::TEXT_IS_SIMPLE_FLOW) { michael@0: nsTextFrame* userDataFrame = static_cast( michael@0: static_cast(aTextRun->GetUserData())); michael@0: nsFrameState whichTextRunState = michael@0: userDataFrame->GetTextRun(nsTextFrame::eInflated) == aTextRun michael@0: ? TEXT_IN_TEXTRUN_USER_DATA michael@0: : TEXT_IN_UNINFLATED_TEXTRUN_USER_DATA; michael@0: DebugOnly found = michael@0: ClearAllTextRunReferences(userDataFrame, aTextRun, michael@0: aStartContinuation, whichTextRunState); michael@0: NS_ASSERTION(!aStartContinuation || found, michael@0: "aStartContinuation wasn't found in simple flow text run"); michael@0: if (!(userDataFrame->GetStateBits() & whichTextRunState)) { michael@0: aTextRun->SetUserData(nullptr); michael@0: } michael@0: } else { michael@0: TextRunUserData* userData = michael@0: static_cast(aTextRun->GetUserData()); michael@0: int32_t destroyFromIndex = aStartContinuation ? -1 : 0; michael@0: for (uint32_t i = 0; i < userData->mMappedFlowCount; ++i) { michael@0: nsTextFrame* userDataFrame = userData->mMappedFlows[i].mStartFrame; michael@0: nsFrameState whichTextRunState = michael@0: userDataFrame->GetTextRun(nsTextFrame::eInflated) == aTextRun michael@0: ? TEXT_IN_TEXTRUN_USER_DATA michael@0: : TEXT_IN_UNINFLATED_TEXTRUN_USER_DATA; michael@0: bool found = michael@0: ClearAllTextRunReferences(userDataFrame, aTextRun, michael@0: aStartContinuation, whichTextRunState); michael@0: if (found) { michael@0: if (userDataFrame->GetStateBits() & whichTextRunState) { michael@0: destroyFromIndex = i + 1; michael@0: } michael@0: else { michael@0: destroyFromIndex = i; michael@0: } michael@0: aStartContinuation = nullptr; michael@0: } michael@0: } michael@0: NS_ASSERTION(destroyFromIndex >= 0, michael@0: "aStartContinuation wasn't found in multi flow text run"); michael@0: if (destroyFromIndex == 0) { michael@0: DestroyUserData(userData); michael@0: aTextRun->SetUserData(nullptr); michael@0: } michael@0: else { michael@0: userData->mMappedFlowCount = uint32_t(destroyFromIndex); michael@0: if (userData->mLastFlowIndex >= uint32_t(destroyFromIndex)) { michael@0: userData->mLastFlowIndex = uint32_t(destroyFromIndex) - 1; michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: void michael@0: GlyphObserver::NotifyGlyphsChanged() michael@0: { michael@0: nsIPresShell* shell = mFrame->PresContext()->PresShell(); michael@0: for (nsIFrame* f = mFrame; f; michael@0: f = nsLayoutUtils::GetNextContinuationOrIBSplitSibling(f)) { michael@0: if (f != mFrame && f->HasAnyStateBits(TEXT_IN_TEXTRUN_USER_DATA)) { michael@0: // f will have its own GlyphObserver (if needed) so we can stop here. michael@0: break; michael@0: } michael@0: f->InvalidateFrame(); michael@0: // Theoretically we could just update overflow areas, perhaps using michael@0: // OverflowChangedTracker, but that would do a bunch of work eagerly that michael@0: // we should probably do lazily here since there could be a lot michael@0: // of text frames affected and we'd like to coalesce the work. So that's michael@0: // not easy to do well. michael@0: shell->FrameNeedsReflow(f, nsIPresShell::eResize, NS_FRAME_IS_DIRTY); michael@0: } michael@0: } michael@0: michael@0: class FrameTextRunCache; michael@0: michael@0: static FrameTextRunCache *gTextRuns = nullptr; michael@0: michael@0: /* michael@0: * Cache textruns and expire them after 3*10 seconds of no use. michael@0: */ michael@0: class FrameTextRunCache MOZ_FINAL : public nsExpirationTracker { michael@0: public: michael@0: enum { TIMEOUT_SECONDS = 10 }; michael@0: FrameTextRunCache() michael@0: : nsExpirationTracker(TIMEOUT_SECONDS*1000) {} michael@0: ~FrameTextRunCache() { michael@0: AgeAllGenerations(); michael@0: } michael@0: michael@0: void RemoveFromCache(gfxTextRun* aTextRun) { michael@0: if (aTextRun->GetExpirationState()->IsTracked()) { michael@0: RemoveObject(aTextRun); michael@0: } michael@0: } michael@0: michael@0: // This gets called when the timeout has expired on a gfxTextRun michael@0: virtual void NotifyExpired(gfxTextRun* aTextRun) { michael@0: UnhookTextRunFromFrames(aTextRun, nullptr); michael@0: RemoveFromCache(aTextRun); michael@0: delete aTextRun; michael@0: } michael@0: }; michael@0: michael@0: // Helper to create a textrun and remember it in the textframe cache, michael@0: // for either 8-bit or 16-bit text strings michael@0: template michael@0: gfxTextRun * michael@0: MakeTextRun(const T *aText, uint32_t aLength, michael@0: gfxFontGroup *aFontGroup, const gfxFontGroup::Parameters* aParams, michael@0: uint32_t aFlags) michael@0: { michael@0: nsAutoPtr textRun(aFontGroup->MakeTextRun(aText, aLength, michael@0: aParams, aFlags)); michael@0: if (!textRun) { michael@0: return nullptr; michael@0: } michael@0: nsresult rv = gTextRuns->AddObject(textRun); michael@0: if (NS_FAILED(rv)) { michael@0: gTextRuns->RemoveFromCache(textRun); michael@0: return nullptr; michael@0: } michael@0: #ifdef NOISY_BIDI michael@0: printf("Created textrun\n"); michael@0: #endif michael@0: return textRun.forget(); michael@0: } michael@0: michael@0: void michael@0: nsTextFrameTextRunCache::Init() { michael@0: gTextRuns = new FrameTextRunCache(); michael@0: } michael@0: michael@0: void michael@0: nsTextFrameTextRunCache::Shutdown() { michael@0: delete gTextRuns; michael@0: gTextRuns = nullptr; michael@0: } michael@0: michael@0: int32_t nsTextFrame::GetContentEnd() const { michael@0: nsTextFrame* next = static_cast(GetNextContinuation()); michael@0: return next ? next->GetContentOffset() : mContent->GetText()->GetLength(); michael@0: } michael@0: michael@0: struct FlowLengthProperty { michael@0: int32_t mStartOffset; michael@0: // The offset of the next fixed continuation after mStartOffset, or michael@0: // of the end of the text if there is none michael@0: int32_t mEndFlowOffset; michael@0: }; michael@0: michael@0: int32_t nsTextFrame::GetInFlowContentLength() { michael@0: if (!(mState & NS_FRAME_IS_BIDI)) { michael@0: return mContent->TextLength() - mContentOffset; michael@0: } michael@0: michael@0: FlowLengthProperty* flowLength = michael@0: static_cast(mContent->GetProperty(nsGkAtoms::flowlength)); michael@0: michael@0: /** michael@0: * This frame must start inside the cached flow. If the flow starts at michael@0: * mContentOffset but this frame is empty, logically it might be before the michael@0: * start of the cached flow. michael@0: */ michael@0: if (flowLength && michael@0: (flowLength->mStartOffset < mContentOffset || michael@0: (flowLength->mStartOffset == mContentOffset && GetContentEnd() > mContentOffset)) && michael@0: flowLength->mEndFlowOffset > mContentOffset) { michael@0: #ifdef DEBUG michael@0: NS_ASSERTION(flowLength->mEndFlowOffset >= GetContentEnd(), michael@0: "frame crosses fixed continuation boundary"); michael@0: #endif michael@0: return flowLength->mEndFlowOffset - mContentOffset; michael@0: } michael@0: michael@0: nsTextFrame* nextBidi = static_cast(LastInFlow()->GetNextContinuation()); michael@0: int32_t endFlow = nextBidi ? nextBidi->GetContentOffset() : mContent->TextLength(); michael@0: michael@0: if (!flowLength) { michael@0: flowLength = new FlowLengthProperty; michael@0: if (NS_FAILED(mContent->SetProperty(nsGkAtoms::flowlength, flowLength, michael@0: nsINode::DeleteProperty))) { michael@0: delete flowLength; michael@0: flowLength = nullptr; michael@0: } michael@0: } michael@0: if (flowLength) { michael@0: flowLength->mStartOffset = mContentOffset; michael@0: flowLength->mEndFlowOffset = endFlow; michael@0: } michael@0: michael@0: return endFlow - mContentOffset; michael@0: } michael@0: michael@0: // Smarter versions of dom::IsSpaceCharacter. michael@0: // Unicode is really annoying; sometimes a space character isn't whitespace --- michael@0: // when it combines with another character michael@0: // So we have several versions of IsSpace for use in different contexts. michael@0: michael@0: static bool IsSpaceCombiningSequenceTail(const nsTextFragment* aFrag, uint32_t aPos) michael@0: { michael@0: NS_ASSERTION(aPos <= aFrag->GetLength(), "Bad offset"); michael@0: if (!aFrag->Is2b()) michael@0: return false; michael@0: return nsTextFrameUtils::IsSpaceCombiningSequenceTail( michael@0: aFrag->Get2b() + aPos, aFrag->GetLength() - aPos); michael@0: } michael@0: michael@0: // Check whether aPos is a space for CSS 'word-spacing' purposes michael@0: static bool IsCSSWordSpacingSpace(const nsTextFragment* aFrag, michael@0: uint32_t aPos, const nsStyleText* aStyleText) michael@0: { michael@0: NS_ASSERTION(aPos < aFrag->GetLength(), "No text for IsSpace!"); michael@0: michael@0: char16_t ch = aFrag->CharAt(aPos); michael@0: switch (ch) { michael@0: case ' ': michael@0: case CH_NBSP: michael@0: return !IsSpaceCombiningSequenceTail(aFrag, aPos + 1); michael@0: case '\r': michael@0: case '\t': return !aStyleText->WhiteSpaceIsSignificant(); michael@0: case '\n': return !aStyleText->NewlineIsSignificant() && michael@0: !aStyleText->NewlineIsDiscarded(); michael@0: default: return false; michael@0: } michael@0: } michael@0: michael@0: // Check whether the string aChars/aLength starts with space that's michael@0: // trimmable according to CSS 'white-space:normal/nowrap'. michael@0: static bool IsTrimmableSpace(const char16_t* aChars, uint32_t aLength) michael@0: { michael@0: NS_ASSERTION(aLength > 0, "No text for IsSpace!"); michael@0: michael@0: char16_t ch = *aChars; michael@0: if (ch == ' ') michael@0: return !nsTextFrameUtils::IsSpaceCombiningSequenceTail(aChars + 1, aLength - 1); michael@0: return ch == '\t' || ch == '\f' || ch == '\n' || ch == '\r'; michael@0: } michael@0: michael@0: // Check whether the character aCh is trimmable according to CSS michael@0: // 'white-space:normal/nowrap' michael@0: static bool IsTrimmableSpace(char aCh) michael@0: { michael@0: return aCh == ' ' || aCh == '\t' || aCh == '\f' || aCh == '\n' || aCh == '\r'; michael@0: } michael@0: michael@0: static bool IsTrimmableSpace(const nsTextFragment* aFrag, uint32_t aPos, michael@0: const nsStyleText* aStyleText) michael@0: { michael@0: NS_ASSERTION(aPos < aFrag->GetLength(), "No text for IsSpace!"); michael@0: michael@0: switch (aFrag->CharAt(aPos)) { michael@0: case ' ': return !aStyleText->WhiteSpaceIsSignificant() && michael@0: !IsSpaceCombiningSequenceTail(aFrag, aPos + 1); michael@0: case '\n': return !aStyleText->NewlineIsSignificant() && michael@0: !aStyleText->NewlineIsDiscarded(); michael@0: case '\t': michael@0: case '\r': michael@0: case '\f': return !aStyleText->WhiteSpaceIsSignificant(); michael@0: default: return false; michael@0: } michael@0: } michael@0: michael@0: static bool IsSelectionSpace(const nsTextFragment* aFrag, uint32_t aPos) michael@0: { michael@0: NS_ASSERTION(aPos < aFrag->GetLength(), "No text for IsSpace!"); michael@0: char16_t ch = aFrag->CharAt(aPos); michael@0: if (ch == ' ' || ch == CH_NBSP) michael@0: return !IsSpaceCombiningSequenceTail(aFrag, aPos + 1); michael@0: return ch == '\t' || ch == '\n' || ch == '\f' || ch == '\r'; michael@0: } michael@0: michael@0: // Count the amount of trimmable whitespace (as per CSS michael@0: // 'white-space:normal/nowrap') in a text fragment. The first michael@0: // character is at offset aStartOffset; the maximum number of characters michael@0: // to check is aLength. aDirection is -1 or 1 depending on whether we should michael@0: // progress backwards or forwards. michael@0: static uint32_t michael@0: GetTrimmableWhitespaceCount(const nsTextFragment* aFrag, michael@0: int32_t aStartOffset, int32_t aLength, michael@0: int32_t aDirection) michael@0: { michael@0: int32_t count = 0; michael@0: if (aFrag->Is2b()) { michael@0: const char16_t* str = aFrag->Get2b() + aStartOffset; michael@0: int32_t fragLen = aFrag->GetLength() - aStartOffset; michael@0: for (; count < aLength; ++count) { michael@0: if (!IsTrimmableSpace(str, fragLen)) michael@0: break; michael@0: str += aDirection; michael@0: fragLen -= aDirection; michael@0: } michael@0: } else { michael@0: const char* str = aFrag->Get1b() + aStartOffset; michael@0: for (; count < aLength; ++count) { michael@0: if (!IsTrimmableSpace(*str)) michael@0: break; michael@0: str += aDirection; michael@0: } michael@0: } michael@0: return count; michael@0: } michael@0: michael@0: static bool michael@0: IsAllWhitespace(const nsTextFragment* aFrag, bool aAllowNewline) michael@0: { michael@0: if (aFrag->Is2b()) michael@0: return false; michael@0: int32_t len = aFrag->GetLength(); michael@0: const char* str = aFrag->Get1b(); michael@0: for (int32_t i = 0; i < len; ++i) { michael@0: char ch = str[i]; michael@0: if (ch == ' ' || ch == '\t' || ch == '\r' || (ch == '\n' && aAllowNewline)) michael@0: continue; michael@0: return false; michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: IsAllNewlines(const nsTextFragment* aFrag) michael@0: { michael@0: if (aFrag->Is2b()) michael@0: return false; michael@0: int32_t len = aFrag->GetLength(); michael@0: const char* str = aFrag->Get1b(); michael@0: for (int32_t i = 0; i < len; ++i) { michael@0: char ch = str[i]; michael@0: if (ch != '\n') michael@0: return false; michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: static void michael@0: CreateObserverForAnimatedGlyphs(nsTextFrame* aFrame, const nsTArray& aFonts) michael@0: { michael@0: if (!(aFrame->GetStateBits() & TEXT_IN_TEXTRUN_USER_DATA)) { michael@0: // Maybe the textrun was created for uninflated text. michael@0: return; michael@0: } michael@0: michael@0: nsTArray >* observers = michael@0: new nsTArray >(); michael@0: for (uint32_t i = 0, count = aFonts.Length(); i < count; ++i) { michael@0: observers->AppendElement(new GlyphObserver(aFonts[i], aFrame)); michael@0: } michael@0: aFrame->Properties().Set(TextFrameGlyphObservers(), observers); michael@0: // We are lazy and don't try to remove a property value that might be michael@0: // obsolete due to style changes or font selection changes. That is michael@0: // likely to be rarely needed, and we don't want to eat the overhead of michael@0: // doing it for the overwhelmingly common case of no property existing. michael@0: // (And we're out of state bits to conveniently use for a fast property michael@0: // existence check.) The only downside is that in some rare cases we might michael@0: // keep fonts alive for longer than necessary, or unnecessarily invalidate michael@0: // frames. michael@0: } michael@0: michael@0: static void michael@0: CreateObserversForAnimatedGlyphs(gfxTextRun* aTextRun) michael@0: { michael@0: if (!aTextRun->GetUserData()) { michael@0: return; michael@0: } michael@0: nsTArray fontsWithAnimatedGlyphs; michael@0: uint32_t numGlyphRuns; michael@0: const gfxTextRun::GlyphRun* glyphRuns = michael@0: aTextRun->GetGlyphRuns(&numGlyphRuns); michael@0: for (uint32_t i = 0; i < numGlyphRuns; ++i) { michael@0: gfxFont* font = glyphRuns[i].mFont; michael@0: if (font->GlyphsMayChange() && !fontsWithAnimatedGlyphs.Contains(font)) { michael@0: fontsWithAnimatedGlyphs.AppendElement(font); michael@0: } michael@0: } michael@0: if (fontsWithAnimatedGlyphs.IsEmpty()) { michael@0: return; michael@0: } michael@0: michael@0: if (aTextRun->GetFlags() & nsTextFrameUtils::TEXT_IS_SIMPLE_FLOW) { michael@0: CreateObserverForAnimatedGlyphs(static_cast( michael@0: static_cast(aTextRun->GetUserData())), fontsWithAnimatedGlyphs); michael@0: } else { michael@0: TextRunUserData* userData = michael@0: static_cast(aTextRun->GetUserData()); michael@0: for (uint32_t i = 0; i < userData->mMappedFlowCount; ++i) { michael@0: CreateObserverForAnimatedGlyphs(userData->mMappedFlows[i].mStartFrame, michael@0: fontsWithAnimatedGlyphs); michael@0: } michael@0: } michael@0: } michael@0: michael@0: /** michael@0: * This class accumulates state as we scan a paragraph of text. It detects michael@0: * textrun boundaries (changes from text to non-text, hard michael@0: * line breaks, and font changes) and builds a gfxTextRun at each boundary. michael@0: * It also detects linebreaker run boundaries (changes from text to non-text, michael@0: * and hard line breaks) and at each boundary runs the linebreaker to compute michael@0: * potential line breaks. It also records actual line breaks to store them in michael@0: * the textruns. michael@0: */ michael@0: class BuildTextRunsScanner { michael@0: public: michael@0: BuildTextRunsScanner(nsPresContext* aPresContext, gfxContext* aContext, michael@0: nsIFrame* aLineContainer, nsTextFrame::TextRunType aWhichTextRun) : michael@0: mCurrentFramesAllSameTextRun(nullptr), michael@0: mContext(aContext), michael@0: mLineContainer(aLineContainer), michael@0: mBidiEnabled(aPresContext->BidiEnabled()), michael@0: mSkipIncompleteTextRuns(false), michael@0: mWhichTextRun(aWhichTextRun), michael@0: mNextRunContextInfo(nsTextFrameUtils::INCOMING_NONE), michael@0: mCurrentRunContextInfo(nsTextFrameUtils::INCOMING_NONE) { michael@0: ResetRunInfo(); michael@0: } michael@0: ~BuildTextRunsScanner() { michael@0: NS_ASSERTION(mBreakSinks.IsEmpty(), "Should have been cleared"); michael@0: NS_ASSERTION(mTextRunsToDelete.IsEmpty(), "Should have been cleared"); michael@0: NS_ASSERTION(mLineBreakBeforeFrames.IsEmpty(), "Should have been cleared"); michael@0: NS_ASSERTION(mMappedFlows.IsEmpty(), "Should have been cleared"); michael@0: } michael@0: michael@0: void SetAtStartOfLine() { michael@0: mStartOfLine = true; michael@0: mCanStopOnThisLine = false; michael@0: } michael@0: void SetSkipIncompleteTextRuns(bool aSkip) { michael@0: mSkipIncompleteTextRuns = aSkip; michael@0: } michael@0: void SetCommonAncestorWithLastFrame(nsIFrame* aFrame) { michael@0: mCommonAncestorWithLastFrame = aFrame; michael@0: } michael@0: bool CanStopOnThisLine() { michael@0: return mCanStopOnThisLine; michael@0: } michael@0: nsIFrame* GetCommonAncestorWithLastFrame() { michael@0: return mCommonAncestorWithLastFrame; michael@0: } michael@0: void LiftCommonAncestorWithLastFrameToParent(nsIFrame* aFrame) { michael@0: if (mCommonAncestorWithLastFrame && michael@0: mCommonAncestorWithLastFrame->GetParent() == aFrame) { michael@0: mCommonAncestorWithLastFrame = aFrame; michael@0: } michael@0: } michael@0: void ScanFrame(nsIFrame* aFrame); michael@0: bool IsTextRunValidForMappedFlows(gfxTextRun* aTextRun); michael@0: void FlushFrames(bool aFlushLineBreaks, bool aSuppressTrailingBreak); michael@0: void FlushLineBreaks(gfxTextRun* aTrailingTextRun); michael@0: void ResetRunInfo() { michael@0: mLastFrame = nullptr; michael@0: mMappedFlows.Clear(); michael@0: mLineBreakBeforeFrames.Clear(); michael@0: mMaxTextLength = 0; michael@0: mDoubleByteText = false; michael@0: } michael@0: void AccumulateRunInfo(nsTextFrame* aFrame); michael@0: /** michael@0: * @return null to indicate either textrun construction failed or michael@0: * we constructed just a partial textrun to set up linebreaker and other michael@0: * state for following textruns. michael@0: */ michael@0: gfxTextRun* BuildTextRunForFrames(void* aTextBuffer); michael@0: bool SetupLineBreakerContext(gfxTextRun *aTextRun); michael@0: void AssignTextRun(gfxTextRun* aTextRun, float aInflation); michael@0: nsTextFrame* GetNextBreakBeforeFrame(uint32_t* aIndex); michael@0: enum SetupBreakSinksFlags { michael@0: SBS_DOUBLE_BYTE = (1 << 0), michael@0: SBS_EXISTING_TEXTRUN = (1 << 1), michael@0: SBS_SUPPRESS_SINK = (1 << 2) michael@0: }; michael@0: void SetupBreakSinksForTextRun(gfxTextRun* aTextRun, michael@0: const void* aTextPtr, michael@0: uint32_t aFlags); michael@0: struct FindBoundaryState { michael@0: nsIFrame* mStopAtFrame; michael@0: nsTextFrame* mFirstTextFrame; michael@0: nsTextFrame* mLastTextFrame; michael@0: bool mSeenTextRunBoundaryOnLaterLine; michael@0: bool mSeenTextRunBoundaryOnThisLine; michael@0: bool mSeenSpaceForLineBreakingOnThisLine; michael@0: }; michael@0: enum FindBoundaryResult { michael@0: FB_CONTINUE, michael@0: FB_STOPPED_AT_STOP_FRAME, michael@0: FB_FOUND_VALID_TEXTRUN_BOUNDARY michael@0: }; michael@0: FindBoundaryResult FindBoundaries(nsIFrame* aFrame, FindBoundaryState* aState); michael@0: michael@0: bool ContinueTextRunAcrossFrames(nsTextFrame* aFrame1, nsTextFrame* aFrame2); michael@0: michael@0: // Like TextRunMappedFlow but with some differences. mStartFrame to mEndFrame michael@0: // (exclusive) are a sequence of in-flow frames (if mEndFrame is null, then michael@0: // continuations starting from mStartFrame are a sequence of in-flow frames). michael@0: struct MappedFlow { michael@0: nsTextFrame* mStartFrame; michael@0: nsTextFrame* mEndFrame; michael@0: // When we consider breaking between elements, the nearest common michael@0: // ancestor of the elements containing the characters is the one whose michael@0: // CSS 'white-space' property governs. So this records the nearest common michael@0: // ancestor of mStartFrame and the previous text frame, or null if there michael@0: // was no previous text frame on this line. michael@0: nsIFrame* mAncestorControllingInitialBreak; michael@0: michael@0: int32_t GetContentEnd() { michael@0: return mEndFrame ? mEndFrame->GetContentOffset() michael@0: : mStartFrame->GetContent()->GetText()->GetLength(); michael@0: } michael@0: }; michael@0: michael@0: class BreakSink MOZ_FINAL : public nsILineBreakSink { michael@0: public: michael@0: BreakSink(gfxTextRun* aTextRun, gfxContext* aContext, uint32_t aOffsetIntoTextRun, michael@0: bool aExistingTextRun) : michael@0: mTextRun(aTextRun), mContext(aContext), michael@0: mOffsetIntoTextRun(aOffsetIntoTextRun), michael@0: mChangedBreaks(false), mExistingTextRun(aExistingTextRun) {} michael@0: michael@0: virtual void SetBreaks(uint32_t aOffset, uint32_t aLength, michael@0: uint8_t* aBreakBefore) MOZ_OVERRIDE { michael@0: if (mTextRun->SetPotentialLineBreaks(aOffset + mOffsetIntoTextRun, aLength, michael@0: aBreakBefore, mContext)) { michael@0: mChangedBreaks = true; michael@0: // Be conservative and assume that some breaks have been set michael@0: mTextRun->ClearFlagBits(nsTextFrameUtils::TEXT_NO_BREAKS); michael@0: } michael@0: } michael@0: michael@0: virtual void SetCapitalization(uint32_t aOffset, uint32_t aLength, michael@0: bool* aCapitalize) MOZ_OVERRIDE { michael@0: NS_ASSERTION(mTextRun->GetFlags() & nsTextFrameUtils::TEXT_IS_TRANSFORMED, michael@0: "Text run should be transformed!"); michael@0: nsTransformedTextRun* transformedTextRun = michael@0: static_cast(mTextRun); michael@0: transformedTextRun->SetCapitalization(aOffset + mOffsetIntoTextRun, aLength, michael@0: aCapitalize, mContext); michael@0: } michael@0: michael@0: void Finish() { michael@0: NS_ASSERTION(!(mTextRun->GetFlags() & michael@0: (gfxTextRunFactory::TEXT_UNUSED_FLAGS | michael@0: nsTextFrameUtils::TEXT_UNUSED_FLAG)), michael@0: "Flag set that should never be set! (memory safety error?)"); michael@0: if (mTextRun->GetFlags() & nsTextFrameUtils::TEXT_IS_TRANSFORMED) { michael@0: nsTransformedTextRun* transformedTextRun = michael@0: static_cast(mTextRun); michael@0: transformedTextRun->FinishSettingProperties(mContext); michael@0: } michael@0: // The way nsTransformedTextRun is implemented, its glyph runs aren't michael@0: // available until after nsTransformedTextRun::FinishSettingProperties() michael@0: // is called. So that's why we defer checking for animated glyphs to here. michael@0: CreateObserversForAnimatedGlyphs(mTextRun); michael@0: } michael@0: michael@0: gfxTextRun* mTextRun; michael@0: gfxContext* mContext; michael@0: uint32_t mOffsetIntoTextRun; michael@0: bool mChangedBreaks; michael@0: bool mExistingTextRun; michael@0: }; michael@0: michael@0: private: michael@0: nsAutoTArray mMappedFlows; michael@0: nsAutoTArray mLineBreakBeforeFrames; michael@0: nsAutoTArray,10> mBreakSinks; michael@0: nsAutoTArray mTextRunsToDelete; michael@0: nsLineBreaker mLineBreaker; michael@0: gfxTextRun* mCurrentFramesAllSameTextRun; michael@0: gfxContext* mContext; michael@0: nsIFrame* mLineContainer; michael@0: nsTextFrame* mLastFrame; michael@0: // The common ancestor of the current frame and the previous leaf frame michael@0: // on the line, or null if there was no previous leaf frame. michael@0: nsIFrame* mCommonAncestorWithLastFrame; michael@0: // mMaxTextLength is an upper bound on the size of the text in all mapped frames michael@0: // The value UINT32_MAX represents overflow; text will be discarded michael@0: uint32_t mMaxTextLength; michael@0: bool mDoubleByteText; michael@0: bool mBidiEnabled; michael@0: bool mStartOfLine; michael@0: bool mSkipIncompleteTextRuns; michael@0: bool mCanStopOnThisLine; michael@0: nsTextFrame::TextRunType mWhichTextRun; michael@0: uint8_t mNextRunContextInfo; michael@0: uint8_t mCurrentRunContextInfo; michael@0: }; michael@0: michael@0: static nsIFrame* michael@0: FindLineContainer(nsIFrame* aFrame) michael@0: { michael@0: while (aFrame && aFrame->CanContinueTextRun()) { michael@0: aFrame = aFrame->GetParent(); michael@0: } michael@0: return aFrame; michael@0: } michael@0: michael@0: static bool michael@0: IsLineBreakingWhiteSpace(char16_t aChar) michael@0: { michael@0: // 0x0A (\n) is not handled as white-space by the line breaker, since michael@0: // we break before it, if it isn't transformed to a normal space. michael@0: // (If we treat it as normal white-space then we'd only break after it.) michael@0: // However, it does induce a line break or is converted to a regular michael@0: // space, and either way it can be used to bound the region of text michael@0: // that needs to be analyzed for line breaking. michael@0: return nsLineBreaker::IsSpace(aChar) || aChar == 0x0A; michael@0: } michael@0: michael@0: static bool michael@0: TextContainsLineBreakerWhiteSpace(const void* aText, uint32_t aLength, michael@0: bool aIsDoubleByte) michael@0: { michael@0: if (aIsDoubleByte) { michael@0: const char16_t* chars = static_cast(aText); michael@0: for (uint32_t i = 0; i < aLength; ++i) { michael@0: if (IsLineBreakingWhiteSpace(chars[i])) michael@0: return true; michael@0: } michael@0: return false; michael@0: } else { michael@0: const uint8_t* chars = static_cast(aText); michael@0: for (uint32_t i = 0; i < aLength; ++i) { michael@0: if (IsLineBreakingWhiteSpace(chars[i])) michael@0: return true; michael@0: } michael@0: return false; michael@0: } michael@0: } michael@0: michael@0: struct FrameTextTraversal { michael@0: // These fields identify which frames should be recursively scanned michael@0: // The first normal frame to scan (or null, if no such frame should be scanned) michael@0: nsIFrame* mFrameToScan; michael@0: // The first overflow frame to scan (or null, if no such frame should be scanned) michael@0: nsIFrame* mOverflowFrameToScan; michael@0: // Whether to scan the siblings of mFrameToDescendInto/mOverflowFrameToDescendInto michael@0: bool mScanSiblings; michael@0: michael@0: // These identify the boundaries of the context required for michael@0: // line breaking or textrun construction michael@0: bool mLineBreakerCanCrossFrameBoundary; michael@0: bool mTextRunCanCrossFrameBoundary; michael@0: michael@0: nsIFrame* NextFrameToScan() { michael@0: nsIFrame* f; michael@0: if (mFrameToScan) { michael@0: f = mFrameToScan; michael@0: mFrameToScan = mScanSiblings ? f->GetNextSibling() : nullptr; michael@0: } else if (mOverflowFrameToScan) { michael@0: f = mOverflowFrameToScan; michael@0: mOverflowFrameToScan = mScanSiblings ? f->GetNextSibling() : nullptr; michael@0: } else { michael@0: f = nullptr; michael@0: } michael@0: return f; michael@0: } michael@0: }; michael@0: michael@0: static FrameTextTraversal michael@0: CanTextCrossFrameBoundary(nsIFrame* aFrame, nsIAtom* aType) michael@0: { michael@0: NS_ASSERTION(aType == aFrame->GetType(), "Wrong type"); michael@0: michael@0: FrameTextTraversal result; michael@0: michael@0: bool continuesTextRun = aFrame->CanContinueTextRun(); michael@0: if (aType == nsGkAtoms::placeholderFrame) { michael@0: // placeholders are "invisible", so a text run should be able to span michael@0: // across one. But don't descend into the out-of-flow. michael@0: result.mLineBreakerCanCrossFrameBoundary = true; michael@0: result.mOverflowFrameToScan = nullptr; michael@0: if (continuesTextRun) { michael@0: // ... Except for first-letter floats, which are really in-flow michael@0: // from the point of view of capitalization etc, so we'd better michael@0: // descend into them. But we actually need to break the textrun for michael@0: // first-letter floats since things look bad if, say, we try to make a michael@0: // ligature across the float boundary. michael@0: result.mFrameToScan = michael@0: (static_cast(aFrame))->GetOutOfFlowFrame(); michael@0: result.mScanSiblings = false; michael@0: result.mTextRunCanCrossFrameBoundary = false; michael@0: } else { michael@0: result.mFrameToScan = nullptr; michael@0: result.mTextRunCanCrossFrameBoundary = true; michael@0: } michael@0: } else { michael@0: if (continuesTextRun) { michael@0: result.mFrameToScan = aFrame->GetFirstPrincipalChild(); michael@0: result.mOverflowFrameToScan = michael@0: aFrame->GetFirstChild(nsIFrame::kOverflowList); michael@0: NS_WARN_IF_FALSE(!result.mOverflowFrameToScan, michael@0: "Scanning overflow inline frames is something we should avoid"); michael@0: result.mScanSiblings = true; michael@0: result.mTextRunCanCrossFrameBoundary = true; michael@0: result.mLineBreakerCanCrossFrameBoundary = true; michael@0: } else { michael@0: result.mFrameToScan = nullptr; michael@0: result.mOverflowFrameToScan = nullptr; michael@0: result.mTextRunCanCrossFrameBoundary = false; michael@0: result.mLineBreakerCanCrossFrameBoundary = false; michael@0: } michael@0: } michael@0: return result; michael@0: } michael@0: michael@0: BuildTextRunsScanner::FindBoundaryResult michael@0: BuildTextRunsScanner::FindBoundaries(nsIFrame* aFrame, FindBoundaryState* aState) michael@0: { michael@0: nsIAtom* frameType = aFrame->GetType(); michael@0: nsTextFrame* textFrame = frameType == nsGkAtoms::textFrame michael@0: ? static_cast(aFrame) : nullptr; michael@0: if (textFrame) { michael@0: if (aState->mLastTextFrame && michael@0: textFrame != aState->mLastTextFrame->GetNextInFlow() && michael@0: !ContinueTextRunAcrossFrames(aState->mLastTextFrame, textFrame)) { michael@0: aState->mSeenTextRunBoundaryOnThisLine = true; michael@0: if (aState->mSeenSpaceForLineBreakingOnThisLine) michael@0: return FB_FOUND_VALID_TEXTRUN_BOUNDARY; michael@0: } michael@0: if (!aState->mFirstTextFrame) { michael@0: aState->mFirstTextFrame = textFrame; michael@0: } michael@0: aState->mLastTextFrame = textFrame; michael@0: } michael@0: michael@0: if (aFrame == aState->mStopAtFrame) michael@0: return FB_STOPPED_AT_STOP_FRAME; michael@0: michael@0: if (textFrame) { michael@0: if (!aState->mSeenSpaceForLineBreakingOnThisLine) { michael@0: const nsTextFragment* frag = textFrame->GetContent()->GetText(); michael@0: uint32_t start = textFrame->GetContentOffset(); michael@0: const void* text = frag->Is2b() michael@0: ? static_cast(frag->Get2b() + start) michael@0: : static_cast(frag->Get1b() + start); michael@0: if (TextContainsLineBreakerWhiteSpace(text, textFrame->GetContentLength(), michael@0: frag->Is2b())) { michael@0: aState->mSeenSpaceForLineBreakingOnThisLine = true; michael@0: if (aState->mSeenTextRunBoundaryOnLaterLine) michael@0: return FB_FOUND_VALID_TEXTRUN_BOUNDARY; michael@0: } michael@0: } michael@0: return FB_CONTINUE; michael@0: } michael@0: michael@0: FrameTextTraversal traversal = michael@0: CanTextCrossFrameBoundary(aFrame, frameType); michael@0: if (!traversal.mTextRunCanCrossFrameBoundary) { michael@0: aState->mSeenTextRunBoundaryOnThisLine = true; michael@0: if (aState->mSeenSpaceForLineBreakingOnThisLine) michael@0: return FB_FOUND_VALID_TEXTRUN_BOUNDARY; michael@0: } michael@0: michael@0: for (nsIFrame* f = traversal.NextFrameToScan(); f; michael@0: f = traversal.NextFrameToScan()) { michael@0: FindBoundaryResult result = FindBoundaries(f, aState); michael@0: if (result != FB_CONTINUE) michael@0: return result; michael@0: } michael@0: michael@0: if (!traversal.mTextRunCanCrossFrameBoundary) { michael@0: aState->mSeenTextRunBoundaryOnThisLine = true; michael@0: if (aState->mSeenSpaceForLineBreakingOnThisLine) michael@0: return FB_FOUND_VALID_TEXTRUN_BOUNDARY; michael@0: } michael@0: michael@0: return FB_CONTINUE; michael@0: } michael@0: michael@0: // build text runs for the 200 lines following aForFrame, and stop after that michael@0: // when we get a chance. michael@0: #define NUM_LINES_TO_BUILD_TEXT_RUNS 200 michael@0: michael@0: /** michael@0: * General routine for building text runs. This is hairy because of the need michael@0: * to build text runs that span content nodes. michael@0: * michael@0: * @param aContext The gfxContext we're using to construct this text run. michael@0: * @param aForFrame The nsTextFrame for which we're building this text run. michael@0: * @param aLineContainer the line container containing aForFrame; if null, michael@0: * we'll walk the ancestors to find it. It's required to be non-null michael@0: * when aForFrameLine is non-null. michael@0: * @param aForFrameLine the line containing aForFrame; if null, we'll figure michael@0: * out the line (slowly) michael@0: * @param aWhichTextRun The type of text run we want to build. If font inflation michael@0: * is enabled, this will be eInflated, otherwise it's eNotInflated. michael@0: */ michael@0: static void michael@0: BuildTextRuns(gfxContext* aContext, nsTextFrame* aForFrame, michael@0: nsIFrame* aLineContainer, michael@0: const nsLineList::iterator* aForFrameLine, michael@0: nsTextFrame::TextRunType aWhichTextRun) michael@0: { michael@0: NS_ASSERTION(aForFrame || aLineContainer, michael@0: "One of aForFrame or aLineContainer must be set!"); michael@0: NS_ASSERTION(!aForFrameLine || aLineContainer, michael@0: "line but no line container"); michael@0: michael@0: nsIFrame* lineContainerChild = aForFrame; michael@0: if (!aLineContainer) { michael@0: if (aForFrame->IsFloatingFirstLetterChild()) { michael@0: lineContainerChild = aForFrame->PresContext()->PresShell()-> michael@0: GetPlaceholderFrameFor(aForFrame->GetParent()); michael@0: } michael@0: aLineContainer = FindLineContainer(lineContainerChild); michael@0: } else { michael@0: NS_ASSERTION(!aForFrame || michael@0: (aLineContainer == FindLineContainer(aForFrame) || michael@0: (aLineContainer->GetType() == nsGkAtoms::letterFrame && michael@0: aLineContainer->IsFloating())), michael@0: "Wrong line container hint"); michael@0: } michael@0: michael@0: if (aForFrame) { michael@0: if (aForFrame->HasAnyStateBits(TEXT_IS_IN_TOKEN_MATHML)) { michael@0: aLineContainer->AddStateBits(TEXT_IS_IN_TOKEN_MATHML); michael@0: if (aForFrame->HasAnyStateBits(NS_FRAME_IS_IN_SINGLE_CHAR_MI)) { michael@0: aLineContainer->AddStateBits(NS_FRAME_IS_IN_SINGLE_CHAR_MI); michael@0: } michael@0: } michael@0: if (aForFrame->HasAnyStateBits(NS_FRAME_MATHML_SCRIPT_DESCENDANT)) { michael@0: aLineContainer->AddStateBits(NS_FRAME_MATHML_SCRIPT_DESCENDANT); michael@0: } michael@0: } michael@0: michael@0: nsPresContext* presContext = aLineContainer->PresContext(); michael@0: BuildTextRunsScanner scanner(presContext, aContext, aLineContainer, michael@0: aWhichTextRun); michael@0: michael@0: nsBlockFrame* block = nsLayoutUtils::GetAsBlock(aLineContainer); michael@0: michael@0: if (!block) { michael@0: NS_ASSERTION(!aLineContainer->GetPrevInFlow() && !aLineContainer->GetNextInFlow(), michael@0: "Breakable non-block line containers not supported"); michael@0: // Just loop through all the children of the linecontainer ... it's really michael@0: // just one line michael@0: scanner.SetAtStartOfLine(); michael@0: scanner.SetCommonAncestorWithLastFrame(nullptr); michael@0: nsIFrame* child = aLineContainer->GetFirstPrincipalChild(); michael@0: while (child) { michael@0: scanner.ScanFrame(child); michael@0: child = child->GetNextSibling(); michael@0: } michael@0: // Set mStartOfLine so FlushFrames knows its textrun ends a line michael@0: scanner.SetAtStartOfLine(); michael@0: scanner.FlushFrames(true, false); michael@0: return; michael@0: } michael@0: michael@0: // Find the line containing 'lineContainerChild'. michael@0: michael@0: bool isValid = true; michael@0: nsBlockInFlowLineIterator backIterator(block, &isValid); michael@0: if (aForFrameLine) { michael@0: backIterator = nsBlockInFlowLineIterator(block, *aForFrameLine); michael@0: } else { michael@0: backIterator = nsBlockInFlowLineIterator(block, lineContainerChild, &isValid); michael@0: NS_ASSERTION(isValid, "aForFrame not found in block, someone lied to us"); michael@0: NS_ASSERTION(backIterator.GetContainer() == block, michael@0: "Someone lied to us about the block"); michael@0: } michael@0: nsBlockFrame::line_iterator startLine = backIterator.GetLine(); michael@0: michael@0: // Find a line where we can start building text runs. We choose the last line michael@0: // where: michael@0: // -- there is a textrun boundary between the start of the line and the michael@0: // start of aForFrame michael@0: // -- there is a space between the start of the line and the textrun boundary michael@0: // (this is so we can be sure the line breaks will be set properly michael@0: // on the textruns we construct). michael@0: // The possibly-partial text runs up to and including the first space michael@0: // are not reconstructed. We construct partial text runs for that text --- michael@0: // for the sake of simplifying the code and feeding the linebreaker --- michael@0: // but we discard them instead of assigning them to frames. michael@0: // This is a little awkward because we traverse lines in the reverse direction michael@0: // but we traverse the frames in each line in the forward direction. michael@0: nsBlockInFlowLineIterator forwardIterator = backIterator; michael@0: nsIFrame* stopAtFrame = lineContainerChild; michael@0: nsTextFrame* nextLineFirstTextFrame = nullptr; michael@0: bool seenTextRunBoundaryOnLaterLine = false; michael@0: bool mayBeginInTextRun = true; michael@0: while (true) { michael@0: forwardIterator = backIterator; michael@0: nsBlockFrame::line_iterator line = backIterator.GetLine(); michael@0: if (!backIterator.Prev() || backIterator.GetLine()->IsBlock()) { michael@0: mayBeginInTextRun = false; michael@0: break; michael@0: } michael@0: michael@0: BuildTextRunsScanner::FindBoundaryState state = { stopAtFrame, nullptr, nullptr, michael@0: bool(seenTextRunBoundaryOnLaterLine), false, false }; michael@0: nsIFrame* child = line->mFirstChild; michael@0: bool foundBoundary = false; michael@0: for (int32_t i = line->GetChildCount() - 1; i >= 0; --i) { michael@0: BuildTextRunsScanner::FindBoundaryResult result = michael@0: scanner.FindBoundaries(child, &state); michael@0: if (result == BuildTextRunsScanner::FB_FOUND_VALID_TEXTRUN_BOUNDARY) { michael@0: foundBoundary = true; michael@0: break; michael@0: } else if (result == BuildTextRunsScanner::FB_STOPPED_AT_STOP_FRAME) { michael@0: break; michael@0: } michael@0: child = child->GetNextSibling(); michael@0: } michael@0: if (foundBoundary) michael@0: break; michael@0: if (!stopAtFrame && state.mLastTextFrame && nextLineFirstTextFrame && michael@0: !scanner.ContinueTextRunAcrossFrames(state.mLastTextFrame, nextLineFirstTextFrame)) { michael@0: // Found a usable textrun boundary at the end of the line michael@0: if (state.mSeenSpaceForLineBreakingOnThisLine) michael@0: break; michael@0: seenTextRunBoundaryOnLaterLine = true; michael@0: } else if (state.mSeenTextRunBoundaryOnThisLine) { michael@0: seenTextRunBoundaryOnLaterLine = true; michael@0: } michael@0: stopAtFrame = nullptr; michael@0: if (state.mFirstTextFrame) { michael@0: nextLineFirstTextFrame = state.mFirstTextFrame; michael@0: } michael@0: } michael@0: scanner.SetSkipIncompleteTextRuns(mayBeginInTextRun); michael@0: michael@0: // Now iterate over all text frames starting from the current line. First-in-flow michael@0: // text frames will be accumulated into textRunFrames as we go. When a michael@0: // text run boundary is required we flush textRunFrames ((re)building their michael@0: // gfxTextRuns as necessary). michael@0: bool seenStartLine = false; michael@0: uint32_t linesAfterStartLine = 0; michael@0: do { michael@0: nsBlockFrame::line_iterator line = forwardIterator.GetLine(); michael@0: if (line->IsBlock()) michael@0: break; michael@0: line->SetInvalidateTextRuns(false); michael@0: scanner.SetAtStartOfLine(); michael@0: scanner.SetCommonAncestorWithLastFrame(nullptr); michael@0: nsIFrame* child = line->mFirstChild; michael@0: for (int32_t i = line->GetChildCount() - 1; i >= 0; --i) { michael@0: scanner.ScanFrame(child); michael@0: child = child->GetNextSibling(); michael@0: } michael@0: if (line.get() == startLine.get()) { michael@0: seenStartLine = true; michael@0: } michael@0: if (seenStartLine) { michael@0: ++linesAfterStartLine; michael@0: if (linesAfterStartLine >= NUM_LINES_TO_BUILD_TEXT_RUNS && scanner.CanStopOnThisLine()) { michael@0: // Don't flush frames; we may be in the middle of a textrun michael@0: // that we can't end here. That's OK, we just won't build it. michael@0: // Note that we must already have finished the textrun for aForFrame, michael@0: // because we've seen the end of a textrun in a line after the line michael@0: // containing aForFrame. michael@0: scanner.FlushLineBreaks(nullptr); michael@0: // This flushes out mMappedFlows and mLineBreakBeforeFrames, which michael@0: // silences assertions in the scanner destructor. michael@0: scanner.ResetRunInfo(); michael@0: return; michael@0: } michael@0: } michael@0: } while (forwardIterator.Next()); michael@0: michael@0: // Set mStartOfLine so FlushFrames knows its textrun ends a line michael@0: scanner.SetAtStartOfLine(); michael@0: scanner.FlushFrames(true, false); michael@0: } michael@0: michael@0: static char16_t* michael@0: ExpandBuffer(char16_t* aDest, uint8_t* aSrc, uint32_t aCount) michael@0: { michael@0: while (aCount) { michael@0: *aDest = *aSrc; michael@0: ++aDest; michael@0: ++aSrc; michael@0: --aCount; michael@0: } michael@0: return aDest; michael@0: } michael@0: michael@0: bool BuildTextRunsScanner::IsTextRunValidForMappedFlows(gfxTextRun* aTextRun) michael@0: { michael@0: if (aTextRun->GetFlags() & nsTextFrameUtils::TEXT_IS_SIMPLE_FLOW) michael@0: return mMappedFlows.Length() == 1 && michael@0: mMappedFlows[0].mStartFrame == static_cast(aTextRun->GetUserData()) && michael@0: mMappedFlows[0].mEndFrame == nullptr; michael@0: michael@0: TextRunUserData* userData = static_cast(aTextRun->GetUserData()); michael@0: if (userData->mMappedFlowCount != mMappedFlows.Length()) michael@0: return false; michael@0: for (uint32_t i = 0; i < mMappedFlows.Length(); ++i) { michael@0: if (userData->mMappedFlows[i].mStartFrame != mMappedFlows[i].mStartFrame || michael@0: int32_t(userData->mMappedFlows[i].mContentLength) != michael@0: mMappedFlows[i].GetContentEnd() - mMappedFlows[i].mStartFrame->GetContentOffset()) michael@0: return false; michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: /** michael@0: * This gets called when we need to make a text run for the current list of michael@0: * frames. michael@0: */ michael@0: void BuildTextRunsScanner::FlushFrames(bool aFlushLineBreaks, bool aSuppressTrailingBreak) michael@0: { michael@0: gfxTextRun* textRun = nullptr; michael@0: if (!mMappedFlows.IsEmpty()) { michael@0: if (!mSkipIncompleteTextRuns && mCurrentFramesAllSameTextRun && michael@0: ((mCurrentFramesAllSameTextRun->GetFlags() & nsTextFrameUtils::TEXT_INCOMING_WHITESPACE) != 0) == michael@0: ((mCurrentRunContextInfo & nsTextFrameUtils::INCOMING_WHITESPACE) != 0) && michael@0: ((mCurrentFramesAllSameTextRun->GetFlags() & gfxTextRunFactory::TEXT_INCOMING_ARABICCHAR) != 0) == michael@0: ((mCurrentRunContextInfo & nsTextFrameUtils::INCOMING_ARABICCHAR) != 0) && michael@0: IsTextRunValidForMappedFlows(mCurrentFramesAllSameTextRun)) { michael@0: // Optimization: We do not need to (re)build the textrun. michael@0: textRun = mCurrentFramesAllSameTextRun; michael@0: michael@0: // Feed this run's text into the linebreaker to provide context. michael@0: if (!SetupLineBreakerContext(textRun)) { michael@0: return; michael@0: } michael@0: michael@0: // Update mNextRunContextInfo appropriately michael@0: mNextRunContextInfo = nsTextFrameUtils::INCOMING_NONE; michael@0: if (textRun->GetFlags() & nsTextFrameUtils::TEXT_TRAILING_WHITESPACE) { michael@0: mNextRunContextInfo |= nsTextFrameUtils::INCOMING_WHITESPACE; michael@0: } michael@0: if (textRun->GetFlags() & gfxTextRunFactory::TEXT_TRAILING_ARABICCHAR) { michael@0: mNextRunContextInfo |= nsTextFrameUtils::INCOMING_ARABICCHAR; michael@0: } michael@0: } else { michael@0: AutoFallibleTArray buffer; michael@0: uint32_t bufferSize = mMaxTextLength*(mDoubleByteText ? 2 : 1); michael@0: if (bufferSize < mMaxTextLength || bufferSize == UINT32_MAX || michael@0: !buffer.AppendElements(bufferSize)) { michael@0: return; michael@0: } michael@0: textRun = BuildTextRunForFrames(buffer.Elements()); michael@0: } michael@0: } michael@0: michael@0: if (aFlushLineBreaks) { michael@0: FlushLineBreaks(aSuppressTrailingBreak ? nullptr : textRun); michael@0: } michael@0: michael@0: mCanStopOnThisLine = true; michael@0: ResetRunInfo(); michael@0: } michael@0: michael@0: void BuildTextRunsScanner::FlushLineBreaks(gfxTextRun* aTrailingTextRun) michael@0: { michael@0: bool trailingLineBreak; michael@0: nsresult rv = mLineBreaker.Reset(&trailingLineBreak); michael@0: // textRun may be null for various reasons, including because we constructed michael@0: // a partial textrun just to get the linebreaker and other state set up michael@0: // to build the next textrun. michael@0: if (NS_SUCCEEDED(rv) && trailingLineBreak && aTrailingTextRun) { michael@0: aTrailingTextRun->SetFlagBits(nsTextFrameUtils::TEXT_HAS_TRAILING_BREAK); michael@0: } michael@0: michael@0: for (uint32_t i = 0; i < mBreakSinks.Length(); ++i) { michael@0: if (!mBreakSinks[i]->mExistingTextRun || mBreakSinks[i]->mChangedBreaks) { michael@0: // TODO cause frames associated with the textrun to be reflowed, if they michael@0: // aren't being reflowed already! michael@0: } michael@0: mBreakSinks[i]->Finish(); michael@0: } michael@0: mBreakSinks.Clear(); michael@0: michael@0: for (uint32_t i = 0; i < mTextRunsToDelete.Length(); ++i) { michael@0: gfxTextRun* deleteTextRun = mTextRunsToDelete[i]; michael@0: gTextRuns->RemoveFromCache(deleteTextRun); michael@0: delete deleteTextRun; michael@0: } michael@0: mTextRunsToDelete.Clear(); michael@0: } michael@0: michael@0: void BuildTextRunsScanner::AccumulateRunInfo(nsTextFrame* aFrame) michael@0: { michael@0: if (mMaxTextLength != UINT32_MAX) { michael@0: NS_ASSERTION(mMaxTextLength < UINT32_MAX - aFrame->GetContentLength(), "integer overflow"); michael@0: if (mMaxTextLength >= UINT32_MAX - aFrame->GetContentLength()) { michael@0: mMaxTextLength = UINT32_MAX; michael@0: } else { michael@0: mMaxTextLength += aFrame->GetContentLength(); michael@0: } michael@0: } michael@0: mDoubleByteText |= aFrame->GetContent()->GetText()->Is2b(); michael@0: mLastFrame = aFrame; michael@0: mCommonAncestorWithLastFrame = aFrame->GetParent(); michael@0: michael@0: MappedFlow* mappedFlow = &mMappedFlows[mMappedFlows.Length() - 1]; michael@0: NS_ASSERTION(mappedFlow->mStartFrame == aFrame || michael@0: mappedFlow->GetContentEnd() == aFrame->GetContentOffset(), michael@0: "Overlapping or discontiguous frames => BAD"); michael@0: mappedFlow->mEndFrame = static_cast(aFrame->GetNextContinuation()); michael@0: if (mCurrentFramesAllSameTextRun != aFrame->GetTextRun(mWhichTextRun)) { michael@0: mCurrentFramesAllSameTextRun = nullptr; michael@0: } michael@0: michael@0: if (mStartOfLine) { michael@0: mLineBreakBeforeFrames.AppendElement(aFrame); michael@0: mStartOfLine = false; michael@0: } michael@0: } michael@0: michael@0: static nscoord StyleToCoord(const nsStyleCoord& aCoord) michael@0: { michael@0: if (eStyleUnit_Coord == aCoord.GetUnit()) { michael@0: return aCoord.GetCoordValue(); michael@0: } else { michael@0: return 0; michael@0: } michael@0: } michael@0: michael@0: static bool michael@0: HasTerminalNewline(const nsTextFrame* aFrame) michael@0: { michael@0: if (aFrame->GetContentLength() == 0) michael@0: return false; michael@0: const nsTextFragment* frag = aFrame->GetContent()->GetText(); michael@0: return frag->CharAt(aFrame->GetContentEnd() - 1) == '\n'; michael@0: } michael@0: michael@0: static nscoord michael@0: LetterSpacing(nsIFrame* aFrame, const nsStyleText* aStyleText = nullptr) michael@0: { michael@0: if (aFrame->IsSVGText()) { michael@0: return 0; michael@0: } michael@0: if (!aStyleText) { michael@0: aStyleText = aFrame->StyleText(); michael@0: } michael@0: return StyleToCoord(aStyleText->mLetterSpacing); michael@0: } michael@0: michael@0: static nscoord michael@0: WordSpacing(nsIFrame* aFrame, const nsStyleText* aStyleText = nullptr) michael@0: { michael@0: if (aFrame->IsSVGText()) { michael@0: return 0; michael@0: } michael@0: if (!aStyleText) { michael@0: aStyleText = aFrame->StyleText(); michael@0: } michael@0: return aStyleText->mWordSpacing; michael@0: } michael@0: michael@0: bool michael@0: BuildTextRunsScanner::ContinueTextRunAcrossFrames(nsTextFrame* aFrame1, nsTextFrame* aFrame2) michael@0: { michael@0: // We don't need to check font size inflation, since michael@0: // |FindLineContainer| above (via |nsIFrame::CanContinueTextRun|) michael@0: // ensures that text runs never cross block boundaries. This means michael@0: // that the font size inflation on all text frames in the text run is michael@0: // already guaranteed to be the same as each other (and for the line michael@0: // container). michael@0: if (mBidiEnabled && michael@0: (NS_GET_EMBEDDING_LEVEL(aFrame1) != NS_GET_EMBEDDING_LEVEL(aFrame2) || michael@0: NS_GET_PARAGRAPH_DEPTH(aFrame1) != NS_GET_PARAGRAPH_DEPTH(aFrame2))) michael@0: return false; michael@0: michael@0: nsStyleContext* sc1 = aFrame1->StyleContext(); michael@0: const nsStyleText* textStyle1 = sc1->StyleText(); michael@0: // If the first frame ends in a preformatted newline, then we end the textrun michael@0: // here. This avoids creating giant textruns for an entire plain text file. michael@0: // Note that we create a single text frame for a preformatted text node, michael@0: // even if it has newlines in it, so typically we won't see trailing newlines michael@0: // until after reflow has broken up the frame into one (or more) frames per michael@0: // line. That's OK though. michael@0: if (textStyle1->NewlineIsSignificant() && HasTerminalNewline(aFrame1)) michael@0: return false; michael@0: michael@0: if (aFrame1->GetContent() == aFrame2->GetContent() && michael@0: aFrame1->GetNextInFlow() != aFrame2) { michael@0: // aFrame2 must be a non-fluid continuation of aFrame1. This can happen michael@0: // sometimes when the unicode-bidi property is used; the bidi resolver michael@0: // breaks text into different frames even though the text has the same michael@0: // direction. We can't allow these two frames to share the same textrun michael@0: // because that would violate our invariant that two flows in the same michael@0: // textrun have different content elements. michael@0: return false; michael@0: } michael@0: michael@0: nsStyleContext* sc2 = aFrame2->StyleContext(); michael@0: const nsStyleText* textStyle2 = sc2->StyleText(); michael@0: if (sc1 == sc2) michael@0: return true; michael@0: michael@0: const nsStyleFont* fontStyle1 = sc1->StyleFont(); michael@0: const nsStyleFont* fontStyle2 = sc2->StyleFont(); michael@0: nscoord letterSpacing1 = LetterSpacing(aFrame1); michael@0: nscoord letterSpacing2 = LetterSpacing(aFrame2); michael@0: return fontStyle1->mFont.BaseEquals(fontStyle2->mFont) && michael@0: sc1->StyleFont()->mLanguage == sc2->StyleFont()->mLanguage && michael@0: textStyle1->mTextTransform == textStyle2->mTextTransform && michael@0: nsLayoutUtils::GetTextRunFlagsForStyle(sc1, fontStyle1, textStyle1, letterSpacing1) == michael@0: nsLayoutUtils::GetTextRunFlagsForStyle(sc2, fontStyle2, textStyle2, letterSpacing2); michael@0: } michael@0: michael@0: void BuildTextRunsScanner::ScanFrame(nsIFrame* aFrame) michael@0: { michael@0: // First check if we can extend the current mapped frame block. This is common. michael@0: if (mMappedFlows.Length() > 0) { michael@0: MappedFlow* mappedFlow = &mMappedFlows[mMappedFlows.Length() - 1]; michael@0: if (mappedFlow->mEndFrame == aFrame && michael@0: (aFrame->GetStateBits() & NS_FRAME_IS_FLUID_CONTINUATION)) { michael@0: NS_ASSERTION(aFrame->GetType() == nsGkAtoms::textFrame, michael@0: "Flow-sibling of a text frame is not a text frame?"); michael@0: michael@0: // Don't do this optimization if mLastFrame has a terminal newline... michael@0: // it's quite likely preformatted and we might want to end the textrun here. michael@0: // This is almost always true: michael@0: if (mLastFrame->StyleContext() == aFrame->StyleContext() && michael@0: !HasTerminalNewline(mLastFrame)) { michael@0: AccumulateRunInfo(static_cast(aFrame)); michael@0: return; michael@0: } michael@0: } michael@0: } michael@0: michael@0: nsIAtom* frameType = aFrame->GetType(); michael@0: // Now see if we can add a new set of frames to the current textrun michael@0: if (frameType == nsGkAtoms::textFrame) { michael@0: nsTextFrame* frame = static_cast(aFrame); michael@0: michael@0: if (mLastFrame) { michael@0: if (!ContinueTextRunAcrossFrames(mLastFrame, frame)) { michael@0: FlushFrames(false, false); michael@0: } else { michael@0: if (mLastFrame->GetContent() == frame->GetContent()) { michael@0: AccumulateRunInfo(frame); michael@0: return; michael@0: } michael@0: } michael@0: } michael@0: michael@0: MappedFlow* mappedFlow = mMappedFlows.AppendElement(); michael@0: if (!mappedFlow) michael@0: return; michael@0: michael@0: mappedFlow->mStartFrame = frame; michael@0: mappedFlow->mAncestorControllingInitialBreak = mCommonAncestorWithLastFrame; michael@0: michael@0: AccumulateRunInfo(frame); michael@0: if (mMappedFlows.Length() == 1) { michael@0: mCurrentFramesAllSameTextRun = frame->GetTextRun(mWhichTextRun); michael@0: mCurrentRunContextInfo = mNextRunContextInfo; michael@0: } michael@0: return; michael@0: } michael@0: michael@0: FrameTextTraversal traversal = michael@0: CanTextCrossFrameBoundary(aFrame, frameType); michael@0: bool isBR = frameType == nsGkAtoms::brFrame; michael@0: if (!traversal.mLineBreakerCanCrossFrameBoundary) { michael@0: // BR frames are special. We do not need or want to record a break opportunity michael@0: // before a BR frame. michael@0: FlushFrames(true, isBR); michael@0: mCommonAncestorWithLastFrame = aFrame; michael@0: mNextRunContextInfo &= ~nsTextFrameUtils::INCOMING_WHITESPACE; michael@0: mStartOfLine = false; michael@0: } else if (!traversal.mTextRunCanCrossFrameBoundary) { michael@0: FlushFrames(false, false); michael@0: } michael@0: michael@0: for (nsIFrame* f = traversal.NextFrameToScan(); f; michael@0: f = traversal.NextFrameToScan()) { michael@0: ScanFrame(f); michael@0: } michael@0: michael@0: if (!traversal.mLineBreakerCanCrossFrameBoundary) { michael@0: // Really if we're a BR frame this is unnecessary since descendInto will be michael@0: // false. In fact this whole "if" statement should move into the descendInto. michael@0: FlushFrames(true, isBR); michael@0: mCommonAncestorWithLastFrame = aFrame; michael@0: mNextRunContextInfo &= ~nsTextFrameUtils::INCOMING_WHITESPACE; michael@0: } else if (!traversal.mTextRunCanCrossFrameBoundary) { michael@0: FlushFrames(false, false); michael@0: } michael@0: michael@0: LiftCommonAncestorWithLastFrameToParent(aFrame->GetParent()); michael@0: } michael@0: michael@0: nsTextFrame* michael@0: BuildTextRunsScanner::GetNextBreakBeforeFrame(uint32_t* aIndex) michael@0: { michael@0: uint32_t index = *aIndex; michael@0: if (index >= mLineBreakBeforeFrames.Length()) michael@0: return nullptr; michael@0: *aIndex = index + 1; michael@0: return static_cast(mLineBreakBeforeFrames.ElementAt(index)); michael@0: } michael@0: michael@0: static uint32_t michael@0: GetSpacingFlags(nscoord spacing) michael@0: { michael@0: return spacing ? gfxTextRunFactory::TEXT_ENABLE_SPACING : 0; michael@0: } michael@0: michael@0: static gfxFontGroup* michael@0: GetFontGroupForFrame(nsIFrame* aFrame, float aFontSizeInflation, michael@0: nsFontMetrics** aOutFontMetrics = nullptr) michael@0: { michael@0: if (aOutFontMetrics) michael@0: *aOutFontMetrics = nullptr; michael@0: michael@0: nsRefPtr metrics; michael@0: nsLayoutUtils::GetFontMetricsForFrame(aFrame, getter_AddRefs(metrics), michael@0: aFontSizeInflation); michael@0: michael@0: if (!metrics) michael@0: return nullptr; michael@0: michael@0: if (aOutFontMetrics) { michael@0: *aOutFontMetrics = metrics; michael@0: NS_ADDREF(*aOutFontMetrics); michael@0: } michael@0: // XXX this is a bit bogus, we're releasing 'metrics' so the michael@0: // returned font-group might actually be torn down, although because michael@0: // of the way the device context caches font metrics, this seems to michael@0: // not actually happen. But we should fix this. michael@0: return metrics->GetThebesFontGroup(); michael@0: } michael@0: michael@0: static already_AddRefed michael@0: CreateReferenceThebesContext(nsTextFrame* aTextFrame) michael@0: { michael@0: nsRefPtr tmp = michael@0: aTextFrame->PresContext()->PresShell()->CreateReferenceRenderingContext(); michael@0: michael@0: nsRefPtr ctx = tmp->ThebesContext(); michael@0: return ctx.forget(); michael@0: } michael@0: michael@0: /** michael@0: * The returned textrun must be deleted when no longer needed. michael@0: */ michael@0: static gfxTextRun* michael@0: GetHyphenTextRun(gfxTextRun* aTextRun, gfxContext* aContext, nsTextFrame* aTextFrame) michael@0: { michael@0: nsRefPtr ctx = aContext; michael@0: if (!ctx) { michael@0: ctx = CreateReferenceThebesContext(aTextFrame); michael@0: } michael@0: if (!ctx) michael@0: return nullptr; michael@0: michael@0: return aTextRun->GetFontGroup()-> michael@0: MakeHyphenTextRun(ctx, aTextRun->GetAppUnitsPerDevUnit()); michael@0: } michael@0: michael@0: static gfxFont::Metrics michael@0: GetFirstFontMetrics(gfxFontGroup* aFontGroup) michael@0: { michael@0: if (!aFontGroup) michael@0: return gfxFont::Metrics(); michael@0: gfxFont* font = aFontGroup->GetFontAt(0); michael@0: if (!font) michael@0: return gfxFont::Metrics(); michael@0: return font->GetMetrics(); michael@0: } michael@0: michael@0: PR_STATIC_ASSERT(NS_STYLE_WHITESPACE_NORMAL == 0); michael@0: PR_STATIC_ASSERT(NS_STYLE_WHITESPACE_PRE == 1); michael@0: PR_STATIC_ASSERT(NS_STYLE_WHITESPACE_NOWRAP == 2); michael@0: PR_STATIC_ASSERT(NS_STYLE_WHITESPACE_PRE_WRAP == 3); michael@0: PR_STATIC_ASSERT(NS_STYLE_WHITESPACE_PRE_LINE == 4); michael@0: PR_STATIC_ASSERT(NS_STYLE_WHITESPACE_PRE_DISCARD_NEWLINES == 5); michael@0: michael@0: static const nsTextFrameUtils::CompressionMode CSSWhitespaceToCompressionMode[] = michael@0: { michael@0: nsTextFrameUtils::COMPRESS_WHITESPACE_NEWLINE, // normal michael@0: nsTextFrameUtils::COMPRESS_NONE, // pre michael@0: nsTextFrameUtils::COMPRESS_WHITESPACE_NEWLINE, // nowrap michael@0: nsTextFrameUtils::COMPRESS_NONE, // pre-wrap michael@0: nsTextFrameUtils::COMPRESS_WHITESPACE, // pre-line michael@0: nsTextFrameUtils::DISCARD_NEWLINE // -moz-pre-discard-newlines michael@0: }; michael@0: michael@0: gfxTextRun* michael@0: BuildTextRunsScanner::BuildTextRunForFrames(void* aTextBuffer) michael@0: { michael@0: gfxSkipChars skipChars; michael@0: michael@0: const void* textPtr = aTextBuffer; michael@0: bool anySmallcapsStyle = false; michael@0: bool anyTextTransformStyle = false; michael@0: bool anyMathMLStyling = false; michael@0: uint8_t sstyScriptLevel = 0; michael@0: uint32_t textFlags = nsTextFrameUtils::TEXT_NO_BREAKS; michael@0: michael@0: if (mCurrentRunContextInfo & nsTextFrameUtils::INCOMING_WHITESPACE) { michael@0: textFlags |= nsTextFrameUtils::TEXT_INCOMING_WHITESPACE; michael@0: } michael@0: if (mCurrentRunContextInfo & nsTextFrameUtils::INCOMING_ARABICCHAR) { michael@0: textFlags |= gfxTextRunFactory::TEXT_INCOMING_ARABICCHAR; michael@0: } michael@0: michael@0: nsAutoTArray textBreakPoints; michael@0: TextRunUserData dummyData; michael@0: TextRunMappedFlow dummyMappedFlow; michael@0: michael@0: TextRunUserData* userData; michael@0: TextRunUserData* userDataToDestroy; michael@0: // If the situation is particularly simple (and common) we don't need to michael@0: // allocate userData. michael@0: if (mMappedFlows.Length() == 1 && !mMappedFlows[0].mEndFrame && michael@0: mMappedFlows[0].mStartFrame->GetContentOffset() == 0) { michael@0: userData = &dummyData; michael@0: userDataToDestroy = nullptr; michael@0: dummyData.mMappedFlows = &dummyMappedFlow; michael@0: } else { michael@0: userData = static_cast michael@0: (nsMemory::Alloc(sizeof(TextRunUserData) + mMappedFlows.Length()*sizeof(TextRunMappedFlow))); michael@0: userDataToDestroy = userData; michael@0: userData->mMappedFlows = reinterpret_cast(userData + 1); michael@0: } michael@0: userData->mMappedFlowCount = mMappedFlows.Length(); michael@0: userData->mLastFlowIndex = 0; michael@0: michael@0: uint32_t currentTransformedTextOffset = 0; michael@0: michael@0: uint32_t nextBreakIndex = 0; michael@0: nsTextFrame* nextBreakBeforeFrame = GetNextBreakBeforeFrame(&nextBreakIndex); michael@0: bool enabledJustification = mLineContainer && michael@0: (mLineContainer->StyleText()->mTextAlign == NS_STYLE_TEXT_ALIGN_JUSTIFY || michael@0: mLineContainer->StyleText()->mTextAlignLast == NS_STYLE_TEXT_ALIGN_JUSTIFY) && michael@0: !mLineContainer->IsSVGText(); michael@0: michael@0: // for word-break style michael@0: switch (mLineContainer->StyleText()->mWordBreak) { michael@0: case NS_STYLE_WORDBREAK_BREAK_ALL: michael@0: mLineBreaker.SetWordBreak(nsILineBreaker::kWordBreak_BreakAll); michael@0: break; michael@0: case NS_STYLE_WORDBREAK_KEEP_ALL: michael@0: mLineBreaker.SetWordBreak(nsILineBreaker::kWordBreak_KeepAll); michael@0: break; michael@0: default: michael@0: mLineBreaker.SetWordBreak(nsILineBreaker::kWordBreak_Normal); michael@0: break; michael@0: } michael@0: michael@0: const nsStyleText* textStyle = nullptr; michael@0: const nsStyleFont* fontStyle = nullptr; michael@0: nsStyleContext* lastStyleContext = nullptr; michael@0: for (uint32_t i = 0; i < mMappedFlows.Length(); ++i) { michael@0: MappedFlow* mappedFlow = &mMappedFlows[i]; michael@0: nsTextFrame* f = mappedFlow->mStartFrame; michael@0: michael@0: lastStyleContext = f->StyleContext(); michael@0: // Detect use of text-transform or font-variant anywhere in the run michael@0: textStyle = f->StyleText(); michael@0: if (NS_STYLE_TEXT_TRANSFORM_NONE != textStyle->mTextTransform) { michael@0: anyTextTransformStyle = true; michael@0: } michael@0: textFlags |= GetSpacingFlags(LetterSpacing(f)); michael@0: textFlags |= GetSpacingFlags(WordSpacing(f)); michael@0: nsTextFrameUtils::CompressionMode compression = michael@0: CSSWhitespaceToCompressionMode[textStyle->mWhiteSpace]; michael@0: if (enabledJustification && !textStyle->WhiteSpaceIsSignificant()) { michael@0: textFlags |= gfxTextRunFactory::TEXT_ENABLE_SPACING; michael@0: } michael@0: fontStyle = f->StyleFont(); michael@0: if (NS_STYLE_FONT_VARIANT_SMALL_CAPS == fontStyle->mFont.variant) { michael@0: anySmallcapsStyle = true; michael@0: } michael@0: if (NS_MATHML_MATHVARIANT_NONE != fontStyle->mMathVariant) { michael@0: anyMathMLStyling = true; michael@0: } else if (mLineContainer->GetStateBits() & NS_FRAME_IS_IN_SINGLE_CHAR_MI) { michael@0: textFlags |= nsTextFrameUtils::TEXT_IS_SINGLE_CHAR_MI; michael@0: anyMathMLStyling = true; michael@0: } michael@0: nsIFrame* parent = mLineContainer->GetParent(); michael@0: if (mLineContainer->HasAnyStateBits(TEXT_IS_IN_TOKEN_MATHML)) { michael@0: // All MathML tokens except use 'math' script. michael@0: if (!(parent && parent->GetContent() && michael@0: parent->GetContent()->Tag() == nsGkAtoms::mtext_)) { michael@0: textFlags |= gfxTextRunFactory::TEXT_USE_MATH_SCRIPT; michael@0: } michael@0: } michael@0: nsIFrame* child = mLineContainer; michael@0: uint8_t oldScriptLevel = 0; michael@0: while (parent && michael@0: child->HasAnyStateBits(NS_FRAME_MATHML_SCRIPT_DESCENDANT)) { michael@0: // Reconstruct the script level ignoring any user overrides. It is michael@0: // calculated this way instead of using scriptlevel to ensure the michael@0: // correct ssty font feature setting is used even if the user sets a michael@0: // different (especially negative) scriptlevel. michael@0: nsIMathMLFrame* mathFrame= do_QueryFrame(parent); michael@0: if (mathFrame) { michael@0: sstyScriptLevel += mathFrame->ScriptIncrement(child); michael@0: } michael@0: if (sstyScriptLevel < oldScriptLevel) { michael@0: // overflow michael@0: sstyScriptLevel = UINT8_MAX; michael@0: break; michael@0: } michael@0: child = parent; michael@0: parent = parent->GetParent(); michael@0: oldScriptLevel = sstyScriptLevel; michael@0: } michael@0: if (sstyScriptLevel) { michael@0: anyMathMLStyling = true; michael@0: } michael@0: michael@0: // Figure out what content is included in this flow. michael@0: nsIContent* content = f->GetContent(); michael@0: const nsTextFragment* frag = content->GetText(); michael@0: int32_t contentStart = mappedFlow->mStartFrame->GetContentOffset(); michael@0: int32_t contentEnd = mappedFlow->GetContentEnd(); michael@0: int32_t contentLength = contentEnd - contentStart; michael@0: michael@0: TextRunMappedFlow* newFlow = &userData->mMappedFlows[i]; michael@0: newFlow->mStartFrame = mappedFlow->mStartFrame; michael@0: newFlow->mDOMOffsetToBeforeTransformOffset = skipChars.GetOriginalCharCount() - michael@0: mappedFlow->mStartFrame->GetContentOffset(); michael@0: newFlow->mContentLength = contentLength; michael@0: michael@0: while (nextBreakBeforeFrame && nextBreakBeforeFrame->GetContent() == content) { michael@0: textBreakPoints.AppendElement( michael@0: nextBreakBeforeFrame->GetContentOffset() + newFlow->mDOMOffsetToBeforeTransformOffset); michael@0: nextBreakBeforeFrame = GetNextBreakBeforeFrame(&nextBreakIndex); michael@0: } michael@0: michael@0: uint32_t analysisFlags; michael@0: if (frag->Is2b()) { michael@0: NS_ASSERTION(mDoubleByteText, "Wrong buffer char size!"); michael@0: char16_t* bufStart = static_cast(aTextBuffer); michael@0: char16_t* bufEnd = nsTextFrameUtils::TransformText( michael@0: frag->Get2b() + contentStart, contentLength, bufStart, michael@0: compression, &mNextRunContextInfo, &skipChars, &analysisFlags); michael@0: aTextBuffer = bufEnd; michael@0: currentTransformedTextOffset = bufEnd - static_cast(textPtr); michael@0: } else { michael@0: if (mDoubleByteText) { michael@0: // Need to expand the text. First transform it into a temporary buffer, michael@0: // then expand. michael@0: AutoFallibleTArray tempBuf; michael@0: uint8_t* bufStart = tempBuf.AppendElements(contentLength); michael@0: if (!bufStart) { michael@0: DestroyUserData(userDataToDestroy); michael@0: return nullptr; michael@0: } michael@0: uint8_t* end = nsTextFrameUtils::TransformText( michael@0: reinterpret_cast(frag->Get1b()) + contentStart, contentLength, michael@0: bufStart, compression, &mNextRunContextInfo, &skipChars, &analysisFlags); michael@0: aTextBuffer = ExpandBuffer(static_cast(aTextBuffer), michael@0: tempBuf.Elements(), end - tempBuf.Elements()); michael@0: currentTransformedTextOffset = michael@0: static_cast(aTextBuffer) - static_cast(textPtr); michael@0: } else { michael@0: uint8_t* bufStart = static_cast(aTextBuffer); michael@0: uint8_t* end = nsTextFrameUtils::TransformText( michael@0: reinterpret_cast(frag->Get1b()) + contentStart, contentLength, michael@0: bufStart, compression, &mNextRunContextInfo, &skipChars, &analysisFlags); michael@0: aTextBuffer = end; michael@0: currentTransformedTextOffset = end - static_cast(textPtr); michael@0: } michael@0: } michael@0: textFlags |= analysisFlags; michael@0: } michael@0: michael@0: void* finalUserData; michael@0: if (userData == &dummyData) { michael@0: textFlags |= nsTextFrameUtils::TEXT_IS_SIMPLE_FLOW; michael@0: userData = nullptr; michael@0: finalUserData = mMappedFlows[0].mStartFrame; michael@0: } else { michael@0: finalUserData = userData; michael@0: } michael@0: michael@0: uint32_t transformedLength = currentTransformedTextOffset; michael@0: michael@0: // Now build the textrun michael@0: nsTextFrame* firstFrame = mMappedFlows[0].mStartFrame; michael@0: float fontInflation; michael@0: if (mWhichTextRun == nsTextFrame::eNotInflated) { michael@0: fontInflation = 1.0f; michael@0: } else { michael@0: fontInflation = nsLayoutUtils::FontSizeInflationFor(firstFrame); michael@0: } michael@0: michael@0: gfxFontGroup* fontGroup = GetFontGroupForFrame(firstFrame, fontInflation); michael@0: if (!fontGroup) { michael@0: DestroyUserData(userDataToDestroy); michael@0: return nullptr; michael@0: } michael@0: michael@0: if (textFlags & nsTextFrameUtils::TEXT_HAS_TAB) { michael@0: textFlags |= gfxTextRunFactory::TEXT_ENABLE_SPACING; michael@0: } michael@0: if (textFlags & nsTextFrameUtils::TEXT_HAS_SHY) { michael@0: textFlags |= gfxTextRunFactory::TEXT_ENABLE_HYPHEN_BREAKS; michael@0: } michael@0: if (mBidiEnabled && (NS_GET_EMBEDDING_LEVEL(firstFrame) & 1)) { michael@0: textFlags |= gfxTextRunFactory::TEXT_IS_RTL; michael@0: } michael@0: if (mNextRunContextInfo & nsTextFrameUtils::INCOMING_WHITESPACE) { michael@0: textFlags |= nsTextFrameUtils::TEXT_TRAILING_WHITESPACE; michael@0: } michael@0: if (mNextRunContextInfo & nsTextFrameUtils::INCOMING_ARABICCHAR) { michael@0: textFlags |= gfxTextRunFactory::TEXT_TRAILING_ARABICCHAR; michael@0: } michael@0: // ContinueTextRunAcrossFrames guarantees that it doesn't matter which michael@0: // frame's style is used, so we use a mixture of the first frame and michael@0: // last frame's style michael@0: textFlags |= nsLayoutUtils::GetTextRunFlagsForStyle(lastStyleContext, michael@0: fontStyle, textStyle, LetterSpacing(firstFrame, textStyle)); michael@0: // XXX this is a bit of a hack. For performance reasons, if we're favouring michael@0: // performance over quality, don't try to get accurate glyph extents. michael@0: if (!(textFlags & gfxTextRunFactory::TEXT_OPTIMIZE_SPEED)) { michael@0: textFlags |= gfxTextRunFactory::TEXT_NEED_BOUNDING_BOX; michael@0: } michael@0: michael@0: // Convert linebreak coordinates to transformed string offsets michael@0: NS_ASSERTION(nextBreakIndex == mLineBreakBeforeFrames.Length(), michael@0: "Didn't find all the frames to break-before..."); michael@0: gfxSkipCharsIterator iter(skipChars); michael@0: nsAutoTArray textBreakPointsAfterTransform; michael@0: for (uint32_t i = 0; i < textBreakPoints.Length(); ++i) { michael@0: nsTextFrameUtils::AppendLineBreakOffset(&textBreakPointsAfterTransform, michael@0: iter.ConvertOriginalToSkipped(textBreakPoints[i])); michael@0: } michael@0: if (mStartOfLine) { michael@0: nsTextFrameUtils::AppendLineBreakOffset(&textBreakPointsAfterTransform, michael@0: transformedLength); michael@0: } michael@0: michael@0: // Setup factory chain michael@0: nsAutoPtr transformingFactory; michael@0: if (anySmallcapsStyle) { michael@0: transformingFactory = new nsFontVariantTextRunFactory(); michael@0: } michael@0: if (anyTextTransformStyle) { michael@0: transformingFactory = michael@0: new nsCaseTransformTextRunFactory(transformingFactory.forget()); michael@0: } michael@0: if (anyMathMLStyling) { michael@0: transformingFactory = michael@0: new MathMLTextRunFactory(transformingFactory.forget(), sstyScriptLevel); michael@0: } michael@0: nsTArray styles; michael@0: if (transformingFactory) { michael@0: iter.SetOriginalOffset(0); michael@0: for (uint32_t i = 0; i < mMappedFlows.Length(); ++i) { michael@0: MappedFlow* mappedFlow = &mMappedFlows[i]; michael@0: nsTextFrame* f; michael@0: for (f = mappedFlow->mStartFrame; f != mappedFlow->mEndFrame; michael@0: f = static_cast(f->GetNextContinuation())) { michael@0: uint32_t offset = iter.GetSkippedOffset(); michael@0: iter.AdvanceOriginal(f->GetContentLength()); michael@0: uint32_t end = iter.GetSkippedOffset(); michael@0: nsStyleContext* sc = f->StyleContext(); michael@0: uint32_t j; michael@0: for (j = offset; j < end; ++j) { michael@0: styles.AppendElement(sc); michael@0: } michael@0: } michael@0: } michael@0: textFlags |= nsTextFrameUtils::TEXT_IS_TRANSFORMED; michael@0: NS_ASSERTION(iter.GetSkippedOffset() == transformedLength, michael@0: "We didn't cover all the characters in the text run!"); michael@0: } michael@0: michael@0: gfxTextRun* textRun; michael@0: gfxTextRunFactory::Parameters params = michael@0: { mContext, finalUserData, &skipChars, michael@0: textBreakPointsAfterTransform.Elements(), michael@0: textBreakPointsAfterTransform.Length(), michael@0: int32_t(firstFrame->PresContext()->AppUnitsPerDevPixel())}; michael@0: michael@0: if (mDoubleByteText) { michael@0: const char16_t* text = static_cast(textPtr); michael@0: if (transformingFactory) { michael@0: textRun = transformingFactory->MakeTextRun(text, transformedLength, ¶ms, michael@0: fontGroup, textFlags, styles.Elements()); michael@0: if (textRun) { michael@0: // ownership of the factory has passed to the textrun michael@0: transformingFactory.forget(); michael@0: } michael@0: } else { michael@0: textRun = MakeTextRun(text, transformedLength, fontGroup, ¶ms, textFlags); michael@0: } michael@0: } else { michael@0: const uint8_t* text = static_cast(textPtr); michael@0: textFlags |= gfxFontGroup::TEXT_IS_8BIT; michael@0: if (transformingFactory) { michael@0: textRun = transformingFactory->MakeTextRun(text, transformedLength, ¶ms, michael@0: fontGroup, textFlags, styles.Elements()); michael@0: if (textRun) { michael@0: // ownership of the factory has passed to the textrun michael@0: transformingFactory.forget(); michael@0: } michael@0: } else { michael@0: textRun = MakeTextRun(text, transformedLength, fontGroup, ¶ms, textFlags); michael@0: } michael@0: } michael@0: if (!textRun) { michael@0: DestroyUserData(userDataToDestroy); michael@0: return nullptr; michael@0: } michael@0: michael@0: // We have to set these up after we've created the textrun, because michael@0: // the breaks may be stored in the textrun during this very call. michael@0: // This is a bit annoying because it requires another loop over the frames michael@0: // making up the textrun, but I don't see a way to avoid this. michael@0: uint32_t flags = 0; michael@0: if (mDoubleByteText) { michael@0: flags |= SBS_DOUBLE_BYTE; michael@0: } michael@0: if (mSkipIncompleteTextRuns) { michael@0: flags |= SBS_SUPPRESS_SINK; michael@0: } michael@0: SetupBreakSinksForTextRun(textRun, textPtr, flags); michael@0: michael@0: if (mSkipIncompleteTextRuns) { michael@0: mSkipIncompleteTextRuns = !TextContainsLineBreakerWhiteSpace(textPtr, michael@0: transformedLength, mDoubleByteText); michael@0: // Arrange for this textrun to be deleted the next time the linebreaker michael@0: // is flushed out michael@0: mTextRunsToDelete.AppendElement(textRun); michael@0: // Since we're doing to destroy the user data now, avoid a dangling michael@0: // pointer. Strictly speaking we don't need to do this since it should michael@0: // not be used (since this textrun will not be used and will be michael@0: // itself deleted soon), but it's always better to not have dangling michael@0: // pointers around. michael@0: textRun->SetUserData(nullptr); michael@0: DestroyUserData(userDataToDestroy); michael@0: return nullptr; michael@0: } michael@0: michael@0: // Actually wipe out the textruns associated with the mapped frames and associate michael@0: // those frames with this text run. michael@0: AssignTextRun(textRun, fontInflation); michael@0: return textRun; michael@0: } michael@0: michael@0: // This is a cut-down version of BuildTextRunForFrames used to set up michael@0: // context for the line-breaker, when the textrun has already been created. michael@0: // So it does the same walk over the mMappedFlows, but doesn't actually michael@0: // build a new textrun. michael@0: bool michael@0: BuildTextRunsScanner::SetupLineBreakerContext(gfxTextRun *aTextRun) michael@0: { michael@0: AutoFallibleTArray buffer; michael@0: uint32_t bufferSize = mMaxTextLength*(mDoubleByteText ? 2 : 1); michael@0: if (bufferSize < mMaxTextLength || bufferSize == UINT32_MAX) { michael@0: return false; michael@0: } michael@0: void *textPtr = buffer.AppendElements(bufferSize); michael@0: if (!textPtr) { michael@0: return false; michael@0: } michael@0: michael@0: gfxSkipChars skipChars; michael@0: michael@0: nsAutoTArray textBreakPoints; michael@0: TextRunUserData dummyData; michael@0: TextRunMappedFlow dummyMappedFlow; michael@0: michael@0: TextRunUserData* userData; michael@0: TextRunUserData* userDataToDestroy; michael@0: // If the situation is particularly simple (and common) we don't need to michael@0: // allocate userData. michael@0: if (mMappedFlows.Length() == 1 && !mMappedFlows[0].mEndFrame && michael@0: mMappedFlows[0].mStartFrame->GetContentOffset() == 0) { michael@0: userData = &dummyData; michael@0: userDataToDestroy = nullptr; michael@0: dummyData.mMappedFlows = &dummyMappedFlow; michael@0: } else { michael@0: userData = static_cast michael@0: (nsMemory::Alloc(sizeof(TextRunUserData) + mMappedFlows.Length()*sizeof(TextRunMappedFlow))); michael@0: userDataToDestroy = userData; michael@0: userData->mMappedFlows = reinterpret_cast(userData + 1); michael@0: } michael@0: userData->mMappedFlowCount = mMappedFlows.Length(); michael@0: userData->mLastFlowIndex = 0; michael@0: michael@0: uint32_t nextBreakIndex = 0; michael@0: nsTextFrame* nextBreakBeforeFrame = GetNextBreakBeforeFrame(&nextBreakIndex); michael@0: michael@0: const nsStyleText* textStyle = nullptr; michael@0: for (uint32_t i = 0; i < mMappedFlows.Length(); ++i) { michael@0: MappedFlow* mappedFlow = &mMappedFlows[i]; michael@0: nsTextFrame* f = mappedFlow->mStartFrame; michael@0: michael@0: textStyle = f->StyleText(); michael@0: nsTextFrameUtils::CompressionMode compression = michael@0: CSSWhitespaceToCompressionMode[textStyle->mWhiteSpace]; michael@0: michael@0: // Figure out what content is included in this flow. michael@0: nsIContent* content = f->GetContent(); michael@0: const nsTextFragment* frag = content->GetText(); michael@0: int32_t contentStart = mappedFlow->mStartFrame->GetContentOffset(); michael@0: int32_t contentEnd = mappedFlow->GetContentEnd(); michael@0: int32_t contentLength = contentEnd - contentStart; michael@0: michael@0: TextRunMappedFlow* newFlow = &userData->mMappedFlows[i]; michael@0: newFlow->mStartFrame = mappedFlow->mStartFrame; michael@0: newFlow->mDOMOffsetToBeforeTransformOffset = skipChars.GetOriginalCharCount() - michael@0: mappedFlow->mStartFrame->GetContentOffset(); michael@0: newFlow->mContentLength = contentLength; michael@0: michael@0: while (nextBreakBeforeFrame && nextBreakBeforeFrame->GetContent() == content) { michael@0: textBreakPoints.AppendElement( michael@0: nextBreakBeforeFrame->GetContentOffset() + newFlow->mDOMOffsetToBeforeTransformOffset); michael@0: nextBreakBeforeFrame = GetNextBreakBeforeFrame(&nextBreakIndex); michael@0: } michael@0: michael@0: uint32_t analysisFlags; michael@0: if (frag->Is2b()) { michael@0: NS_ASSERTION(mDoubleByteText, "Wrong buffer char size!"); michael@0: char16_t* bufStart = static_cast(textPtr); michael@0: char16_t* bufEnd = nsTextFrameUtils::TransformText( michael@0: frag->Get2b() + contentStart, contentLength, bufStart, michael@0: compression, &mNextRunContextInfo, &skipChars, &analysisFlags); michael@0: textPtr = bufEnd; michael@0: } else { michael@0: if (mDoubleByteText) { michael@0: // Need to expand the text. First transform it into a temporary buffer, michael@0: // then expand. michael@0: AutoFallibleTArray tempBuf; michael@0: uint8_t* bufStart = tempBuf.AppendElements(contentLength); michael@0: if (!bufStart) { michael@0: DestroyUserData(userDataToDestroy); michael@0: return false; michael@0: } michael@0: uint8_t* end = nsTextFrameUtils::TransformText( michael@0: reinterpret_cast(frag->Get1b()) + contentStart, contentLength, michael@0: bufStart, compression, &mNextRunContextInfo, &skipChars, &analysisFlags); michael@0: textPtr = ExpandBuffer(static_cast(textPtr), michael@0: tempBuf.Elements(), end - tempBuf.Elements()); michael@0: } else { michael@0: uint8_t* bufStart = static_cast(textPtr); michael@0: uint8_t* end = nsTextFrameUtils::TransformText( michael@0: reinterpret_cast(frag->Get1b()) + contentStart, contentLength, michael@0: bufStart, compression, &mNextRunContextInfo, &skipChars, &analysisFlags); michael@0: textPtr = end; michael@0: } michael@0: } michael@0: } michael@0: michael@0: // We have to set these up after we've created the textrun, because michael@0: // the breaks may be stored in the textrun during this very call. michael@0: // This is a bit annoying because it requires another loop over the frames michael@0: // making up the textrun, but I don't see a way to avoid this. michael@0: uint32_t flags = 0; michael@0: if (mDoubleByteText) { michael@0: flags |= SBS_DOUBLE_BYTE; michael@0: } michael@0: if (mSkipIncompleteTextRuns) { michael@0: flags |= SBS_SUPPRESS_SINK; michael@0: } michael@0: SetupBreakSinksForTextRun(aTextRun, buffer.Elements(), flags); michael@0: michael@0: DestroyUserData(userDataToDestroy); michael@0: michael@0: return true; michael@0: } michael@0: michael@0: static bool michael@0: HasCompressedLeadingWhitespace(nsTextFrame* aFrame, const nsStyleText* aStyleText, michael@0: int32_t aContentEndOffset, michael@0: const gfxSkipCharsIterator& aIterator) michael@0: { michael@0: if (!aIterator.IsOriginalCharSkipped()) michael@0: return false; michael@0: michael@0: gfxSkipCharsIterator iter = aIterator; michael@0: int32_t frameContentOffset = aFrame->GetContentOffset(); michael@0: const nsTextFragment* frag = aFrame->GetContent()->GetText(); michael@0: while (frameContentOffset < aContentEndOffset && iter.IsOriginalCharSkipped()) { michael@0: if (IsTrimmableSpace(frag, frameContentOffset, aStyleText)) michael@0: return true; michael@0: ++frameContentOffset; michael@0: iter.AdvanceOriginal(1); michael@0: } michael@0: return false; michael@0: } michael@0: michael@0: void michael@0: BuildTextRunsScanner::SetupBreakSinksForTextRun(gfxTextRun* aTextRun, michael@0: const void* aTextPtr, michael@0: uint32_t aFlags) michael@0: { michael@0: // textruns have uniform language michael@0: const nsStyleFont *styleFont = mMappedFlows[0].mStartFrame->StyleFont(); michael@0: // We should only use a language for hyphenation if it was specified michael@0: // explicitly. michael@0: nsIAtom* hyphenationLanguage = michael@0: styleFont->mExplicitLanguage ? styleFont->mLanguage : nullptr; michael@0: // We keep this pointed at the skip-chars data for the current mappedFlow. michael@0: // This lets us cheaply check whether the flow has compressed initial michael@0: // whitespace... michael@0: gfxSkipCharsIterator iter(aTextRun->GetSkipChars()); michael@0: michael@0: for (uint32_t i = 0; i < mMappedFlows.Length(); ++i) { michael@0: MappedFlow* mappedFlow = &mMappedFlows[i]; michael@0: uint32_t offset = iter.GetSkippedOffset(); michael@0: gfxSkipCharsIterator iterNext = iter; michael@0: iterNext.AdvanceOriginal(mappedFlow->GetContentEnd() - michael@0: mappedFlow->mStartFrame->GetContentOffset()); michael@0: michael@0: nsAutoPtr* breakSink = mBreakSinks.AppendElement( michael@0: new BreakSink(aTextRun, mContext, offset, michael@0: (aFlags & SBS_EXISTING_TEXTRUN) != 0)); michael@0: if (!breakSink || !*breakSink) michael@0: return; michael@0: michael@0: uint32_t length = iterNext.GetSkippedOffset() - offset; michael@0: uint32_t flags = 0; michael@0: nsIFrame* initialBreakController = mappedFlow->mAncestorControllingInitialBreak; michael@0: if (!initialBreakController) { michael@0: initialBreakController = mLineContainer; michael@0: } michael@0: if (!initialBreakController->StyleText()-> michael@0: WhiteSpaceCanWrap(initialBreakController)) { michael@0: flags |= nsLineBreaker::BREAK_SUPPRESS_INITIAL; michael@0: } michael@0: nsTextFrame* startFrame = mappedFlow->mStartFrame; michael@0: const nsStyleText* textStyle = startFrame->StyleText(); michael@0: if (!textStyle->WhiteSpaceCanWrap(startFrame)) { michael@0: flags |= nsLineBreaker::BREAK_SUPPRESS_INSIDE; michael@0: } michael@0: if (aTextRun->GetFlags() & nsTextFrameUtils::TEXT_NO_BREAKS) { michael@0: flags |= nsLineBreaker::BREAK_SKIP_SETTING_NO_BREAKS; michael@0: } michael@0: if (textStyle->mTextTransform == NS_STYLE_TEXT_TRANSFORM_CAPITALIZE) { michael@0: flags |= nsLineBreaker::BREAK_NEED_CAPITALIZATION; michael@0: } michael@0: if (textStyle->mHyphens == NS_STYLE_HYPHENS_AUTO) { michael@0: flags |= nsLineBreaker::BREAK_USE_AUTO_HYPHENATION; michael@0: } michael@0: michael@0: if (HasCompressedLeadingWhitespace(startFrame, textStyle, michael@0: mappedFlow->GetContentEnd(), iter)) { michael@0: mLineBreaker.AppendInvisibleWhitespace(flags); michael@0: } michael@0: michael@0: if (length > 0) { michael@0: BreakSink* sink = michael@0: (aFlags & SBS_SUPPRESS_SINK) ? nullptr : (*breakSink).get(); michael@0: if (aFlags & SBS_DOUBLE_BYTE) { michael@0: const char16_t* text = reinterpret_cast(aTextPtr); michael@0: mLineBreaker.AppendText(hyphenationLanguage, text + offset, michael@0: length, flags, sink); michael@0: } else { michael@0: const uint8_t* text = reinterpret_cast(aTextPtr); michael@0: mLineBreaker.AppendText(hyphenationLanguage, text + offset, michael@0: length, flags, sink); michael@0: } michael@0: } michael@0: michael@0: iter = iterNext; michael@0: } michael@0: } michael@0: michael@0: // Find the flow corresponding to aContent in aUserData michael@0: static inline TextRunMappedFlow* michael@0: FindFlowForContent(TextRunUserData* aUserData, nsIContent* aContent) michael@0: { michael@0: // Find the flow that contains us michael@0: int32_t i = aUserData->mLastFlowIndex; michael@0: int32_t delta = 1; michael@0: int32_t sign = 1; michael@0: // Search starting at the current position and examine close-by michael@0: // positions first, moving further and further away as we go. michael@0: while (i >= 0 && uint32_t(i) < aUserData->mMappedFlowCount) { michael@0: TextRunMappedFlow* flow = &aUserData->mMappedFlows[i]; michael@0: if (flow->mStartFrame->GetContent() == aContent) { michael@0: return flow; michael@0: } michael@0: michael@0: i += delta; michael@0: sign = -sign; michael@0: delta = -delta + sign; michael@0: } michael@0: michael@0: // We ran into an array edge. Add |delta| to |i| once more to get michael@0: // back to the side where we still need to search, then step in michael@0: // the |sign| direction. michael@0: i += delta; michael@0: if (sign > 0) { michael@0: for (; i < int32_t(aUserData->mMappedFlowCount); ++i) { michael@0: TextRunMappedFlow* flow = &aUserData->mMappedFlows[i]; michael@0: if (flow->mStartFrame->GetContent() == aContent) { michael@0: return flow; michael@0: } michael@0: } michael@0: } else { michael@0: for (; i >= 0; --i) { michael@0: TextRunMappedFlow* flow = &aUserData->mMappedFlows[i]; michael@0: if (flow->mStartFrame->GetContent() == aContent) { michael@0: return flow; michael@0: } michael@0: } michael@0: } michael@0: michael@0: return nullptr; michael@0: } michael@0: michael@0: void michael@0: BuildTextRunsScanner::AssignTextRun(gfxTextRun* aTextRun, float aInflation) michael@0: { michael@0: for (uint32_t i = 0; i < mMappedFlows.Length(); ++i) { michael@0: MappedFlow* mappedFlow = &mMappedFlows[i]; michael@0: nsTextFrame* startFrame = mappedFlow->mStartFrame; michael@0: nsTextFrame* endFrame = mappedFlow->mEndFrame; michael@0: nsTextFrame* f; michael@0: for (f = startFrame; f != endFrame; michael@0: f = static_cast(f->GetNextContinuation())) { michael@0: #ifdef DEBUG_roc michael@0: if (f->GetTextRun(mWhichTextRun)) { michael@0: gfxTextRun* textRun = f->GetTextRun(mWhichTextRun); michael@0: if (textRun->GetFlags() & nsTextFrameUtils::TEXT_IS_SIMPLE_FLOW) { michael@0: if (mMappedFlows[0].mStartFrame != static_cast(textRun->GetUserData())) { michael@0: NS_WARNING("REASSIGNING SIMPLE FLOW TEXT RUN!"); michael@0: } michael@0: } else { michael@0: TextRunUserData* userData = michael@0: static_cast(textRun->GetUserData()); michael@0: michael@0: if (userData->mMappedFlowCount >= mMappedFlows.Length() || michael@0: userData->mMappedFlows[userData->mMappedFlowCount - 1].mStartFrame != michael@0: mMappedFlows[userData->mMappedFlowCount - 1].mStartFrame) { michael@0: NS_WARNING("REASSIGNING MULTIFLOW TEXT RUN (not append)!"); michael@0: } michael@0: } michael@0: } michael@0: #endif michael@0: michael@0: gfxTextRun* oldTextRun = f->GetTextRun(mWhichTextRun); michael@0: if (oldTextRun) { michael@0: nsTextFrame* firstFrame = nullptr; michael@0: uint32_t startOffset = 0; michael@0: if (oldTextRun->GetFlags() & nsTextFrameUtils::TEXT_IS_SIMPLE_FLOW) { michael@0: firstFrame = static_cast(oldTextRun->GetUserData()); michael@0: } michael@0: else { michael@0: TextRunUserData* userData = static_cast(oldTextRun->GetUserData()); michael@0: firstFrame = userData->mMappedFlows[0].mStartFrame; michael@0: if (MOZ_UNLIKELY(f != firstFrame)) { michael@0: TextRunMappedFlow* flow = FindFlowForContent(userData, f->GetContent()); michael@0: if (flow) { michael@0: startOffset = flow->mDOMOffsetToBeforeTransformOffset; michael@0: } michael@0: else { michael@0: NS_ERROR("Can't find flow containing frame 'f'"); michael@0: } michael@0: } michael@0: } michael@0: michael@0: // Optimization: if |f| is the first frame in the flow then there are no michael@0: // prev-continuations that use |oldTextRun|. michael@0: nsTextFrame* clearFrom = nullptr; michael@0: if (MOZ_UNLIKELY(f != firstFrame)) { michael@0: // If all the frames in the mapped flow starting at |f| (inclusive) michael@0: // are empty then we let the prev-continuations keep the old text run. michael@0: gfxSkipCharsIterator iter(oldTextRun->GetSkipChars(), startOffset, f->GetContentOffset()); michael@0: uint32_t textRunOffset = iter.ConvertOriginalToSkipped(f->GetContentOffset()); michael@0: clearFrom = textRunOffset == oldTextRun->GetLength() ? f : nullptr; michael@0: } michael@0: f->ClearTextRun(clearFrom, mWhichTextRun); michael@0: michael@0: #ifdef DEBUG michael@0: if (firstFrame && !firstFrame->GetTextRun(mWhichTextRun)) { michael@0: // oldTextRun was destroyed - assert that we don't reference it. michael@0: for (uint32_t j = 0; j < mBreakSinks.Length(); ++j) { michael@0: NS_ASSERTION(oldTextRun != mBreakSinks[j]->mTextRun, michael@0: "destroyed text run is still in use"); michael@0: } michael@0: } michael@0: #endif michael@0: } michael@0: f->SetTextRun(aTextRun, mWhichTextRun, aInflation); michael@0: } michael@0: // Set this bit now; we can't set it any earlier because michael@0: // f->ClearTextRun() might clear it out. michael@0: nsFrameState whichTextRunState = michael@0: startFrame->GetTextRun(nsTextFrame::eInflated) == aTextRun michael@0: ? TEXT_IN_TEXTRUN_USER_DATA michael@0: : TEXT_IN_UNINFLATED_TEXTRUN_USER_DATA; michael@0: startFrame->AddStateBits(whichTextRunState); michael@0: } michael@0: } michael@0: michael@0: NS_QUERYFRAME_HEAD(nsTextFrame) michael@0: NS_QUERYFRAME_ENTRY(nsTextFrame) michael@0: NS_QUERYFRAME_TAIL_INHERITING(nsTextFrameBase) michael@0: michael@0: gfxSkipCharsIterator michael@0: nsTextFrame::EnsureTextRun(TextRunType aWhichTextRun, michael@0: gfxContext* aReferenceContext, michael@0: nsIFrame* aLineContainer, michael@0: const nsLineList::iterator* aLine, michael@0: uint32_t* aFlowEndInTextRun) michael@0: { michael@0: gfxTextRun *textRun = GetTextRun(aWhichTextRun); michael@0: if (textRun && (!aLine || !(*aLine)->GetInvalidateTextRuns())) { michael@0: if (textRun->GetExpirationState()->IsTracked()) { michael@0: gTextRuns->MarkUsed(textRun); michael@0: } michael@0: } else { michael@0: nsRefPtr ctx = aReferenceContext; michael@0: if (!ctx) { michael@0: ctx = CreateReferenceThebesContext(this); michael@0: } michael@0: if (ctx) { michael@0: BuildTextRuns(ctx, this, aLineContainer, aLine, aWhichTextRun); michael@0: } michael@0: textRun = GetTextRun(aWhichTextRun); michael@0: if (!textRun) { michael@0: // A text run was not constructed for this frame. This is bad. The caller michael@0: // will check mTextRun. michael@0: static const gfxSkipChars emptySkipChars; michael@0: return gfxSkipCharsIterator(emptySkipChars, 0); michael@0: } michael@0: TabWidthStore* tabWidths = michael@0: static_cast(Properties().Get(TabWidthProperty())); michael@0: if (tabWidths && tabWidths->mValidForContentOffset != GetContentOffset()) { michael@0: Properties().Delete(TabWidthProperty()); michael@0: } michael@0: } michael@0: michael@0: if (textRun->GetFlags() & nsTextFrameUtils::TEXT_IS_SIMPLE_FLOW) { michael@0: if (aFlowEndInTextRun) { michael@0: *aFlowEndInTextRun = textRun->GetLength(); michael@0: } michael@0: return gfxSkipCharsIterator(textRun->GetSkipChars(), 0, mContentOffset); michael@0: } michael@0: michael@0: TextRunUserData* userData = static_cast(textRun->GetUserData()); michael@0: TextRunMappedFlow* flow = FindFlowForContent(userData, mContent); michael@0: if (flow) { michael@0: // Since textruns can only contain one flow for a given content element, michael@0: // this must be our flow. michael@0: uint32_t flowIndex = flow - userData->mMappedFlows; michael@0: userData->mLastFlowIndex = flowIndex; michael@0: gfxSkipCharsIterator iter(textRun->GetSkipChars(), michael@0: flow->mDOMOffsetToBeforeTransformOffset, mContentOffset); michael@0: if (aFlowEndInTextRun) { michael@0: if (flowIndex + 1 < userData->mMappedFlowCount) { michael@0: gfxSkipCharsIterator end(textRun->GetSkipChars()); michael@0: *aFlowEndInTextRun = end.ConvertOriginalToSkipped( michael@0: flow[1].mStartFrame->GetContentOffset() + flow[1].mDOMOffsetToBeforeTransformOffset); michael@0: } else { michael@0: *aFlowEndInTextRun = textRun->GetLength(); michael@0: } michael@0: } michael@0: return iter; michael@0: } michael@0: michael@0: NS_ERROR("Can't find flow containing this frame???"); michael@0: static const gfxSkipChars emptySkipChars; michael@0: return gfxSkipCharsIterator(emptySkipChars, 0); michael@0: } michael@0: michael@0: static uint32_t michael@0: GetEndOfTrimmedText(const nsTextFragment* aFrag, const nsStyleText* aStyleText, michael@0: uint32_t aStart, uint32_t aEnd, michael@0: gfxSkipCharsIterator* aIterator) michael@0: { michael@0: aIterator->SetSkippedOffset(aEnd); michael@0: while (aIterator->GetSkippedOffset() > aStart) { michael@0: aIterator->AdvanceSkipped(-1); michael@0: if (!IsTrimmableSpace(aFrag, aIterator->GetOriginalOffset(), aStyleText)) michael@0: return aIterator->GetSkippedOffset() + 1; michael@0: } michael@0: return aStart; michael@0: } michael@0: michael@0: nsTextFrame::TrimmedOffsets michael@0: nsTextFrame::GetTrimmedOffsets(const nsTextFragment* aFrag, michael@0: bool aTrimAfter, bool aPostReflow) michael@0: { michael@0: NS_ASSERTION(mTextRun, "Need textrun here"); michael@0: if (aPostReflow) { michael@0: // This should not be used during reflow. We need our TEXT_REFLOW_FLAGS michael@0: // to be set correctly. If our parent wasn't reflowed due to the frame michael@0: // tree being too deep then the return value doesn't matter. michael@0: NS_ASSERTION(!(GetStateBits() & NS_FRAME_FIRST_REFLOW) || michael@0: (GetParent()->GetStateBits() & michael@0: NS_FRAME_TOO_DEEP_IN_FRAME_TREE), michael@0: "Can only call this on frames that have been reflowed"); michael@0: NS_ASSERTION(!(GetStateBits() & NS_FRAME_IN_REFLOW), michael@0: "Can only call this on frames that are not being reflowed"); michael@0: } michael@0: michael@0: TrimmedOffsets offsets = { GetContentOffset(), GetContentLength() }; michael@0: const nsStyleText* textStyle = StyleText(); michael@0: // Note that pre-line newlines should still allow us to trim spaces michael@0: // for display michael@0: if (textStyle->WhiteSpaceIsSignificant()) michael@0: return offsets; michael@0: michael@0: if (!aPostReflow || (GetStateBits() & TEXT_START_OF_LINE)) { michael@0: int32_t whitespaceCount = michael@0: GetTrimmableWhitespaceCount(aFrag, michael@0: offsets.mStart, offsets.mLength, 1); michael@0: offsets.mStart += whitespaceCount; michael@0: offsets.mLength -= whitespaceCount; michael@0: } michael@0: michael@0: if (aTrimAfter && (!aPostReflow || (GetStateBits() & TEXT_END_OF_LINE))) { michael@0: // This treats a trailing 'pre-line' newline as trimmable. That's fine, michael@0: // it's actually what we want since we want whitespace before it to michael@0: // be trimmed. michael@0: int32_t whitespaceCount = michael@0: GetTrimmableWhitespaceCount(aFrag, michael@0: offsets.GetEnd() - 1, offsets.mLength, -1); michael@0: offsets.mLength -= whitespaceCount; michael@0: } michael@0: return offsets; michael@0: } michael@0: michael@0: /* michael@0: * Currently only Unicode characters below 0x10000 have their spacing modified michael@0: * by justification. If characters above 0x10000 turn out to need michael@0: * justification spacing, that will require extra work. Currently, michael@0: * this function must not include 0xd800 to 0xdbff because these characters michael@0: * are surrogates. michael@0: */ michael@0: static bool IsJustifiableCharacter(const nsTextFragment* aFrag, int32_t aPos, michael@0: bool aLangIsCJ) michael@0: { michael@0: char16_t ch = aFrag->CharAt(aPos); michael@0: if (ch == '\n' || ch == '\t' || ch == '\r') michael@0: return true; michael@0: if (ch == ' ' || ch == CH_NBSP) { michael@0: // Don't justify spaces that are combined with diacriticals michael@0: if (!aFrag->Is2b()) michael@0: return true; michael@0: return !nsTextFrameUtils::IsSpaceCombiningSequenceTail( michael@0: aFrag->Get2b() + aPos + 1, aFrag->GetLength() - (aPos + 1)); michael@0: } michael@0: if (ch < 0x2150u) michael@0: return false; michael@0: if (aLangIsCJ && ( michael@0: (0x2150u <= ch && ch <= 0x22ffu) || // Number Forms, Arrows, Mathematical Operators michael@0: (0x2460u <= ch && ch <= 0x24ffu) || // Enclosed Alphanumerics michael@0: (0x2580u <= ch && ch <= 0x27bfu) || // Block Elements, Geometric Shapes, Miscellaneous Symbols, Dingbats michael@0: (0x27f0u <= ch && ch <= 0x2bffu) || // Supplemental Arrows-A, Braille Patterns, Supplemental Arrows-B, michael@0: // Miscellaneous Mathematical Symbols-B, Supplemental Mathematical Operators, michael@0: // Miscellaneous Symbols and Arrows michael@0: (0x2e80u <= ch && ch <= 0x312fu) || // CJK Radicals Supplement, CJK Radicals Supplement, michael@0: // Ideographic Description Characters, CJK Symbols and Punctuation, michael@0: // Hiragana, Katakana, Bopomofo michael@0: (0x3190u <= ch && ch <= 0xabffu) || // Kanbun, Bopomofo Extended, Katakana Phonetic Extensions, michael@0: // Enclosed CJK Letters and Months, CJK Compatibility, michael@0: // CJK Unified Ideographs Extension A, Yijing Hexagram Symbols, michael@0: // CJK Unified Ideographs, Yi Syllables, Yi Radicals michael@0: (0xf900u <= ch && ch <= 0xfaffu) || // CJK Compatibility Ideographs michael@0: (0xff5eu <= ch && ch <= 0xff9fu) // Halfwidth and Fullwidth Forms(a part) michael@0: )) michael@0: return true; michael@0: return false; michael@0: } michael@0: michael@0: void michael@0: nsTextFrame::ClearMetrics(nsHTMLReflowMetrics& aMetrics) michael@0: { michael@0: aMetrics.Width() = 0; michael@0: aMetrics.Height() = 0; michael@0: aMetrics.SetTopAscent(0); michael@0: mAscent = 0; michael@0: } michael@0: michael@0: static int32_t FindChar(const nsTextFragment* frag, michael@0: int32_t aOffset, int32_t aLength, char16_t ch) michael@0: { michael@0: int32_t i = 0; michael@0: if (frag->Is2b()) { michael@0: const char16_t* str = frag->Get2b() + aOffset; michael@0: for (; i < aLength; ++i) { michael@0: if (*str == ch) michael@0: return i + aOffset; michael@0: ++str; michael@0: } michael@0: } else { michael@0: if (uint16_t(ch) <= 0xFF) { michael@0: const char* str = frag->Get1b() + aOffset; michael@0: const void* p = memchr(str, ch, aLength); michael@0: if (p) michael@0: return (static_cast(p) - str) + aOffset; michael@0: } michael@0: } michael@0: return -1; michael@0: } michael@0: michael@0: static bool IsChineseOrJapanese(nsIFrame* aFrame) michael@0: { michael@0: nsIAtom* language = aFrame->StyleFont()->mLanguage; michael@0: if (!language) { michael@0: return false; michael@0: } michael@0: const char16_t *lang = language->GetUTF16String(); michael@0: return (!nsCRT::strncmp(lang, MOZ_UTF16("ja"), 2) || michael@0: !nsCRT::strncmp(lang, MOZ_UTF16("zh"), 2)) && michael@0: (language->GetLength() == 2 || lang[2] == '-'); michael@0: } michael@0: michael@0: #ifdef DEBUG michael@0: static bool IsInBounds(const gfxSkipCharsIterator& aStart, int32_t aContentLength, michael@0: uint32_t aOffset, uint32_t aLength) { michael@0: if (aStart.GetSkippedOffset() > aOffset) michael@0: return false; michael@0: if (aContentLength == INT32_MAX) michael@0: return true; michael@0: gfxSkipCharsIterator iter(aStart); michael@0: iter.AdvanceOriginal(aContentLength); michael@0: return iter.GetSkippedOffset() >= aOffset + aLength; michael@0: } michael@0: #endif michael@0: michael@0: class MOZ_STACK_CLASS PropertyProvider : public gfxTextRun::PropertyProvider { michael@0: public: michael@0: /** michael@0: * Use this constructor for reflow, when we don't know what text is michael@0: * really mapped by the frame and we have a lot of other data around. michael@0: * michael@0: * @param aLength can be INT32_MAX to indicate we cover all the text michael@0: * associated with aFrame up to where its flow chain ends in the given michael@0: * textrun. If INT32_MAX is passed, justification and hyphen-related methods michael@0: * cannot be called, nor can GetOriginalLength(). michael@0: */ michael@0: PropertyProvider(gfxTextRun* aTextRun, const nsStyleText* aTextStyle, michael@0: const nsTextFragment* aFrag, nsTextFrame* aFrame, michael@0: const gfxSkipCharsIterator& aStart, int32_t aLength, michael@0: nsIFrame* aLineContainer, michael@0: nscoord aOffsetFromBlockOriginForTabs, michael@0: nsTextFrame::TextRunType aWhichTextRun) michael@0: : mTextRun(aTextRun), mFontGroup(nullptr), michael@0: mTextStyle(aTextStyle), mFrag(aFrag), michael@0: mLineContainer(aLineContainer), michael@0: mFrame(aFrame), mStart(aStart), mTempIterator(aStart), michael@0: mTabWidths(nullptr), mTabWidthsAnalyzedLimit(0), michael@0: mLength(aLength), michael@0: mWordSpacing(WordSpacing(aFrame, aTextStyle)), michael@0: mLetterSpacing(LetterSpacing(aFrame, aTextStyle)), michael@0: mJustificationSpacing(0), michael@0: mHyphenWidth(-1), michael@0: mOffsetFromBlockOriginForTabs(aOffsetFromBlockOriginForTabs), michael@0: mReflowing(true), michael@0: mWhichTextRun(aWhichTextRun) michael@0: { michael@0: NS_ASSERTION(mStart.IsInitialized(), "Start not initialized?"); michael@0: } michael@0: michael@0: /** michael@0: * Use this constructor after the frame has been reflowed and we don't michael@0: * have other data around. Gets everything from the frame. EnsureTextRun michael@0: * *must* be called before this!!! michael@0: */ michael@0: PropertyProvider(nsTextFrame* aFrame, const gfxSkipCharsIterator& aStart, michael@0: nsTextFrame::TextRunType aWhichTextRun) michael@0: : mTextRun(aFrame->GetTextRun(aWhichTextRun)), mFontGroup(nullptr), michael@0: mTextStyle(aFrame->StyleText()), michael@0: mFrag(aFrame->GetContent()->GetText()), michael@0: mLineContainer(nullptr), michael@0: mFrame(aFrame), mStart(aStart), mTempIterator(aStart), michael@0: mTabWidths(nullptr), mTabWidthsAnalyzedLimit(0), michael@0: mLength(aFrame->GetContentLength()), michael@0: mWordSpacing(WordSpacing(aFrame)), michael@0: mLetterSpacing(LetterSpacing(aFrame)), michael@0: mJustificationSpacing(0), michael@0: mHyphenWidth(-1), michael@0: mOffsetFromBlockOriginForTabs(0), michael@0: mReflowing(false), michael@0: mWhichTextRun(aWhichTextRun) michael@0: { michael@0: NS_ASSERTION(mTextRun, "Textrun not initialized!"); michael@0: } michael@0: michael@0: // Call this after construction if you're not going to reflow the text michael@0: void InitializeForDisplay(bool aTrimAfter); michael@0: michael@0: void InitializeForMeasure(); michael@0: michael@0: virtual void GetSpacing(uint32_t aStart, uint32_t aLength, Spacing* aSpacing); michael@0: virtual gfxFloat GetHyphenWidth(); michael@0: virtual void GetHyphenationBreaks(uint32_t aStart, uint32_t aLength, michael@0: bool* aBreakBefore); michael@0: virtual int8_t GetHyphensOption() { michael@0: return mTextStyle->mHyphens; michael@0: } michael@0: michael@0: virtual already_AddRefed GetContext() { michael@0: return CreateReferenceThebesContext(GetFrame()); michael@0: } michael@0: michael@0: virtual uint32_t GetAppUnitsPerDevUnit() { michael@0: return mTextRun->GetAppUnitsPerDevUnit(); michael@0: } michael@0: michael@0: void GetSpacingInternal(uint32_t aStart, uint32_t aLength, Spacing* aSpacing, michael@0: bool aIgnoreTabs); michael@0: michael@0: /** michael@0: * Count the number of justifiable characters in the given DOM range michael@0: */ michael@0: uint32_t ComputeJustifiableCharacters(int32_t aOffset, int32_t aLength); michael@0: /** michael@0: * Find the start and end of the justifiable characters. Does not depend on the michael@0: * position of aStart or aEnd, although it's most efficient if they are near the michael@0: * start and end of the text frame. michael@0: */ michael@0: void FindJustificationRange(gfxSkipCharsIterator* aStart, michael@0: gfxSkipCharsIterator* aEnd); michael@0: michael@0: const nsStyleText* StyleText() { return mTextStyle; } michael@0: nsTextFrame* GetFrame() { return mFrame; } michael@0: // This may not be equal to the frame offset/length in because we may have michael@0: // adjusted for whitespace trimming according to the state bits set in the frame michael@0: // (for the static provider) michael@0: const gfxSkipCharsIterator& GetStart() { return mStart; } michael@0: // May return INT32_MAX if that was given to the constructor michael@0: uint32_t GetOriginalLength() { michael@0: NS_ASSERTION(mLength != INT32_MAX, "Length not known"); michael@0: return mLength; michael@0: } michael@0: const nsTextFragment* GetFragment() { return mFrag; } michael@0: michael@0: gfxFontGroup* GetFontGroup() { michael@0: if (!mFontGroup) michael@0: InitFontGroupAndFontMetrics(); michael@0: return mFontGroup; michael@0: } michael@0: michael@0: nsFontMetrics* GetFontMetrics() { michael@0: if (!mFontMetrics) michael@0: InitFontGroupAndFontMetrics(); michael@0: return mFontMetrics; michael@0: } michael@0: michael@0: void CalcTabWidths(uint32_t aTransformedStart, uint32_t aTransformedLength); michael@0: michael@0: const gfxSkipCharsIterator& GetEndHint() { return mTempIterator; } michael@0: michael@0: protected: michael@0: void SetupJustificationSpacing(bool aPostReflow); michael@0: michael@0: void InitFontGroupAndFontMetrics() { michael@0: float inflation = (mWhichTextRun == nsTextFrame::eInflated) michael@0: ? mFrame->GetFontSizeInflation() : 1.0f; michael@0: mFontGroup = GetFontGroupForFrame(mFrame, inflation, michael@0: getter_AddRefs(mFontMetrics)); michael@0: } michael@0: michael@0: gfxTextRun* mTextRun; michael@0: gfxFontGroup* mFontGroup; michael@0: nsRefPtr mFontMetrics; michael@0: const nsStyleText* mTextStyle; michael@0: const nsTextFragment* mFrag; michael@0: nsIFrame* mLineContainer; michael@0: nsTextFrame* mFrame; michael@0: gfxSkipCharsIterator mStart; // Offset in original and transformed string michael@0: gfxSkipCharsIterator mTempIterator; michael@0: michael@0: // Either null, or pointing to the frame's TabWidthProperty. michael@0: TabWidthStore* mTabWidths; michael@0: // How far we've done tab-width calculation; this is ONLY valid when michael@0: // mTabWidths is nullptr (otherwise rely on mTabWidths->mLimit instead). michael@0: // It's a DOM offset relative to the current frame's offset. michael@0: uint32_t mTabWidthsAnalyzedLimit; michael@0: michael@0: int32_t mLength; // DOM string length, may be INT32_MAX michael@0: gfxFloat mWordSpacing; // space for each whitespace char michael@0: gfxFloat mLetterSpacing; // space for each letter michael@0: gfxFloat mJustificationSpacing; michael@0: gfxFloat mHyphenWidth; michael@0: gfxFloat mOffsetFromBlockOriginForTabs; michael@0: bool mReflowing; michael@0: nsTextFrame::TextRunType mWhichTextRun; michael@0: }; michael@0: michael@0: uint32_t michael@0: PropertyProvider::ComputeJustifiableCharacters(int32_t aOffset, int32_t aLength) michael@0: { michael@0: // Scan non-skipped characters and count justifiable chars. michael@0: nsSkipCharsRunIterator michael@0: run(mStart, nsSkipCharsRunIterator::LENGTH_INCLUDES_SKIPPED, aLength); michael@0: run.SetOriginalOffset(aOffset); michael@0: uint32_t justifiableChars = 0; michael@0: bool isCJK = IsChineseOrJapanese(mFrame); michael@0: while (run.NextRun()) { michael@0: for (int32_t i = 0; i < run.GetRunLength(); ++i) { michael@0: justifiableChars += michael@0: IsJustifiableCharacter(mFrag, run.GetOriginalOffset() + i, isCJK); michael@0: } michael@0: } michael@0: return justifiableChars; michael@0: } michael@0: michael@0: /** michael@0: * Finds the offset of the first character of the cluster containing aPos michael@0: */ michael@0: static void FindClusterStart(gfxTextRun* aTextRun, int32_t aOriginalStart, michael@0: gfxSkipCharsIterator* aPos) michael@0: { michael@0: while (aPos->GetOriginalOffset() > aOriginalStart) { michael@0: if (aPos->IsOriginalCharSkipped() || michael@0: aTextRun->IsClusterStart(aPos->GetSkippedOffset())) { michael@0: break; michael@0: } michael@0: aPos->AdvanceOriginal(-1); michael@0: } michael@0: } michael@0: michael@0: /** michael@0: * Finds the offset of the last character of the cluster containing aPos michael@0: */ michael@0: static void FindClusterEnd(gfxTextRun* aTextRun, int32_t aOriginalEnd, michael@0: gfxSkipCharsIterator* aPos) michael@0: { michael@0: NS_PRECONDITION(aPos->GetOriginalOffset() < aOriginalEnd, michael@0: "character outside string"); michael@0: aPos->AdvanceOriginal(1); michael@0: while (aPos->GetOriginalOffset() < aOriginalEnd) { michael@0: if (aPos->IsOriginalCharSkipped() || michael@0: aTextRun->IsClusterStart(aPos->GetSkippedOffset())) { michael@0: break; michael@0: } michael@0: aPos->AdvanceOriginal(1); michael@0: } michael@0: aPos->AdvanceOriginal(-1); michael@0: } michael@0: michael@0: // aStart, aLength in transformed string offsets michael@0: void michael@0: PropertyProvider::GetSpacing(uint32_t aStart, uint32_t aLength, michael@0: Spacing* aSpacing) michael@0: { michael@0: GetSpacingInternal(aStart, aLength, aSpacing, michael@0: (mTextRun->GetFlags() & nsTextFrameUtils::TEXT_HAS_TAB) == 0); michael@0: } michael@0: michael@0: static bool michael@0: CanAddSpacingAfter(gfxTextRun* aTextRun, uint32_t aOffset) michael@0: { michael@0: if (aOffset + 1 >= aTextRun->GetLength()) michael@0: return true; michael@0: return aTextRun->IsClusterStart(aOffset + 1) && michael@0: aTextRun->IsLigatureGroupStart(aOffset + 1); michael@0: } michael@0: michael@0: void michael@0: PropertyProvider::GetSpacingInternal(uint32_t aStart, uint32_t aLength, michael@0: Spacing* aSpacing, bool aIgnoreTabs) michael@0: { michael@0: NS_PRECONDITION(IsInBounds(mStart, mLength, aStart, aLength), "Range out of bounds"); michael@0: michael@0: uint32_t index; michael@0: for (index = 0; index < aLength; ++index) { michael@0: aSpacing[index].mBefore = 0.0; michael@0: aSpacing[index].mAfter = 0.0; michael@0: } michael@0: michael@0: // Find our offset into the original+transformed string michael@0: gfxSkipCharsIterator start(mStart); michael@0: start.SetSkippedOffset(aStart); michael@0: michael@0: // First, compute the word and letter spacing michael@0: if (mWordSpacing || mLetterSpacing) { michael@0: // Iterate over non-skipped characters michael@0: nsSkipCharsRunIterator michael@0: run(start, nsSkipCharsRunIterator::LENGTH_UNSKIPPED_ONLY, aLength); michael@0: while (run.NextRun()) { michael@0: uint32_t runOffsetInSubstring = run.GetSkippedOffset() - aStart; michael@0: gfxSkipCharsIterator iter = run.GetPos(); michael@0: for (int32_t i = 0; i < run.GetRunLength(); ++i) { michael@0: if (CanAddSpacingAfter(mTextRun, run.GetSkippedOffset() + i)) { michael@0: // End of a cluster, not in a ligature: put letter-spacing after it michael@0: aSpacing[runOffsetInSubstring + i].mAfter += mLetterSpacing; michael@0: } michael@0: if (IsCSSWordSpacingSpace(mFrag, i + run.GetOriginalOffset(), michael@0: mTextStyle)) { michael@0: // It kinda sucks, but space characters can be part of clusters, michael@0: // and even still be whitespace (I think!) michael@0: iter.SetSkippedOffset(run.GetSkippedOffset() + i); michael@0: FindClusterEnd(mTextRun, run.GetOriginalOffset() + run.GetRunLength(), michael@0: &iter); michael@0: aSpacing[iter.GetSkippedOffset() - aStart].mAfter += mWordSpacing; michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: // Ignore tab spacing rather than computing it, if the tab size is 0 michael@0: if (!aIgnoreTabs) michael@0: aIgnoreTabs = mFrame->StyleText()->mTabSize == 0; michael@0: michael@0: // Now add tab spacing, if there is any michael@0: if (!aIgnoreTabs) { michael@0: CalcTabWidths(aStart, aLength); michael@0: if (mTabWidths) { michael@0: mTabWidths->ApplySpacing(aSpacing, michael@0: aStart - mStart.GetSkippedOffset(), aLength); michael@0: } michael@0: } michael@0: michael@0: // Now add in justification spacing michael@0: if (mJustificationSpacing) { michael@0: gfxFloat halfJustificationSpace = mJustificationSpacing/2; michael@0: // Scan non-skipped characters and adjust justifiable chars, adding michael@0: // justification space on either side of the cluster michael@0: bool isCJK = IsChineseOrJapanese(mFrame); michael@0: gfxSkipCharsIterator justificationStart(mStart), justificationEnd(mStart); michael@0: FindJustificationRange(&justificationStart, &justificationEnd); michael@0: michael@0: nsSkipCharsRunIterator michael@0: run(start, nsSkipCharsRunIterator::LENGTH_UNSKIPPED_ONLY, aLength); michael@0: while (run.NextRun()) { michael@0: gfxSkipCharsIterator iter = run.GetPos(); michael@0: int32_t runOriginalOffset = run.GetOriginalOffset(); michael@0: for (int32_t i = 0; i < run.GetRunLength(); ++i) { michael@0: int32_t iterOriginalOffset = runOriginalOffset + i; michael@0: if (IsJustifiableCharacter(mFrag, iterOriginalOffset, isCJK)) { michael@0: iter.SetOriginalOffset(iterOriginalOffset); michael@0: FindClusterStart(mTextRun, runOriginalOffset, &iter); michael@0: uint32_t clusterFirstChar = iter.GetSkippedOffset(); michael@0: FindClusterEnd(mTextRun, runOriginalOffset + run.GetRunLength(), &iter); michael@0: uint32_t clusterLastChar = iter.GetSkippedOffset(); michael@0: // Only apply justification to characters before justificationEnd michael@0: if (clusterFirstChar >= justificationStart.GetSkippedOffset() && michael@0: clusterLastChar < justificationEnd.GetSkippedOffset()) { michael@0: aSpacing[clusterFirstChar - aStart].mBefore += halfJustificationSpace; michael@0: aSpacing[clusterLastChar - aStart].mAfter += halfJustificationSpace; michael@0: } michael@0: } michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: static gfxFloat michael@0: ComputeTabWidthAppUnits(nsIFrame* aFrame, gfxTextRun* aTextRun) michael@0: { michael@0: // Get the number of spaces from CSS -moz-tab-size michael@0: const nsStyleText* textStyle = aFrame->StyleText(); michael@0: michael@0: // Round the space width when converting to appunits the same way michael@0: // textruns do michael@0: gfxFloat spaceWidthAppUnits = michael@0: NS_round(GetFirstFontMetrics(aTextRun->GetFontGroup()).spaceWidth * michael@0: aTextRun->GetAppUnitsPerDevUnit()); michael@0: return textStyle->mTabSize * spaceWidthAppUnits; michael@0: } michael@0: michael@0: // aX and the result are in whole appunits. michael@0: static gfxFloat michael@0: AdvanceToNextTab(gfxFloat aX, nsIFrame* aFrame, michael@0: gfxTextRun* aTextRun, gfxFloat* aCachedTabWidth) michael@0: { michael@0: if (*aCachedTabWidth < 0) { michael@0: *aCachedTabWidth = ComputeTabWidthAppUnits(aFrame, aTextRun); michael@0: } michael@0: michael@0: // Advance aX to the next multiple of *aCachedTabWidth. We must advance michael@0: // by at least 1 appunit. michael@0: // XXX should we make this 1 CSS pixel? michael@0: return ceil((aX + 1)/(*aCachedTabWidth))*(*aCachedTabWidth); michael@0: } michael@0: michael@0: void michael@0: PropertyProvider::CalcTabWidths(uint32_t aStart, uint32_t aLength) michael@0: { michael@0: if (!mTabWidths) { michael@0: if (mReflowing && !mLineContainer) { michael@0: // Intrinsic width computation does its own tab processing. We michael@0: // just don't do anything here. michael@0: return; michael@0: } michael@0: if (!mReflowing) { michael@0: mTabWidths = static_cast michael@0: (mFrame->Properties().Get(TabWidthProperty())); michael@0: #ifdef DEBUG michael@0: // If we're not reflowing, we should have already computed the michael@0: // tab widths; check that they're available as far as the last michael@0: // tab character present (if any) michael@0: for (uint32_t i = aStart + aLength; i > aStart; --i) { michael@0: if (mTextRun->CharIsTab(i - 1)) { michael@0: uint32_t startOffset = mStart.GetSkippedOffset(); michael@0: NS_ASSERTION(mTabWidths && mTabWidths->mLimit + startOffset >= i, michael@0: "Precomputed tab widths are missing!"); michael@0: break; michael@0: } michael@0: } michael@0: #endif michael@0: return; michael@0: } michael@0: } michael@0: michael@0: uint32_t startOffset = mStart.GetSkippedOffset(); michael@0: MOZ_ASSERT(aStart >= startOffset, "wrong start offset"); michael@0: MOZ_ASSERT(aStart + aLength <= startOffset + mLength, "beyond the end"); michael@0: uint32_t tabsEnd = michael@0: (mTabWidths ? mTabWidths->mLimit : mTabWidthsAnalyzedLimit) + startOffset; michael@0: if (tabsEnd < aStart + aLength) { michael@0: NS_ASSERTION(mReflowing, michael@0: "We need precomputed tab widths, but don't have enough."); michael@0: michael@0: gfxFloat tabWidth = -1; michael@0: for (uint32_t i = tabsEnd; i < aStart + aLength; ++i) { michael@0: Spacing spacing; michael@0: GetSpacingInternal(i, 1, &spacing, true); michael@0: mOffsetFromBlockOriginForTabs += spacing.mBefore; michael@0: michael@0: if (!mTextRun->CharIsTab(i)) { michael@0: if (mTextRun->IsClusterStart(i)) { michael@0: uint32_t clusterEnd = i + 1; michael@0: while (clusterEnd < mTextRun->GetLength() && michael@0: !mTextRun->IsClusterStart(clusterEnd)) { michael@0: ++clusterEnd; michael@0: } michael@0: mOffsetFromBlockOriginForTabs += michael@0: mTextRun->GetAdvanceWidth(i, clusterEnd - i, nullptr); michael@0: } michael@0: } else { michael@0: if (!mTabWidths) { michael@0: mTabWidths = new TabWidthStore(mFrame->GetContentOffset()); michael@0: mFrame->Properties().Set(TabWidthProperty(), mTabWidths); michael@0: } michael@0: double nextTab = AdvanceToNextTab(mOffsetFromBlockOriginForTabs, michael@0: mFrame, mTextRun, &tabWidth); michael@0: mTabWidths->mWidths.AppendElement(TabWidth(i - startOffset, michael@0: NSToIntRound(nextTab - mOffsetFromBlockOriginForTabs))); michael@0: mOffsetFromBlockOriginForTabs = nextTab; michael@0: } michael@0: michael@0: mOffsetFromBlockOriginForTabs += spacing.mAfter; michael@0: } michael@0: michael@0: if (mTabWidths) { michael@0: mTabWidths->mLimit = aStart + aLength - startOffset; michael@0: } michael@0: } michael@0: michael@0: if (!mTabWidths) { michael@0: // Delete any stale property that may be left on the frame michael@0: mFrame->Properties().Delete(TabWidthProperty()); michael@0: mTabWidthsAnalyzedLimit = std::max(mTabWidthsAnalyzedLimit, michael@0: aStart + aLength - startOffset); michael@0: } michael@0: } michael@0: michael@0: gfxFloat michael@0: PropertyProvider::GetHyphenWidth() michael@0: { michael@0: if (mHyphenWidth < 0) { michael@0: mHyphenWidth = GetFontGroup()->GetHyphenWidth(this); michael@0: } michael@0: return mHyphenWidth + mLetterSpacing; michael@0: } michael@0: michael@0: void michael@0: PropertyProvider::GetHyphenationBreaks(uint32_t aStart, uint32_t aLength, michael@0: bool* aBreakBefore) michael@0: { michael@0: NS_PRECONDITION(IsInBounds(mStart, mLength, aStart, aLength), "Range out of bounds"); michael@0: NS_PRECONDITION(mLength != INT32_MAX, "Can't call this with undefined length"); michael@0: michael@0: if (!mTextStyle->WhiteSpaceCanWrap(mFrame) || michael@0: mTextStyle->mHyphens == NS_STYLE_HYPHENS_NONE) michael@0: { michael@0: memset(aBreakBefore, false, aLength*sizeof(bool)); michael@0: return; michael@0: } michael@0: michael@0: // Iterate through the original-string character runs michael@0: nsSkipCharsRunIterator michael@0: run(mStart, nsSkipCharsRunIterator::LENGTH_UNSKIPPED_ONLY, aLength); michael@0: run.SetSkippedOffset(aStart); michael@0: // We need to visit skipped characters so that we can detect SHY michael@0: run.SetVisitSkipped(); michael@0: michael@0: int32_t prevTrailingCharOffset = run.GetPos().GetOriginalOffset() - 1; michael@0: bool allowHyphenBreakBeforeNextChar = michael@0: prevTrailingCharOffset >= mStart.GetOriginalOffset() && michael@0: prevTrailingCharOffset < mStart.GetOriginalOffset() + mLength && michael@0: mFrag->CharAt(prevTrailingCharOffset) == CH_SHY; michael@0: michael@0: while (run.NextRun()) { michael@0: NS_ASSERTION(run.GetRunLength() > 0, "Shouldn't return zero-length runs"); michael@0: if (run.IsSkipped()) { michael@0: // Check if there's a soft hyphen which would let us hyphenate before michael@0: // the next non-skipped character. Don't look at soft hyphens followed michael@0: // by other skipped characters, we won't use them. michael@0: allowHyphenBreakBeforeNextChar = michael@0: mFrag->CharAt(run.GetOriginalOffset() + run.GetRunLength() - 1) == CH_SHY; michael@0: } else { michael@0: int32_t runOffsetInSubstring = run.GetSkippedOffset() - aStart; michael@0: memset(aBreakBefore + runOffsetInSubstring, false, run.GetRunLength()*sizeof(bool)); michael@0: // Don't allow hyphen breaks at the start of the line michael@0: aBreakBefore[runOffsetInSubstring] = allowHyphenBreakBeforeNextChar && michael@0: (!(mFrame->GetStateBits() & TEXT_START_OF_LINE) || michael@0: run.GetSkippedOffset() > mStart.GetSkippedOffset()); michael@0: allowHyphenBreakBeforeNextChar = false; michael@0: } michael@0: } michael@0: michael@0: if (mTextStyle->mHyphens == NS_STYLE_HYPHENS_AUTO) { michael@0: for (uint32_t i = 0; i < aLength; ++i) { michael@0: if (mTextRun->CanHyphenateBefore(aStart + i)) { michael@0: aBreakBefore[i] = true; michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: void michael@0: PropertyProvider::InitializeForDisplay(bool aTrimAfter) michael@0: { michael@0: nsTextFrame::TrimmedOffsets trimmed = michael@0: mFrame->GetTrimmedOffsets(mFrag, aTrimAfter); michael@0: mStart.SetOriginalOffset(trimmed.mStart); michael@0: mLength = trimmed.mLength; michael@0: SetupJustificationSpacing(true); michael@0: } michael@0: michael@0: void michael@0: PropertyProvider::InitializeForMeasure() michael@0: { michael@0: nsTextFrame::TrimmedOffsets trimmed = michael@0: mFrame->GetTrimmedOffsets(mFrag, true, false); michael@0: mStart.SetOriginalOffset(trimmed.mStart); michael@0: mLength = trimmed.mLength; michael@0: SetupJustificationSpacing(false); michael@0: } michael@0: michael@0: michael@0: static uint32_t GetSkippedDistance(const gfxSkipCharsIterator& aStart, michael@0: const gfxSkipCharsIterator& aEnd) michael@0: { michael@0: return aEnd.GetSkippedOffset() - aStart.GetSkippedOffset(); michael@0: } michael@0: michael@0: void michael@0: PropertyProvider::FindJustificationRange(gfxSkipCharsIterator* aStart, michael@0: gfxSkipCharsIterator* aEnd) michael@0: { michael@0: NS_PRECONDITION(mLength != INT32_MAX, "Can't call this with undefined length"); michael@0: NS_ASSERTION(aStart && aEnd, "aStart or/and aEnd is null"); michael@0: michael@0: aStart->SetOriginalOffset(mStart.GetOriginalOffset()); michael@0: aEnd->SetOriginalOffset(mStart.GetOriginalOffset() + mLength); michael@0: michael@0: // Ignore first cluster at start of line for justification purposes michael@0: if (mFrame->GetStateBits() & TEXT_START_OF_LINE) { michael@0: while (aStart->GetOriginalOffset() < aEnd->GetOriginalOffset()) { michael@0: aStart->AdvanceOriginal(1); michael@0: if (!aStart->IsOriginalCharSkipped() && michael@0: mTextRun->IsClusterStart(aStart->GetSkippedOffset())) michael@0: break; michael@0: } michael@0: } michael@0: michael@0: // Ignore trailing cluster at end of line for justification purposes michael@0: if (mFrame->GetStateBits() & TEXT_END_OF_LINE) { michael@0: while (aEnd->GetOriginalOffset() > aStart->GetOriginalOffset()) { michael@0: aEnd->AdvanceOriginal(-1); michael@0: if (!aEnd->IsOriginalCharSkipped() && michael@0: mTextRun->IsClusterStart(aEnd->GetSkippedOffset())) michael@0: break; michael@0: } michael@0: } michael@0: } michael@0: michael@0: void michael@0: PropertyProvider::SetupJustificationSpacing(bool aPostReflow) michael@0: { michael@0: NS_PRECONDITION(mLength != INT32_MAX, "Can't call this with undefined length"); michael@0: michael@0: if (!(mFrame->GetStateBits() & TEXT_JUSTIFICATION_ENABLED)) michael@0: return; michael@0: michael@0: gfxSkipCharsIterator start(mStart), end(mStart); michael@0: // We can't just use our mLength here; when InitializeForDisplay is michael@0: // called with false for aTrimAfter, we still shouldn't be assigning michael@0: // justification space to any trailing whitespace. michael@0: nsTextFrame::TrimmedOffsets trimmed = michael@0: mFrame->GetTrimmedOffsets(mFrag, true, aPostReflow); michael@0: end.AdvanceOriginal(trimmed.mLength); michael@0: gfxSkipCharsIterator realEnd(end); michael@0: FindJustificationRange(&start, &end); michael@0: michael@0: int32_t justifiableCharacters = michael@0: ComputeJustifiableCharacters(start.GetOriginalOffset(), michael@0: end.GetOriginalOffset() - start.GetOriginalOffset()); michael@0: if (justifiableCharacters == 0) { michael@0: // Nothing to do, nothing is justifiable and we shouldn't have any michael@0: // justification space assigned michael@0: return; michael@0: } michael@0: michael@0: gfxFloat naturalWidth = michael@0: mTextRun->GetAdvanceWidth(mStart.GetSkippedOffset(), michael@0: GetSkippedDistance(mStart, realEnd), this); michael@0: if (mFrame->GetStateBits() & TEXT_HYPHEN_BREAK) { michael@0: naturalWidth += GetHyphenWidth(); michael@0: } michael@0: gfxFloat totalJustificationSpace = mFrame->GetSize().width - naturalWidth; michael@0: if (totalJustificationSpace <= 0) { michael@0: // No space available michael@0: return; michael@0: } michael@0: michael@0: mJustificationSpacing = totalJustificationSpace/justifiableCharacters; michael@0: } michael@0: michael@0: //---------------------------------------------------------------------- michael@0: michael@0: static nscolor michael@0: EnsureDifferentColors(nscolor colorA, nscolor colorB) michael@0: { michael@0: if (colorA == colorB) { michael@0: nscolor res; michael@0: res = NS_RGB(NS_GET_R(colorA) ^ 0xff, michael@0: NS_GET_G(colorA) ^ 0xff, michael@0: NS_GET_B(colorA) ^ 0xff); michael@0: return res; michael@0: } michael@0: return colorA; michael@0: } michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: michael@0: nsTextPaintStyle::nsTextPaintStyle(nsTextFrame* aFrame) michael@0: : mFrame(aFrame), michael@0: mPresContext(aFrame->PresContext()), michael@0: mInitCommonColors(false), michael@0: mInitSelectionColorsAndShadow(false), michael@0: mResolveColors(true), michael@0: mHasSelectionShadow(false) michael@0: { michael@0: for (uint32_t i = 0; i < ArrayLength(mSelectionStyle); i++) michael@0: mSelectionStyle[i].mInit = false; michael@0: } michael@0: michael@0: bool michael@0: nsTextPaintStyle::EnsureSufficientContrast(nscolor *aForeColor, nscolor *aBackColor) michael@0: { michael@0: InitCommonColors(); michael@0: michael@0: // If the combination of selection background color and frame background color michael@0: // is sufficient contrast, don't exchange the selection colors. michael@0: int32_t backLuminosityDifference = michael@0: NS_LUMINOSITY_DIFFERENCE(*aBackColor, mFrameBackgroundColor); michael@0: if (backLuminosityDifference >= mSufficientContrast) michael@0: return false; michael@0: michael@0: // Otherwise, we should use the higher-contrast color for the selection michael@0: // background color. michael@0: int32_t foreLuminosityDifference = michael@0: NS_LUMINOSITY_DIFFERENCE(*aForeColor, mFrameBackgroundColor); michael@0: if (backLuminosityDifference < foreLuminosityDifference) { michael@0: nscolor tmpColor = *aForeColor; michael@0: *aForeColor = *aBackColor; michael@0: *aBackColor = tmpColor; michael@0: return true; michael@0: } michael@0: return false; michael@0: } michael@0: michael@0: nscolor michael@0: nsTextPaintStyle::GetTextColor() michael@0: { michael@0: if (mFrame->IsSVGText()) { michael@0: if (!mResolveColors) michael@0: return NS_SAME_AS_FOREGROUND_COLOR; michael@0: michael@0: const nsStyleSVG* style = mFrame->StyleSVG(); michael@0: switch (style->mFill.mType) { michael@0: case eStyleSVGPaintType_None: michael@0: return NS_RGBA(0, 0, 0, 0); michael@0: case eStyleSVGPaintType_Color: michael@0: return nsLayoutUtils::GetColor(mFrame, eCSSProperty_fill); michael@0: default: michael@0: NS_ERROR("cannot resolve SVG paint to nscolor"); michael@0: return NS_RGBA(0, 0, 0, 255); michael@0: } michael@0: } michael@0: return nsLayoutUtils::GetColor(mFrame, eCSSProperty_color); michael@0: } michael@0: michael@0: bool michael@0: nsTextPaintStyle::GetSelectionColors(nscolor* aForeColor, michael@0: nscolor* aBackColor) michael@0: { michael@0: NS_ASSERTION(aForeColor, "aForeColor is null"); michael@0: NS_ASSERTION(aBackColor, "aBackColor is null"); michael@0: michael@0: if (!InitSelectionColorsAndShadow()) michael@0: return false; michael@0: michael@0: *aForeColor = mSelectionTextColor; michael@0: *aBackColor = mSelectionBGColor; michael@0: return true; michael@0: } michael@0: michael@0: void michael@0: nsTextPaintStyle::GetHighlightColors(nscolor* aForeColor, michael@0: nscolor* aBackColor) michael@0: { michael@0: NS_ASSERTION(aForeColor, "aForeColor is null"); michael@0: NS_ASSERTION(aBackColor, "aBackColor is null"); michael@0: michael@0: nscolor backColor = michael@0: LookAndFeel::GetColor(LookAndFeel::eColorID_TextHighlightBackground); michael@0: nscolor foreColor = michael@0: LookAndFeel::GetColor(LookAndFeel::eColorID_TextHighlightForeground); michael@0: EnsureSufficientContrast(&foreColor, &backColor); michael@0: *aForeColor = foreColor; michael@0: *aBackColor = backColor; michael@0: } michael@0: michael@0: void michael@0: nsTextPaintStyle::GetURLSecondaryColor(nscolor* aForeColor) michael@0: { michael@0: NS_ASSERTION(aForeColor, "aForeColor is null"); michael@0: michael@0: nscolor textColor = GetTextColor(); michael@0: textColor = NS_RGBA(NS_GET_R(textColor), michael@0: NS_GET_G(textColor), michael@0: NS_GET_B(textColor), michael@0: (uint8_t)(255 * 0.5f)); michael@0: // Don't use true alpha color for readability. michael@0: InitCommonColors(); michael@0: *aForeColor = NS_ComposeColors(mFrameBackgroundColor, textColor); michael@0: } michael@0: michael@0: void michael@0: nsTextPaintStyle::GetIMESelectionColors(int32_t aIndex, michael@0: nscolor* aForeColor, michael@0: nscolor* aBackColor) michael@0: { michael@0: NS_ASSERTION(aForeColor, "aForeColor is null"); michael@0: NS_ASSERTION(aBackColor, "aBackColor is null"); michael@0: NS_ASSERTION(aIndex >= 0 && aIndex < 5, "Index out of range"); michael@0: michael@0: nsSelectionStyle* selectionStyle = GetSelectionStyle(aIndex); michael@0: *aForeColor = selectionStyle->mTextColor; michael@0: *aBackColor = selectionStyle->mBGColor; michael@0: } michael@0: michael@0: bool michael@0: nsTextPaintStyle::GetSelectionUnderlineForPaint(int32_t aIndex, michael@0: nscolor* aLineColor, michael@0: float* aRelativeSize, michael@0: uint8_t* aStyle) michael@0: { michael@0: NS_ASSERTION(aLineColor, "aLineColor is null"); michael@0: NS_ASSERTION(aRelativeSize, "aRelativeSize is null"); michael@0: NS_ASSERTION(aIndex >= 0 && aIndex < 5, "Index out of range"); michael@0: michael@0: nsSelectionStyle* selectionStyle = GetSelectionStyle(aIndex); michael@0: if (selectionStyle->mUnderlineStyle == NS_STYLE_BORDER_STYLE_NONE || michael@0: selectionStyle->mUnderlineColor == NS_TRANSPARENT || michael@0: selectionStyle->mUnderlineRelativeSize <= 0.0f) michael@0: return false; michael@0: michael@0: *aLineColor = selectionStyle->mUnderlineColor; michael@0: *aRelativeSize = selectionStyle->mUnderlineRelativeSize; michael@0: *aStyle = selectionStyle->mUnderlineStyle; michael@0: return true; michael@0: } michael@0: michael@0: void michael@0: nsTextPaintStyle::InitCommonColors() michael@0: { michael@0: if (mInitCommonColors) michael@0: return; michael@0: michael@0: nsIFrame* bgFrame = michael@0: nsCSSRendering::FindNonTransparentBackgroundFrame(mFrame); michael@0: NS_ASSERTION(bgFrame, "Cannot find NonTransparentBackgroundFrame."); michael@0: nscolor bgColor = michael@0: bgFrame->GetVisitedDependentColor(eCSSProperty_background_color); michael@0: michael@0: nscolor defaultBgColor = mPresContext->DefaultBackgroundColor(); michael@0: mFrameBackgroundColor = NS_ComposeColors(defaultBgColor, bgColor); michael@0: michael@0: if (bgFrame->IsThemed()) { michael@0: // Assume a native widget has sufficient contrast always michael@0: mSufficientContrast = 0; michael@0: mInitCommonColors = true; michael@0: return; michael@0: } michael@0: michael@0: NS_ASSERTION(NS_GET_A(defaultBgColor) == 255, michael@0: "default background color is not opaque"); michael@0: michael@0: nscolor defaultWindowBackgroundColor = michael@0: LookAndFeel::GetColor(LookAndFeel::eColorID_WindowBackground); michael@0: nscolor selectionTextColor = michael@0: LookAndFeel::GetColor(LookAndFeel::eColorID_TextSelectForeground); michael@0: nscolor selectionBGColor = michael@0: LookAndFeel::GetColor(LookAndFeel::eColorID_TextSelectBackground); michael@0: michael@0: mSufficientContrast = michael@0: std::min(std::min(NS_SUFFICIENT_LUMINOSITY_DIFFERENCE, michael@0: NS_LUMINOSITY_DIFFERENCE(selectionTextColor, michael@0: selectionBGColor)), michael@0: NS_LUMINOSITY_DIFFERENCE(defaultWindowBackgroundColor, michael@0: selectionBGColor)); michael@0: michael@0: mInitCommonColors = true; michael@0: } michael@0: michael@0: static Element* michael@0: FindElementAncestorForMozSelection(nsIContent* aContent) michael@0: { michael@0: NS_ENSURE_TRUE(aContent, nullptr); michael@0: while (aContent && aContent->IsInNativeAnonymousSubtree()) { michael@0: aContent = aContent->GetBindingParent(); michael@0: } michael@0: NS_ASSERTION(aContent, "aContent isn't in non-anonymous tree?"); michael@0: while (aContent && !aContent->IsElement()) { michael@0: aContent = aContent->GetParent(); michael@0: } michael@0: return aContent ? aContent->AsElement() : nullptr; michael@0: } michael@0: michael@0: bool michael@0: nsTextPaintStyle::InitSelectionColorsAndShadow() michael@0: { michael@0: if (mInitSelectionColorsAndShadow) michael@0: return true; michael@0: michael@0: int16_t selectionFlags; michael@0: int16_t selectionStatus = mFrame->GetSelectionStatus(&selectionFlags); michael@0: if (!(selectionFlags & nsISelectionDisplay::DISPLAY_TEXT) || michael@0: selectionStatus < nsISelectionController::SELECTION_ON) { michael@0: // Not displaying the normal selection. michael@0: // We're not caching this fact, so every call to GetSelectionColors michael@0: // will come through here. We could avoid this, but it's not really worth it. michael@0: return false; michael@0: } michael@0: michael@0: mInitSelectionColorsAndShadow = true; michael@0: michael@0: nsIFrame* nonGeneratedAncestor = nsLayoutUtils::GetNonGeneratedAncestor(mFrame); michael@0: Element* selectionElement = michael@0: FindElementAncestorForMozSelection(nonGeneratedAncestor->GetContent()); michael@0: michael@0: if (selectionElement && michael@0: selectionStatus == nsISelectionController::SELECTION_ON) { michael@0: nsRefPtr sc = nullptr; michael@0: sc = mPresContext->StyleSet()-> michael@0: ProbePseudoElementStyle(selectionElement, michael@0: nsCSSPseudoElements::ePseudo_mozSelection, michael@0: mFrame->StyleContext()); michael@0: // Use -moz-selection pseudo class. michael@0: if (sc) { michael@0: mSelectionBGColor = michael@0: sc->GetVisitedDependentColor(eCSSProperty_background_color); michael@0: mSelectionTextColor = sc->GetVisitedDependentColor(eCSSProperty_color); michael@0: mHasSelectionShadow = michael@0: nsRuleNode::HasAuthorSpecifiedRules(sc, michael@0: NS_AUTHOR_SPECIFIED_TEXT_SHADOW, michael@0: true); michael@0: if (mHasSelectionShadow) { michael@0: mSelectionShadow = sc->StyleText()->mTextShadow; michael@0: } michael@0: return true; michael@0: } michael@0: } michael@0: michael@0: nscolor selectionBGColor = michael@0: LookAndFeel::GetColor(LookAndFeel::eColorID_TextSelectBackground); michael@0: michael@0: if (selectionStatus == nsISelectionController::SELECTION_ATTENTION) { michael@0: mSelectionBGColor = michael@0: LookAndFeel::GetColor( michael@0: LookAndFeel::eColorID_TextSelectBackgroundAttention); michael@0: mSelectionBGColor = EnsureDifferentColors(mSelectionBGColor, michael@0: selectionBGColor); michael@0: } else if (selectionStatus != nsISelectionController::SELECTION_ON) { michael@0: mSelectionBGColor = michael@0: LookAndFeel::GetColor(LookAndFeel::eColorID_TextSelectBackgroundDisabled); michael@0: mSelectionBGColor = EnsureDifferentColors(mSelectionBGColor, michael@0: selectionBGColor); michael@0: } else { michael@0: mSelectionBGColor = selectionBGColor; michael@0: } michael@0: michael@0: mSelectionTextColor = michael@0: LookAndFeel::GetColor(LookAndFeel::eColorID_TextSelectForeground); michael@0: michael@0: if (mResolveColors) { michael@0: // On MacOS X, we don't exchange text color and BG color. michael@0: if (mSelectionTextColor == NS_DONT_CHANGE_COLOR) { michael@0: nsCSSProperty property = mFrame->IsSVGText() ? eCSSProperty_fill : michael@0: eCSSProperty_color; michael@0: nscoord frameColor = mFrame->GetVisitedDependentColor(property); michael@0: mSelectionTextColor = EnsureDifferentColors(frameColor, mSelectionBGColor); michael@0: } else { michael@0: EnsureSufficientContrast(&mSelectionTextColor, &mSelectionBGColor); michael@0: } michael@0: } else { michael@0: if (mSelectionTextColor == NS_DONT_CHANGE_COLOR) { michael@0: mSelectionTextColor = NS_SAME_AS_FOREGROUND_COLOR; michael@0: } michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: nsTextPaintStyle::nsSelectionStyle* michael@0: nsTextPaintStyle::GetSelectionStyle(int32_t aIndex) michael@0: { michael@0: InitSelectionStyle(aIndex); michael@0: return &mSelectionStyle[aIndex]; michael@0: } michael@0: michael@0: struct StyleIDs { michael@0: LookAndFeel::ColorID mForeground, mBackground, mLine; michael@0: LookAndFeel::IntID mLineStyle; michael@0: LookAndFeel::FloatID mLineRelativeSize; michael@0: }; michael@0: static StyleIDs SelectionStyleIDs[] = { michael@0: { LookAndFeel::eColorID_IMERawInputForeground, michael@0: LookAndFeel::eColorID_IMERawInputBackground, michael@0: LookAndFeel::eColorID_IMERawInputUnderline, michael@0: LookAndFeel::eIntID_IMERawInputUnderlineStyle, michael@0: LookAndFeel::eFloatID_IMEUnderlineRelativeSize }, michael@0: { LookAndFeel::eColorID_IMESelectedRawTextForeground, michael@0: LookAndFeel::eColorID_IMESelectedRawTextBackground, michael@0: LookAndFeel::eColorID_IMESelectedRawTextUnderline, michael@0: LookAndFeel::eIntID_IMESelectedRawTextUnderlineStyle, michael@0: LookAndFeel::eFloatID_IMEUnderlineRelativeSize }, michael@0: { LookAndFeel::eColorID_IMEConvertedTextForeground, michael@0: LookAndFeel::eColorID_IMEConvertedTextBackground, michael@0: LookAndFeel::eColorID_IMEConvertedTextUnderline, michael@0: LookAndFeel::eIntID_IMEConvertedTextUnderlineStyle, michael@0: LookAndFeel::eFloatID_IMEUnderlineRelativeSize }, michael@0: { LookAndFeel::eColorID_IMESelectedConvertedTextForeground, michael@0: LookAndFeel::eColorID_IMESelectedConvertedTextBackground, michael@0: LookAndFeel::eColorID_IMESelectedConvertedTextUnderline, michael@0: LookAndFeel::eIntID_IMESelectedConvertedTextUnderline, michael@0: LookAndFeel::eFloatID_IMEUnderlineRelativeSize }, michael@0: { LookAndFeel::eColorID_LAST_COLOR, michael@0: LookAndFeel::eColorID_LAST_COLOR, michael@0: LookAndFeel::eColorID_SpellCheckerUnderline, michael@0: LookAndFeel::eIntID_SpellCheckerUnderlineStyle, michael@0: LookAndFeel::eFloatID_SpellCheckerUnderlineRelativeSize } michael@0: }; michael@0: michael@0: void michael@0: nsTextPaintStyle::InitSelectionStyle(int32_t aIndex) michael@0: { michael@0: NS_ASSERTION(aIndex >= 0 && aIndex < 5, "aIndex is invalid"); michael@0: nsSelectionStyle* selectionStyle = &mSelectionStyle[aIndex]; michael@0: if (selectionStyle->mInit) michael@0: return; michael@0: michael@0: StyleIDs* styleIDs = &SelectionStyleIDs[aIndex]; michael@0: michael@0: nscolor foreColor, backColor; michael@0: if (styleIDs->mForeground == LookAndFeel::eColorID_LAST_COLOR) { michael@0: foreColor = NS_SAME_AS_FOREGROUND_COLOR; michael@0: } else { michael@0: foreColor = LookAndFeel::GetColor(styleIDs->mForeground); michael@0: } michael@0: if (styleIDs->mBackground == LookAndFeel::eColorID_LAST_COLOR) { michael@0: backColor = NS_TRANSPARENT; michael@0: } else { michael@0: backColor = LookAndFeel::GetColor(styleIDs->mBackground); michael@0: } michael@0: michael@0: // Convert special color to actual color michael@0: NS_ASSERTION(foreColor != NS_TRANSPARENT, michael@0: "foreColor cannot be NS_TRANSPARENT"); michael@0: NS_ASSERTION(backColor != NS_SAME_AS_FOREGROUND_COLOR, michael@0: "backColor cannot be NS_SAME_AS_FOREGROUND_COLOR"); michael@0: NS_ASSERTION(backColor != NS_40PERCENT_FOREGROUND_COLOR, michael@0: "backColor cannot be NS_40PERCENT_FOREGROUND_COLOR"); michael@0: michael@0: if (mResolveColors) { michael@0: foreColor = GetResolvedForeColor(foreColor, GetTextColor(), backColor); michael@0: michael@0: if (NS_GET_A(backColor) > 0) michael@0: EnsureSufficientContrast(&foreColor, &backColor); michael@0: } michael@0: michael@0: nscolor lineColor; michael@0: float relativeSize; michael@0: uint8_t lineStyle; michael@0: GetSelectionUnderline(mPresContext, aIndex, michael@0: &lineColor, &relativeSize, &lineStyle); michael@0: michael@0: if (mResolveColors) michael@0: lineColor = GetResolvedForeColor(lineColor, foreColor, backColor); michael@0: michael@0: selectionStyle->mTextColor = foreColor; michael@0: selectionStyle->mBGColor = backColor; michael@0: selectionStyle->mUnderlineColor = lineColor; michael@0: selectionStyle->mUnderlineStyle = lineStyle; michael@0: selectionStyle->mUnderlineRelativeSize = relativeSize; michael@0: selectionStyle->mInit = true; michael@0: } michael@0: michael@0: /* static */ bool michael@0: nsTextPaintStyle::GetSelectionUnderline(nsPresContext* aPresContext, michael@0: int32_t aIndex, michael@0: nscolor* aLineColor, michael@0: float* aRelativeSize, michael@0: uint8_t* aStyle) michael@0: { michael@0: NS_ASSERTION(aPresContext, "aPresContext is null"); michael@0: NS_ASSERTION(aRelativeSize, "aRelativeSize is null"); michael@0: NS_ASSERTION(aStyle, "aStyle is null"); michael@0: NS_ASSERTION(aIndex >= 0 && aIndex < 5, "Index out of range"); michael@0: michael@0: StyleIDs& styleID = SelectionStyleIDs[aIndex]; michael@0: michael@0: nscolor color = LookAndFeel::GetColor(styleID.mLine); michael@0: int32_t style = LookAndFeel::GetInt(styleID.mLineStyle); michael@0: if (style > NS_STYLE_TEXT_DECORATION_STYLE_MAX) { michael@0: NS_ERROR("Invalid underline style value is specified"); michael@0: style = NS_STYLE_TEXT_DECORATION_STYLE_SOLID; michael@0: } michael@0: float size = LookAndFeel::GetFloat(styleID.mLineRelativeSize); michael@0: michael@0: NS_ASSERTION(size, "selection underline relative size must be larger than 0"); michael@0: michael@0: if (aLineColor) { michael@0: *aLineColor = color; michael@0: } michael@0: *aRelativeSize = size; michael@0: *aStyle = style; michael@0: michael@0: return style != NS_STYLE_TEXT_DECORATION_STYLE_NONE && michael@0: color != NS_TRANSPARENT && michael@0: size > 0.0f; michael@0: } michael@0: michael@0: bool michael@0: nsTextPaintStyle::GetSelectionShadow(nsCSSShadowArray** aShadow) michael@0: { michael@0: if (!InitSelectionColorsAndShadow()) { michael@0: return false; michael@0: } michael@0: michael@0: if (mHasSelectionShadow) { michael@0: *aShadow = mSelectionShadow; michael@0: return true; michael@0: } michael@0: michael@0: return false; michael@0: } michael@0: michael@0: inline nscolor Get40PercentColor(nscolor aForeColor, nscolor aBackColor) michael@0: { michael@0: nscolor foreColor = NS_RGBA(NS_GET_R(aForeColor), michael@0: NS_GET_G(aForeColor), michael@0: NS_GET_B(aForeColor), michael@0: (uint8_t)(255 * 0.4f)); michael@0: // Don't use true alpha color for readability. michael@0: return NS_ComposeColors(aBackColor, foreColor); michael@0: } michael@0: michael@0: nscolor michael@0: nsTextPaintStyle::GetResolvedForeColor(nscolor aColor, michael@0: nscolor aDefaultForeColor, michael@0: nscolor aBackColor) michael@0: { michael@0: if (aColor == NS_SAME_AS_FOREGROUND_COLOR) michael@0: return aDefaultForeColor; michael@0: michael@0: if (aColor != NS_40PERCENT_FOREGROUND_COLOR) michael@0: return aColor; michael@0: michael@0: // Get actual background color michael@0: nscolor actualBGColor = aBackColor; michael@0: if (actualBGColor == NS_TRANSPARENT) { michael@0: InitCommonColors(); michael@0: actualBGColor = mFrameBackgroundColor; michael@0: } michael@0: return Get40PercentColor(aDefaultForeColor, actualBGColor); michael@0: } michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: michael@0: #ifdef ACCESSIBILITY michael@0: a11y::AccType michael@0: nsTextFrame::AccessibleType() michael@0: { michael@0: if (IsEmpty()) { michael@0: nsAutoString renderedWhitespace; michael@0: GetRenderedText(&renderedWhitespace, nullptr, nullptr, 0, 1); michael@0: if (renderedWhitespace.IsEmpty()) { michael@0: return a11y::eNoType; michael@0: } michael@0: } michael@0: michael@0: return a11y::eTextLeafType; michael@0: } michael@0: #endif michael@0: michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: void michael@0: nsTextFrame::Init(nsIContent* aContent, michael@0: nsIFrame* aParent, michael@0: nsIFrame* aPrevInFlow) michael@0: { michael@0: NS_ASSERTION(!aPrevInFlow, "Can't be a continuation!"); michael@0: NS_PRECONDITION(aContent->IsNodeOfType(nsINode::eTEXT), michael@0: "Bogus content!"); michael@0: michael@0: // Remove any NewlineOffsetProperty or InFlowContentLengthProperty since they michael@0: // might be invalid if the content was modified while there was no frame michael@0: aContent->DeleteProperty(nsGkAtoms::newline); michael@0: if (PresContext()->BidiEnabled()) { michael@0: aContent->DeleteProperty(nsGkAtoms::flowlength); michael@0: } michael@0: michael@0: // Since our content has a frame now, this flag is no longer needed. michael@0: aContent->UnsetFlags(NS_CREATE_FRAME_IF_NON_WHITESPACE); michael@0: michael@0: // We're not a continuing frame. michael@0: // mContentOffset = 0; not necessary since we get zeroed out at init michael@0: nsFrame::Init(aContent, aParent, aPrevInFlow); michael@0: } michael@0: michael@0: void michael@0: nsTextFrame::ClearFrameOffsetCache() michael@0: { michael@0: // See if we need to remove ourselves from the offset cache michael@0: if (GetStateBits() & TEXT_IN_OFFSET_CACHE) { michael@0: nsIFrame* primaryFrame = mContent->GetPrimaryFrame(); michael@0: if (primaryFrame) { michael@0: // The primary frame might be null here. For example, nsLineBox::DeleteLineList michael@0: // just destroys the frames in order, which means that the primary frame is already michael@0: // dead if we're a continuing text frame, in which case, all of its properties are michael@0: // gone, and we don't need to worry about deleting this property here. michael@0: primaryFrame->Properties().Delete(OffsetToFrameProperty()); michael@0: } michael@0: RemoveStateBits(TEXT_IN_OFFSET_CACHE); michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsTextFrame::DestroyFrom(nsIFrame* aDestructRoot) michael@0: { michael@0: ClearFrameOffsetCache(); michael@0: michael@0: // We might want to clear NS_CREATE_FRAME_IF_NON_WHITESPACE or michael@0: // NS_REFRAME_IF_WHITESPACE on mContent here, since our parent frame michael@0: // type might be changing. Not clear whether it's worth it. michael@0: ClearTextRuns(); michael@0: if (mNextContinuation) { michael@0: mNextContinuation->SetPrevInFlow(nullptr); michael@0: } michael@0: // Let the base class destroy the frame michael@0: nsFrame::DestroyFrom(aDestructRoot); michael@0: } michael@0: michael@0: class nsContinuingTextFrame : public nsTextFrame { michael@0: public: michael@0: NS_DECL_FRAMEARENA_HELPERS michael@0: michael@0: friend nsIFrame* NS_NewContinuingTextFrame(nsIPresShell* aPresShell, nsStyleContext* aContext); michael@0: michael@0: virtual void Init(nsIContent* aContent, michael@0: nsIFrame* aParent, michael@0: nsIFrame* aPrevInFlow) MOZ_OVERRIDE; michael@0: michael@0: virtual void DestroyFrom(nsIFrame* aDestructRoot) MOZ_OVERRIDE; michael@0: michael@0: virtual nsIFrame* GetPrevContinuation() const MOZ_OVERRIDE { michael@0: return mPrevContinuation; michael@0: } michael@0: virtual void SetPrevContinuation(nsIFrame* aPrevContinuation) MOZ_OVERRIDE { michael@0: NS_ASSERTION (!aPrevContinuation || GetType() == aPrevContinuation->GetType(), michael@0: "setting a prev continuation with incorrect type!"); michael@0: NS_ASSERTION (!nsSplittableFrame::IsInPrevContinuationChain(aPrevContinuation, this), michael@0: "creating a loop in continuation chain!"); michael@0: mPrevContinuation = aPrevContinuation; michael@0: RemoveStateBits(NS_FRAME_IS_FLUID_CONTINUATION); michael@0: } michael@0: virtual nsIFrame* GetPrevInFlowVirtual() const MOZ_OVERRIDE { michael@0: return GetPrevInFlow(); michael@0: } michael@0: nsIFrame* GetPrevInFlow() const { michael@0: return (GetStateBits() & NS_FRAME_IS_FLUID_CONTINUATION) ? mPrevContinuation : nullptr; michael@0: } michael@0: virtual void SetPrevInFlow(nsIFrame* aPrevInFlow) MOZ_OVERRIDE { michael@0: NS_ASSERTION (!aPrevInFlow || GetType() == aPrevInFlow->GetType(), michael@0: "setting a prev in flow with incorrect type!"); michael@0: NS_ASSERTION (!nsSplittableFrame::IsInPrevContinuationChain(aPrevInFlow, this), michael@0: "creating a loop in continuation chain!"); michael@0: mPrevContinuation = aPrevInFlow; michael@0: AddStateBits(NS_FRAME_IS_FLUID_CONTINUATION); michael@0: } michael@0: virtual nsIFrame* FirstInFlow() const MOZ_OVERRIDE; michael@0: virtual nsIFrame* FirstContinuation() const MOZ_OVERRIDE; michael@0: michael@0: virtual void AddInlineMinWidth(nsRenderingContext *aRenderingContext, michael@0: InlineMinWidthData *aData) MOZ_OVERRIDE; michael@0: virtual void AddInlinePrefWidth(nsRenderingContext *aRenderingContext, michael@0: InlinePrefWidthData *aData) MOZ_OVERRIDE; michael@0: michael@0: virtual nsresult GetRenderedText(nsAString* aString = nullptr, michael@0: gfxSkipChars* aSkipChars = nullptr, michael@0: gfxSkipCharsIterator* aSkipIter = nullptr, michael@0: uint32_t aSkippedStartOffset = 0, michael@0: uint32_t aSkippedMaxLength = UINT32_MAX) MOZ_OVERRIDE michael@0: { return NS_ERROR_NOT_IMPLEMENTED; } // Call on a primary text frame only michael@0: michael@0: protected: michael@0: nsContinuingTextFrame(nsStyleContext* aContext) : nsTextFrame(aContext) {} michael@0: nsIFrame* mPrevContinuation; michael@0: }; michael@0: michael@0: void michael@0: nsContinuingTextFrame::Init(nsIContent* aContent, michael@0: nsIFrame* aParent, michael@0: nsIFrame* aPrevInFlow) michael@0: { michael@0: NS_ASSERTION(aPrevInFlow, "Must be a continuation!"); michael@0: // NOTE: bypassing nsTextFrame::Init!!! michael@0: nsFrame::Init(aContent, aParent, aPrevInFlow); michael@0: michael@0: nsTextFrame* nextContinuation = michael@0: static_cast(aPrevInFlow->GetNextContinuation()); michael@0: // Hook the frame into the flow michael@0: SetPrevInFlow(aPrevInFlow); michael@0: aPrevInFlow->SetNextInFlow(this); michael@0: nsTextFrame* prev = static_cast(aPrevInFlow); michael@0: mContentOffset = prev->GetContentOffset() + prev->GetContentLengthHint(); michael@0: NS_ASSERTION(mContentOffset < int32_t(aContent->GetText()->GetLength()), michael@0: "Creating ContinuingTextFrame, but there is no more content"); michael@0: if (prev->StyleContext() != StyleContext()) { michael@0: // We're taking part of prev's text, and its style may be different michael@0: // so clear its textrun which may no longer be valid (and don't set ours) michael@0: prev->ClearTextRuns(); michael@0: } else { michael@0: float inflation = prev->GetFontSizeInflation(); michael@0: SetFontSizeInflation(inflation); michael@0: mTextRun = prev->GetTextRun(nsTextFrame::eInflated); michael@0: if (inflation != 1.0f) { michael@0: gfxTextRun *uninflatedTextRun = michael@0: prev->GetTextRun(nsTextFrame::eNotInflated); michael@0: if (uninflatedTextRun) { michael@0: SetTextRun(uninflatedTextRun, nsTextFrame::eNotInflated, 1.0f); michael@0: } michael@0: } michael@0: } michael@0: if (aPrevInFlow->GetStateBits() & NS_FRAME_IS_BIDI) { michael@0: FramePropertyTable *propTable = PresContext()->PropertyTable(); michael@0: // Get all the properties from the prev-in-flow first to take michael@0: // advantage of the propTable's cache and simplify the assertion below michael@0: void* embeddingLevel = propTable->Get(aPrevInFlow, EmbeddingLevelProperty()); michael@0: void* baseLevel = propTable->Get(aPrevInFlow, BaseLevelProperty()); michael@0: void* paragraphDepth = propTable->Get(aPrevInFlow, ParagraphDepthProperty()); michael@0: propTable->Set(this, EmbeddingLevelProperty(), embeddingLevel); michael@0: propTable->Set(this, BaseLevelProperty(), baseLevel); michael@0: propTable->Set(this, ParagraphDepthProperty(), paragraphDepth); michael@0: michael@0: if (nextContinuation) { michael@0: SetNextContinuation(nextContinuation); michael@0: nextContinuation->SetPrevContinuation(this); michael@0: // Adjust next-continuations' content offset as needed. michael@0: while (nextContinuation && michael@0: nextContinuation->GetContentOffset() < mContentOffset) { michael@0: NS_ASSERTION( michael@0: embeddingLevel == propTable->Get(nextContinuation, EmbeddingLevelProperty()) && michael@0: baseLevel == propTable->Get(nextContinuation, BaseLevelProperty()) && michael@0: paragraphDepth == propTable->Get(nextContinuation, ParagraphDepthProperty()), michael@0: "stealing text from different type of BIDI continuation"); michael@0: nextContinuation->mContentOffset = mContentOffset; michael@0: nextContinuation = static_cast(nextContinuation->GetNextContinuation()); michael@0: } michael@0: } michael@0: mState |= NS_FRAME_IS_BIDI; michael@0: } // prev frame is bidi michael@0: } michael@0: michael@0: void michael@0: nsContinuingTextFrame::DestroyFrom(nsIFrame* aDestructRoot) michael@0: { michael@0: ClearFrameOffsetCache(); michael@0: michael@0: // The text associated with this frame will become associated with our michael@0: // prev-continuation. If that means the text has changed style, then michael@0: // we need to wipe out the text run for the text. michael@0: // Note that mPrevContinuation can be null if we're destroying the whole michael@0: // frame chain from the start to the end. michael@0: // If this frame is mentioned in the userData for a textrun (say michael@0: // because there's a direction change at the start of this frame), then michael@0: // we have to clear the textrun because we're going away and the michael@0: // textrun had better not keep a dangling reference to us. michael@0: if (IsInTextRunUserData() || michael@0: (mPrevContinuation && michael@0: mPrevContinuation->StyleContext() != StyleContext())) { michael@0: ClearTextRuns(); michael@0: // Clear the previous continuation's text run also, so that it can rebuild michael@0: // the text run to include our text. michael@0: if (mPrevContinuation) { michael@0: nsTextFrame *prevContinuationText = michael@0: static_cast(mPrevContinuation); michael@0: prevContinuationText->ClearTextRuns(); michael@0: } michael@0: } michael@0: nsSplittableFrame::RemoveFromFlow(this); michael@0: // Let the base class destroy the frame michael@0: nsFrame::DestroyFrom(aDestructRoot); michael@0: } michael@0: michael@0: nsIFrame* michael@0: nsContinuingTextFrame::FirstInFlow() const michael@0: { michael@0: // Can't cast to |nsContinuingTextFrame*| because the first one isn't. michael@0: nsIFrame *firstInFlow, michael@0: *previous = const_cast michael@0: (static_cast(this)); michael@0: do { michael@0: firstInFlow = previous; michael@0: previous = firstInFlow->GetPrevInFlow(); michael@0: } while (previous); michael@0: MOZ_ASSERT(firstInFlow, "post-condition failed"); michael@0: return firstInFlow; michael@0: } michael@0: michael@0: nsIFrame* michael@0: nsContinuingTextFrame::FirstContinuation() const michael@0: { michael@0: // Can't cast to |nsContinuingTextFrame*| because the first one isn't. michael@0: nsIFrame *firstContinuation, michael@0: *previous = const_cast michael@0: (static_cast(mPrevContinuation)); michael@0: michael@0: NS_ASSERTION(previous, "How can an nsContinuingTextFrame be the first continuation?"); michael@0: michael@0: do { michael@0: firstContinuation = previous; michael@0: previous = firstContinuation->GetPrevContinuation(); michael@0: } while (previous); michael@0: MOZ_ASSERT(firstContinuation, "post-condition failed"); michael@0: return firstContinuation; michael@0: } michael@0: michael@0: // XXX Do we want to do all the work for the first-in-flow or do the michael@0: // work for each part? (Be careful of first-letter / first-line, though, michael@0: // especially first-line!) Doing all the work on the first-in-flow has michael@0: // the advantage of avoiding the potential for incremental reflow bugs, michael@0: // but depends on our maintining the frame tree in reasonable ways even michael@0: // for edge cases (block-within-inline splits, nextBidi, etc.) michael@0: michael@0: // XXX We really need to make :first-letter happen during frame michael@0: // construction. michael@0: michael@0: // Needed for text frames in XUL. michael@0: /* virtual */ nscoord michael@0: nsTextFrame::GetMinWidth(nsRenderingContext *aRenderingContext) michael@0: { michael@0: return nsLayoutUtils::MinWidthFromInline(this, aRenderingContext); michael@0: } michael@0: michael@0: // Needed for text frames in XUL. michael@0: /* virtual */ nscoord michael@0: nsTextFrame::GetPrefWidth(nsRenderingContext *aRenderingContext) michael@0: { michael@0: return nsLayoutUtils::PrefWidthFromInline(this, aRenderingContext); michael@0: } michael@0: michael@0: /* virtual */ void michael@0: nsContinuingTextFrame::AddInlineMinWidth(nsRenderingContext *aRenderingContext, michael@0: InlineMinWidthData *aData) michael@0: { michael@0: // Do nothing, since the first-in-flow accounts for everything. michael@0: return; michael@0: } michael@0: michael@0: /* virtual */ void michael@0: nsContinuingTextFrame::AddInlinePrefWidth(nsRenderingContext *aRenderingContext, michael@0: InlinePrefWidthData *aData) michael@0: { michael@0: // Do nothing, since the first-in-flow accounts for everything. michael@0: return; michael@0: } michael@0: michael@0: static void michael@0: DestroySelectionDetails(SelectionDetails* aDetails) michael@0: { michael@0: while (aDetails) { michael@0: SelectionDetails* next = aDetails->mNext; michael@0: delete aDetails; michael@0: aDetails = next; michael@0: } michael@0: } michael@0: michael@0: //---------------------------------------------------------------------- michael@0: michael@0: #if defined(DEBUG_rbs) || defined(DEBUG_bzbarsky) michael@0: static void michael@0: VerifyNotDirty(nsFrameState state) michael@0: { michael@0: bool isZero = state & NS_FRAME_FIRST_REFLOW; michael@0: bool isDirty = state & NS_FRAME_IS_DIRTY; michael@0: if (!isZero && isDirty) michael@0: NS_WARNING("internal offsets may be out-of-sync"); michael@0: } michael@0: #define DEBUG_VERIFY_NOT_DIRTY(state) \ michael@0: VerifyNotDirty(state) michael@0: #else michael@0: #define DEBUG_VERIFY_NOT_DIRTY(state) michael@0: #endif michael@0: michael@0: nsIFrame* michael@0: NS_NewTextFrame(nsIPresShell* aPresShell, nsStyleContext* aContext) michael@0: { michael@0: return new (aPresShell) nsTextFrame(aContext); michael@0: } michael@0: michael@0: NS_IMPL_FRAMEARENA_HELPERS(nsTextFrame) michael@0: michael@0: nsIFrame* michael@0: NS_NewContinuingTextFrame(nsIPresShell* aPresShell, nsStyleContext* aContext) michael@0: { michael@0: return new (aPresShell) nsContinuingTextFrame(aContext); michael@0: } michael@0: michael@0: NS_IMPL_FRAMEARENA_HELPERS(nsContinuingTextFrame) michael@0: michael@0: nsTextFrame::~nsTextFrame() michael@0: { michael@0: } michael@0: michael@0: nsresult michael@0: nsTextFrame::GetCursor(const nsPoint& aPoint, michael@0: nsIFrame::Cursor& aCursor) michael@0: { michael@0: FillCursorInformationFromStyle(StyleUserInterface(), aCursor); michael@0: if (NS_STYLE_CURSOR_AUTO == aCursor.mCursor) { michael@0: aCursor.mCursor = NS_STYLE_CURSOR_TEXT; michael@0: // If this is editable, we should ignore tabindex value. michael@0: if (mContent->IsEditable()) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: // If tabindex >= 0, use default cursor to indicate it's not selectable michael@0: nsIFrame *ancestorFrame = this; michael@0: while ((ancestorFrame = ancestorFrame->GetParent()) != nullptr) { michael@0: nsIContent *ancestorContent = ancestorFrame->GetContent(); michael@0: if (ancestorContent && ancestorContent->HasAttr(kNameSpaceID_None, nsGkAtoms::tabindex)) { michael@0: nsAutoString tabIndexStr; michael@0: ancestorContent->GetAttr(kNameSpaceID_None, nsGkAtoms::tabindex, tabIndexStr); michael@0: if (!tabIndexStr.IsEmpty()) { michael@0: nsresult rv; michael@0: int32_t tabIndexVal = tabIndexStr.ToInteger(&rv); michael@0: if (NS_SUCCEEDED(rv) && tabIndexVal >= 0) { michael@0: aCursor.mCursor = NS_STYLE_CURSOR_DEFAULT; michael@0: break; michael@0: } michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsIFrame* michael@0: nsTextFrame::LastInFlow() const michael@0: { michael@0: nsTextFrame* lastInFlow = const_cast(this); michael@0: while (lastInFlow->GetNextInFlow()) { michael@0: lastInFlow = static_cast(lastInFlow->GetNextInFlow()); michael@0: } michael@0: MOZ_ASSERT(lastInFlow, "post-condition failed"); michael@0: return lastInFlow; michael@0: } michael@0: michael@0: nsIFrame* michael@0: nsTextFrame::LastContinuation() const michael@0: { michael@0: nsTextFrame* lastContinuation = const_cast(this); michael@0: while (lastContinuation->mNextContinuation) { michael@0: lastContinuation = michael@0: static_cast(lastContinuation->mNextContinuation); michael@0: } michael@0: MOZ_ASSERT(lastContinuation, "post-condition failed"); michael@0: return lastContinuation; michael@0: } michael@0: michael@0: void michael@0: nsTextFrame::InvalidateFrame(uint32_t aDisplayItemKey) michael@0: { michael@0: if (IsSVGText()) { michael@0: nsIFrame* svgTextFrame = michael@0: nsLayoutUtils::GetClosestFrameOfType(GetParent(), michael@0: nsGkAtoms::svgTextFrame); michael@0: svgTextFrame->InvalidateFrame(); michael@0: return; michael@0: } michael@0: nsTextFrameBase::InvalidateFrame(aDisplayItemKey); michael@0: } michael@0: michael@0: void michael@0: nsTextFrame::InvalidateFrameWithRect(const nsRect& aRect, uint32_t aDisplayItemKey) michael@0: { michael@0: if (IsSVGText()) { michael@0: nsIFrame* svgTextFrame = michael@0: nsLayoutUtils::GetClosestFrameOfType(GetParent(), michael@0: nsGkAtoms::svgTextFrame); michael@0: svgTextFrame->InvalidateFrame(); michael@0: return; michael@0: } michael@0: nsTextFrameBase::InvalidateFrameWithRect(aRect, aDisplayItemKey); michael@0: } michael@0: michael@0: gfxTextRun* michael@0: nsTextFrame::GetUninflatedTextRun() michael@0: { michael@0: return static_cast( michael@0: Properties().Get(UninflatedTextRunProperty())); michael@0: } michael@0: michael@0: void michael@0: nsTextFrame::SetTextRun(gfxTextRun* aTextRun, TextRunType aWhichTextRun, michael@0: float aInflation) michael@0: { michael@0: NS_ASSERTION(aTextRun, "must have text run"); michael@0: michael@0: // Our inflated text run is always stored in mTextRun. In the cases michael@0: // where our current inflation is not 1.0, however, we store two text michael@0: // runs, and the uninflated one goes in a frame property. We never michael@0: // store a single text run in both. michael@0: if (aWhichTextRun == eInflated) { michael@0: if (HasFontSizeInflation() && aInflation == 1.0f) { michael@0: // FIXME: Probably shouldn't do this within each SetTextRun michael@0: // method, but it doesn't hurt. michael@0: ClearTextRun(nullptr, nsTextFrame::eNotInflated); michael@0: } michael@0: SetFontSizeInflation(aInflation); michael@0: } else { michael@0: NS_ABORT_IF_FALSE(aInflation == 1.0f, "unexpected inflation"); michael@0: if (HasFontSizeInflation()) { michael@0: Properties().Set(UninflatedTextRunProperty(), aTextRun); michael@0: return; michael@0: } michael@0: // fall through to setting mTextRun michael@0: } michael@0: michael@0: mTextRun = aTextRun; michael@0: michael@0: // FIXME: Add assertions testing the relationship between michael@0: // GetFontSizeInflation() and whether we have an uninflated text run michael@0: // (but be aware that text runs can go away). michael@0: } michael@0: michael@0: bool michael@0: nsTextFrame::RemoveTextRun(gfxTextRun* aTextRun) michael@0: { michael@0: if (aTextRun == mTextRun) { michael@0: mTextRun = nullptr; michael@0: return true; michael@0: } michael@0: FrameProperties props = Properties(); michael@0: if ((GetStateBits() & TEXT_HAS_FONT_INFLATION) && michael@0: props.Get(UninflatedTextRunProperty()) == aTextRun) { michael@0: props.Delete(UninflatedTextRunProperty()); michael@0: return true; michael@0: } michael@0: return false; michael@0: } michael@0: michael@0: void michael@0: nsTextFrame::ClearTextRun(nsTextFrame* aStartContinuation, michael@0: TextRunType aWhichTextRun) michael@0: { michael@0: gfxTextRun* textRun = GetTextRun(aWhichTextRun); michael@0: if (!textRun) { michael@0: return; michael@0: } michael@0: michael@0: DebugOnly checkmTextrun = textRun == mTextRun; michael@0: UnhookTextRunFromFrames(textRun, aStartContinuation); michael@0: MOZ_ASSERT(checkmTextrun ? !mTextRun michael@0: : !Properties().Get(UninflatedTextRunProperty())); michael@0: michael@0: // see comments in BuildTextRunForFrames... michael@0: // if (textRun->GetFlags() & gfxFontGroup::TEXT_IS_PERSISTENT) { michael@0: // NS_ERROR("Shouldn't reach here for now..."); michael@0: // // the textrun's text may be referencing a DOM node that has changed, michael@0: // // so we'd better kill this textrun now. michael@0: // if (textRun->GetExpirationState()->IsTracked()) { michael@0: // gTextRuns->RemoveFromCache(textRun); michael@0: // } michael@0: // delete textRun; michael@0: // return; michael@0: // } michael@0: michael@0: if (!textRun->GetUserData()) { michael@0: // Remove it now because it's not doing anything useful michael@0: gTextRuns->RemoveFromCache(textRun); michael@0: delete textRun; michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsTextFrame::DisconnectTextRuns() michael@0: { michael@0: MOZ_ASSERT(!IsInTextRunUserData(), michael@0: "Textrun mentions this frame in its user data so we can't just disconnect"); michael@0: mTextRun = nullptr; michael@0: if ((GetStateBits() & TEXT_HAS_FONT_INFLATION)) { michael@0: Properties().Delete(UninflatedTextRunProperty()); michael@0: } michael@0: } michael@0: michael@0: nsresult michael@0: nsTextFrame::CharacterDataChanged(CharacterDataChangeInfo* aInfo) michael@0: { michael@0: mContent->DeleteProperty(nsGkAtoms::newline); michael@0: if (PresContext()->BidiEnabled()) { michael@0: mContent->DeleteProperty(nsGkAtoms::flowlength); michael@0: } michael@0: michael@0: // Find the first frame whose text has changed. Frames that are entirely michael@0: // before the text change are completely unaffected. michael@0: nsTextFrame* next; michael@0: nsTextFrame* textFrame = this; michael@0: while (true) { michael@0: next = static_cast(textFrame->GetNextContinuation()); michael@0: if (!next || next->GetContentOffset() > int32_t(aInfo->mChangeStart)) michael@0: break; michael@0: textFrame = next; michael@0: } michael@0: michael@0: int32_t endOfChangedText = aInfo->mChangeStart + aInfo->mReplaceLength; michael@0: nsTextFrame* lastDirtiedFrame = nullptr; michael@0: michael@0: nsIPresShell* shell = PresContext()->GetPresShell(); michael@0: do { michael@0: // textFrame contained deleted text (or the insertion point, michael@0: // if this was a pure insertion). michael@0: textFrame->mState &= ~TEXT_WHITESPACE_FLAGS; michael@0: textFrame->ClearTextRuns(); michael@0: if (!lastDirtiedFrame || michael@0: lastDirtiedFrame->GetParent() != textFrame->GetParent()) { michael@0: // Ask the parent frame to reflow me. michael@0: shell->FrameNeedsReflow(textFrame, nsIPresShell::eStyleChange, michael@0: NS_FRAME_IS_DIRTY); michael@0: lastDirtiedFrame = textFrame; michael@0: } else { michael@0: // if the parent is a block, we're cheating here because we should michael@0: // be marking our line dirty, but we're not. nsTextFrame::SetLength michael@0: // will do that when it gets called during reflow. michael@0: textFrame->AddStateBits(NS_FRAME_IS_DIRTY); michael@0: } michael@0: textFrame->InvalidateFrame(); michael@0: michael@0: // Below, frames that start after the deleted text will be adjusted so that michael@0: // their offsets move with the trailing unchanged text. If this change michael@0: // deletes more text than it inserts, those frame offsets will decrease. michael@0: // We need to maintain the invariant that mContentOffset is non-decreasing michael@0: // along the continuation chain. So we need to ensure that frames that michael@0: // started in the deleted text are all still starting before the michael@0: // unchanged text. michael@0: if (textFrame->mContentOffset > endOfChangedText) { michael@0: textFrame->mContentOffset = endOfChangedText; michael@0: } michael@0: michael@0: textFrame = static_cast(textFrame->GetNextContinuation()); michael@0: } while (textFrame && textFrame->GetContentOffset() < int32_t(aInfo->mChangeEnd)); michael@0: michael@0: // This is how much the length of the string changed by --- i.e., michael@0: // how much the trailing unchanged text moved. michael@0: int32_t sizeChange = michael@0: aInfo->mChangeStart + aInfo->mReplaceLength - aInfo->mChangeEnd; michael@0: michael@0: if (sizeChange) { michael@0: // Fix the offsets of the text frames that start in the trailing michael@0: // unchanged text. michael@0: while (textFrame) { michael@0: textFrame->mContentOffset += sizeChange; michael@0: // XXX we could rescue some text runs by adjusting their user data michael@0: // to reflect the change in DOM offsets michael@0: textFrame->ClearTextRuns(); michael@0: textFrame = static_cast(textFrame->GetNextContinuation()); michael@0: } michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: /* virtual */ void michael@0: nsTextFrame::DidSetStyleContext(nsStyleContext* aOldStyleContext) michael@0: { michael@0: nsFrame::DidSetStyleContext(aOldStyleContext); michael@0: } michael@0: michael@0: class nsDisplayTextGeometry : public nsDisplayItemGenericGeometry michael@0: { michael@0: public: michael@0: nsDisplayTextGeometry(nsDisplayItem* aItem, nsDisplayListBuilder* aBuilder) michael@0: : nsDisplayItemGenericGeometry(aItem, aBuilder) michael@0: { michael@0: nsTextFrame* f = static_cast(aItem->Frame()); michael@0: f->GetTextDecorations(f->PresContext(), nsTextFrame::eResolvedColors, mDecorations); michael@0: } michael@0: michael@0: /** michael@0: * We store the computed text decorations here since they are michael@0: * computed using style data from parent frames. Any changes to these michael@0: * styles will only invalidate the parent frame and not this frame. michael@0: */ michael@0: nsTextFrame::TextDecorations mDecorations; michael@0: }; michael@0: michael@0: class nsDisplayText : public nsCharClipDisplayItem { michael@0: public: michael@0: nsDisplayText(nsDisplayListBuilder* aBuilder, nsTextFrame* aFrame) : michael@0: nsCharClipDisplayItem(aBuilder, aFrame), michael@0: mDisableSubpixelAA(false) { michael@0: MOZ_COUNT_CTOR(nsDisplayText); michael@0: } michael@0: #ifdef NS_BUILD_REFCNT_LOGGING michael@0: virtual ~nsDisplayText() { michael@0: MOZ_COUNT_DTOR(nsDisplayText); michael@0: } michael@0: #endif michael@0: michael@0: virtual nsRect GetBounds(nsDisplayListBuilder* aBuilder, michael@0: bool* aSnap) MOZ_OVERRIDE { michael@0: *aSnap = false; michael@0: nsRect temp = mFrame->GetVisualOverflowRectRelativeToSelf() + ToReferenceFrame(); michael@0: // Bug 748228 michael@0: temp.Inflate(mFrame->PresContext()->AppUnitsPerDevPixel()); michael@0: return temp; michael@0: } michael@0: virtual void HitTest(nsDisplayListBuilder* aBuilder, const nsRect& aRect, michael@0: HitTestState* aState, michael@0: nsTArray *aOutFrames) MOZ_OVERRIDE { michael@0: if (nsRect(ToReferenceFrame(), mFrame->GetSize()).Intersects(aRect)) { michael@0: aOutFrames->AppendElement(mFrame); michael@0: } michael@0: } michael@0: virtual void Paint(nsDisplayListBuilder* aBuilder, michael@0: nsRenderingContext* aCtx) MOZ_OVERRIDE; michael@0: NS_DISPLAY_DECL_NAME("Text", TYPE_TEXT) michael@0: michael@0: virtual nsRect GetComponentAlphaBounds(nsDisplayListBuilder* aBuilder) MOZ_OVERRIDE michael@0: { michael@0: bool snap; michael@0: return GetBounds(aBuilder, &snap); michael@0: } michael@0: michael@0: virtual nsDisplayItemGeometry* AllocateGeometry(nsDisplayListBuilder* aBuilder) MOZ_OVERRIDE michael@0: { michael@0: return new nsDisplayTextGeometry(this, aBuilder); michael@0: } michael@0: michael@0: virtual void ComputeInvalidationRegion(nsDisplayListBuilder* aBuilder, michael@0: const nsDisplayItemGeometry* aGeometry, michael@0: nsRegion *aInvalidRegion) MOZ_OVERRIDE michael@0: { michael@0: const nsDisplayTextGeometry* geometry = static_cast(aGeometry); michael@0: nsTextFrame* f = static_cast(mFrame); michael@0: michael@0: nsTextFrame::TextDecorations decorations; michael@0: f->GetTextDecorations(f->PresContext(), nsTextFrame::eResolvedColors, decorations); michael@0: michael@0: bool snap; michael@0: nsRect newRect = geometry->mBounds; michael@0: nsRect oldRect = GetBounds(aBuilder, &snap); michael@0: if (decorations != geometry->mDecorations || michael@0: !oldRect.IsEqualInterior(newRect) || michael@0: !geometry->mBorderRect.IsEqualInterior(GetBorderRect())) { michael@0: aInvalidRegion->Or(oldRect, newRect); michael@0: } michael@0: } michael@0: michael@0: virtual void DisableComponentAlpha() MOZ_OVERRIDE { michael@0: mDisableSubpixelAA = true; michael@0: } michael@0: michael@0: bool mDisableSubpixelAA; michael@0: }; michael@0: michael@0: void michael@0: nsDisplayText::Paint(nsDisplayListBuilder* aBuilder, michael@0: nsRenderingContext* aCtx) { michael@0: PROFILER_LABEL("nsDisplayText", "Paint"); michael@0: // Add 1 pixel of dirty area around mVisibleRect to allow us to paint michael@0: // antialiased pixels beyond the measured text extents. michael@0: // This is temporary until we do this in the actual calculation of text extents. michael@0: nsRect extraVisible = mVisibleRect; michael@0: nscoord appUnitsPerDevPixel = mFrame->PresContext()->AppUnitsPerDevPixel(); michael@0: extraVisible.Inflate(appUnitsPerDevPixel, appUnitsPerDevPixel); michael@0: nsTextFrame* f = static_cast(mFrame); michael@0: michael@0: gfxContextAutoDisableSubpixelAntialiasing disable(aCtx->ThebesContext(), michael@0: mDisableSubpixelAA); michael@0: NS_ASSERTION(mLeftEdge >= 0, "illegal left edge"); michael@0: NS_ASSERTION(mRightEdge >= 0, "illegal right edge"); michael@0: f->PaintText(aCtx, ToReferenceFrame(), extraVisible, *this); michael@0: } michael@0: michael@0: void michael@0: nsTextFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder, michael@0: const nsRect& aDirtyRect, michael@0: const nsDisplayListSet& aLists) michael@0: { michael@0: if (!IsVisibleForPainting(aBuilder)) michael@0: return; michael@0: michael@0: DO_GLOBAL_REFLOW_COUNT_DSP("nsTextFrame"); michael@0: michael@0: aLists.Content()->AppendNewToTop( michael@0: new (aBuilder) nsDisplayText(aBuilder, this)); michael@0: } michael@0: michael@0: static nsIFrame* michael@0: GetGeneratedContentOwner(nsIFrame* aFrame, bool* aIsBefore) michael@0: { michael@0: *aIsBefore = false; michael@0: while (aFrame && (aFrame->GetStateBits() & NS_FRAME_GENERATED_CONTENT)) { michael@0: if (aFrame->StyleContext()->GetPseudo() == nsCSSPseudoElements::before) { michael@0: *aIsBefore = true; michael@0: } michael@0: aFrame = aFrame->GetParent(); michael@0: } michael@0: return aFrame; michael@0: } michael@0: michael@0: SelectionDetails* michael@0: nsTextFrame::GetSelectionDetails() michael@0: { michael@0: const nsFrameSelection* frameSelection = GetConstFrameSelection(); michael@0: if (frameSelection->GetTableCellSelection()) { michael@0: return nullptr; michael@0: } michael@0: if (!(GetStateBits() & NS_FRAME_GENERATED_CONTENT)) { michael@0: SelectionDetails* details = michael@0: frameSelection->LookUpSelection(mContent, GetContentOffset(), michael@0: GetContentLength(), false); michael@0: SelectionDetails* sd; michael@0: for (sd = details; sd; sd = sd->mNext) { michael@0: sd->mStart += mContentOffset; michael@0: sd->mEnd += mContentOffset; michael@0: } michael@0: return details; michael@0: } michael@0: michael@0: // Check if the beginning or end of the element is selected, depending on michael@0: // whether we're :before content or :after content. michael@0: bool isBefore; michael@0: nsIFrame* owner = GetGeneratedContentOwner(this, &isBefore); michael@0: if (!owner || !owner->GetContent()) michael@0: return nullptr; michael@0: michael@0: SelectionDetails* details = michael@0: frameSelection->LookUpSelection(owner->GetContent(), michael@0: isBefore ? 0 : owner->GetContent()->GetChildCount(), 0, false); michael@0: SelectionDetails* sd; michael@0: for (sd = details; sd; sd = sd->mNext) { michael@0: // The entire text is selected! michael@0: sd->mStart = GetContentOffset(); michael@0: sd->mEnd = GetContentEnd(); michael@0: } michael@0: return details; michael@0: } michael@0: michael@0: static void michael@0: PaintSelectionBackground(gfxContext* aCtx, nsPresContext* aPresContext, michael@0: nscolor aColor, const gfxRect& aDirtyRect, michael@0: const gfxRect& aRect, michael@0: nsTextFrame::DrawPathCallbacks* aCallbacks) michael@0: { michael@0: if (aCallbacks) { michael@0: aCallbacks->NotifyBeforeSelectionBackground(aColor); michael@0: } michael@0: michael@0: gfxRect r = aRect.Intersect(aDirtyRect); michael@0: // For now, we need to put this in pixel coordinates michael@0: int32_t app = aPresContext->AppUnitsPerDevPixel(); michael@0: aCtx->NewPath(); michael@0: // pixel-snap michael@0: aCtx->Rectangle(gfxRect(r.X() / app, r.Y() / app, michael@0: r.Width() / app, r.Height() / app), true); michael@0: michael@0: if (aCallbacks) { michael@0: aCallbacks->NotifySelectionBackgroundPathEmitted(); michael@0: } else { michael@0: aCtx->SetColor(gfxRGBA(aColor)); michael@0: aCtx->Fill(); michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsTextFrame::GetTextDecorations( michael@0: nsPresContext* aPresContext, michael@0: nsTextFrame::TextDecorationColorResolution aColorResolution, michael@0: nsTextFrame::TextDecorations& aDecorations) michael@0: { michael@0: const nsCompatibility compatMode = aPresContext->CompatibilityMode(); michael@0: michael@0: bool useOverride = false; michael@0: nscolor overrideColor = NS_RGBA(0, 0, 0, 0); michael@0: michael@0: // frameTopOffset represents the offset to f's top from our baseline in our michael@0: // coordinate space michael@0: // baselineOffset represents the offset from our baseline to f's baseline or michael@0: // the nearest block's baseline, in our coordinate space, whichever is closest michael@0: // during the particular iteration michael@0: nscoord frameTopOffset = mAscent, michael@0: baselineOffset = 0; michael@0: michael@0: bool nearestBlockFound = false; michael@0: michael@0: for (nsIFrame* f = this, *fChild = nullptr; michael@0: f; michael@0: fChild = f, michael@0: f = nsLayoutUtils::GetParentOrPlaceholderFor(f)) michael@0: { michael@0: nsStyleContext *const context = f->StyleContext(); michael@0: if (!context->HasTextDecorationLines()) { michael@0: break; michael@0: } michael@0: michael@0: const nsStyleTextReset *const styleText = context->StyleTextReset(); michael@0: const uint8_t textDecorations = styleText->mTextDecorationLine; michael@0: michael@0: if (!useOverride && michael@0: (NS_STYLE_TEXT_DECORATION_LINE_OVERRIDE_ALL & textDecorations)) { michael@0: // This handles the La michael@0: // la la case. The link underline should be green. michael@0: useOverride = true; michael@0: overrideColor = michael@0: nsLayoutUtils::GetColor(f, eCSSProperty_text_decoration_color); michael@0: } michael@0: michael@0: const bool firstBlock = !nearestBlockFound && nsLayoutUtils::GetAsBlock(f); michael@0: michael@0: // Not updating positions once we hit a parent block is equivalent to michael@0: // the CSS 2.1 spec that blocks should propagate decorations down to their michael@0: // children (albeit the style should be preserved) michael@0: // However, if we're vertically aligned within a block, then we need to michael@0: // recover the right baseline from the line by querying the FrameProperty michael@0: // that should be set (see nsLineLayout::VerticalAlignLine). michael@0: if (firstBlock) { michael@0: // At this point, fChild can't be null since TextFrames can't be blocks michael@0: if (fChild->VerticalAlignEnum() != NS_STYLE_VERTICAL_ALIGN_BASELINE) { michael@0: // Since offset is the offset in the child's coordinate space, we have michael@0: // to undo the accumulation to bring the transform out of the block's michael@0: // coordinate space michael@0: baselineOffset = michael@0: frameTopOffset - fChild->GetNormalPosition().y michael@0: - NS_PTR_TO_INT32( michael@0: fChild->Properties().Get(nsIFrame::LineBaselineOffset())); michael@0: } michael@0: } michael@0: else if (!nearestBlockFound) { michael@0: baselineOffset = frameTopOffset - f->GetBaseline(); michael@0: } michael@0: michael@0: nearestBlockFound = nearestBlockFound || firstBlock; michael@0: frameTopOffset += f->GetNormalPosition().y; michael@0: michael@0: const uint8_t style = styleText->GetDecorationStyle(); michael@0: if (textDecorations) { michael@0: nscolor color; michael@0: if (useOverride) { michael@0: color = overrideColor; michael@0: } else if (IsSVGText()) { michael@0: // XXX We might want to do something with text-decoration-color when michael@0: // painting SVG text, but it's not clear what we should do. We michael@0: // at least need SVG text decorations to paint with 'fill' if michael@0: // text-decoration-color has its initial value currentColor. michael@0: // We could choose to interpret currentColor as "currentFill" michael@0: // for SVG text, and have e.g. text-decoration-color:red to michael@0: // override the fill paint of the decoration. michael@0: color = aColorResolution == eResolvedColors ? michael@0: nsLayoutUtils::GetColor(f, eCSSProperty_fill) : michael@0: NS_SAME_AS_FOREGROUND_COLOR; michael@0: } else { michael@0: color = nsLayoutUtils::GetColor(f, eCSSProperty_text_decoration_color); michael@0: } michael@0: michael@0: if (textDecorations & NS_STYLE_TEXT_DECORATION_LINE_UNDERLINE) { michael@0: aDecorations.mUnderlines.AppendElement( michael@0: nsTextFrame::LineDecoration(f, baselineOffset, color, style)); michael@0: } michael@0: if (textDecorations & NS_STYLE_TEXT_DECORATION_LINE_OVERLINE) { michael@0: aDecorations.mOverlines.AppendElement( michael@0: nsTextFrame::LineDecoration(f, baselineOffset, color, style)); michael@0: } michael@0: if (textDecorations & NS_STYLE_TEXT_DECORATION_LINE_LINE_THROUGH) { michael@0: aDecorations.mStrikes.AppendElement( michael@0: nsTextFrame::LineDecoration(f, baselineOffset, color, style)); michael@0: } michael@0: } michael@0: michael@0: // In all modes, if we're on an inline-block or inline-table (or michael@0: // inline-stack, inline-box, inline-grid), we're done. michael@0: uint8_t display = f->GetDisplay(); michael@0: if (display != NS_STYLE_DISPLAY_INLINE && michael@0: nsStyleDisplay::IsDisplayTypeInlineOutside(display)) { michael@0: break; michael@0: } michael@0: michael@0: if (compatMode == eCompatibility_NavQuirks) { michael@0: // In quirks mode, if we're on an HTML table element, we're done. michael@0: if (f->GetContent()->IsHTML(nsGkAtoms::table)) { michael@0: break; michael@0: } michael@0: } else { michael@0: // In standards/almost-standards mode, if we're on an michael@0: // absolutely-positioned element or a floating element, we're done. michael@0: if (f->IsFloating() || f->IsAbsolutelyPositioned()) { michael@0: break; michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: static float michael@0: GetInflationForTextDecorations(nsIFrame* aFrame, nscoord aInflationMinFontSize) michael@0: { michael@0: if (aFrame->IsSVGText()) { michael@0: const nsIFrame* container = aFrame; michael@0: while (container->GetType() != nsGkAtoms::svgTextFrame) { michael@0: container = container->GetParent(); michael@0: } michael@0: NS_ASSERTION(container, "expected to find an ancestor SVGTextFrame"); michael@0: return michael@0: static_cast(container)->GetFontSizeScaleFactor(); michael@0: } michael@0: return nsLayoutUtils::FontSizeInflationInner(aFrame, aInflationMinFontSize); michael@0: } michael@0: michael@0: void michael@0: nsTextFrame::UnionAdditionalOverflow(nsPresContext* aPresContext, michael@0: const nsHTMLReflowState& aBlockReflowState, michael@0: PropertyProvider& aProvider, michael@0: nsRect* aVisualOverflowRect, michael@0: bool aIncludeTextDecorations) michael@0: { michael@0: // Text-shadow overflows michael@0: nsRect shadowRect = michael@0: nsLayoutUtils::GetTextShadowRectsUnion(*aVisualOverflowRect, this); michael@0: aVisualOverflowRect->UnionRect(*aVisualOverflowRect, shadowRect); michael@0: michael@0: if (IsFloatingFirstLetterChild()) { michael@0: // The underline/overline drawable area must be contained in the overflow michael@0: // rect when this is in floating first letter frame at *both* modes. michael@0: nsIFrame* firstLetterFrame = aBlockReflowState.frame; michael@0: uint8_t decorationStyle = firstLetterFrame->StyleContext()-> michael@0: StyleTextReset()->GetDecorationStyle(); michael@0: // If the style is none, let's include decoration line rect as solid style michael@0: // since changing the style from none to solid/dotted/dashed doesn't cause michael@0: // reflow. michael@0: if (decorationStyle == NS_STYLE_TEXT_DECORATION_STYLE_NONE) { michael@0: decorationStyle = NS_STYLE_TEXT_DECORATION_STYLE_SOLID; michael@0: } michael@0: nsFontMetrics* fontMetrics = aProvider.GetFontMetrics(); michael@0: nscoord underlineOffset, underlineSize; michael@0: fontMetrics->GetUnderline(underlineOffset, underlineSize); michael@0: nscoord maxAscent = fontMetrics->MaxAscent(); michael@0: michael@0: gfxFloat appUnitsPerDevUnit = aPresContext->AppUnitsPerDevPixel(); michael@0: gfxFloat gfxWidth = aVisualOverflowRect->width / appUnitsPerDevUnit; michael@0: gfxFloat gfxAscent = gfxFloat(mAscent) / appUnitsPerDevUnit; michael@0: gfxFloat gfxMaxAscent = maxAscent / appUnitsPerDevUnit; michael@0: gfxFloat gfxUnderlineSize = underlineSize / appUnitsPerDevUnit; michael@0: gfxFloat gfxUnderlineOffset = underlineOffset / appUnitsPerDevUnit; michael@0: nsRect underlineRect = michael@0: nsCSSRendering::GetTextDecorationRect(aPresContext, michael@0: gfxSize(gfxWidth, gfxUnderlineSize), gfxAscent, gfxUnderlineOffset, michael@0: NS_STYLE_TEXT_DECORATION_LINE_UNDERLINE, decorationStyle); michael@0: nsRect overlineRect = michael@0: nsCSSRendering::GetTextDecorationRect(aPresContext, michael@0: gfxSize(gfxWidth, gfxUnderlineSize), gfxAscent, gfxMaxAscent, michael@0: NS_STYLE_TEXT_DECORATION_LINE_OVERLINE, decorationStyle); michael@0: michael@0: aVisualOverflowRect->UnionRect(*aVisualOverflowRect, underlineRect); michael@0: aVisualOverflowRect->UnionRect(*aVisualOverflowRect, overlineRect); michael@0: michael@0: // XXX If strikeoutSize is much thicker than the underlineSize, it may michael@0: // cause overflowing from the overflow rect. However, such case michael@0: // isn't realistic, we don't need to compute it now. michael@0: } michael@0: if (aIncludeTextDecorations) { michael@0: // Since CSS 2.1 requires that text-decoration defined on ancestors maintain michael@0: // style and position, they can be drawn at virtually any y-offset, so michael@0: // maxima and minima are required to reliably generate the rectangle for michael@0: // them michael@0: TextDecorations textDecs; michael@0: GetTextDecorations(aPresContext, eResolvedColors, textDecs); michael@0: if (textDecs.HasDecorationLines()) { michael@0: nscoord inflationMinFontSize = michael@0: nsLayoutUtils::InflationMinFontSizeFor(aBlockReflowState.frame); michael@0: michael@0: const nscoord width = GetSize().width; michael@0: const gfxFloat appUnitsPerDevUnit = aPresContext->AppUnitsPerDevPixel(), michael@0: gfxWidth = width / appUnitsPerDevUnit, michael@0: ascent = gfxFloat(mAscent) / appUnitsPerDevUnit; michael@0: nscoord top(nscoord_MAX), bottom(nscoord_MIN); michael@0: // Below we loop through all text decorations and compute the rectangle michael@0: // containing all of them, in this frame's coordinate space michael@0: for (uint32_t i = 0; i < textDecs.mUnderlines.Length(); ++i) { michael@0: const LineDecoration& dec = textDecs.mUnderlines[i]; michael@0: uint8_t decorationStyle = dec.mStyle; michael@0: // If the style is solid, let's include decoration line rect of solid michael@0: // style since changing the style from none to solid/dotted/dashed michael@0: // doesn't cause reflow. michael@0: if (decorationStyle == NS_STYLE_TEXT_DECORATION_STYLE_NONE) { michael@0: decorationStyle = NS_STYLE_TEXT_DECORATION_STYLE_SOLID; michael@0: } michael@0: michael@0: float inflation = michael@0: GetInflationForTextDecorations(dec.mFrame, inflationMinFontSize); michael@0: const gfxFont::Metrics metrics = michael@0: GetFirstFontMetrics(GetFontGroupForFrame(dec.mFrame, inflation)); michael@0: michael@0: const nsRect decorationRect = michael@0: nsCSSRendering::GetTextDecorationRect(aPresContext, michael@0: gfxSize(gfxWidth, metrics.underlineSize), michael@0: ascent, metrics.underlineOffset, michael@0: NS_STYLE_TEXT_DECORATION_LINE_UNDERLINE, decorationStyle) + michael@0: nsPoint(0, -dec.mBaselineOffset); michael@0: michael@0: top = std::min(decorationRect.y, top); michael@0: bottom = std::max(decorationRect.YMost(), bottom); michael@0: } michael@0: for (uint32_t i = 0; i < textDecs.mOverlines.Length(); ++i) { michael@0: const LineDecoration& dec = textDecs.mOverlines[i]; michael@0: uint8_t decorationStyle = dec.mStyle; michael@0: // If the style is solid, let's include decoration line rect of solid michael@0: // style since changing the style from none to solid/dotted/dashed michael@0: // doesn't cause reflow. michael@0: if (decorationStyle == NS_STYLE_TEXT_DECORATION_STYLE_NONE) { michael@0: decorationStyle = NS_STYLE_TEXT_DECORATION_STYLE_SOLID; michael@0: } michael@0: michael@0: float inflation = michael@0: GetInflationForTextDecorations(dec.mFrame, inflationMinFontSize); michael@0: const gfxFont::Metrics metrics = michael@0: GetFirstFontMetrics(GetFontGroupForFrame(dec.mFrame, inflation)); michael@0: michael@0: const nsRect decorationRect = michael@0: nsCSSRendering::GetTextDecorationRect(aPresContext, michael@0: gfxSize(gfxWidth, metrics.underlineSize), michael@0: ascent, metrics.maxAscent, michael@0: NS_STYLE_TEXT_DECORATION_LINE_OVERLINE, decorationStyle) + michael@0: nsPoint(0, -dec.mBaselineOffset); michael@0: michael@0: top = std::min(decorationRect.y, top); michael@0: bottom = std::max(decorationRect.YMost(), bottom); michael@0: } michael@0: for (uint32_t i = 0; i < textDecs.mStrikes.Length(); ++i) { michael@0: const LineDecoration& dec = textDecs.mStrikes[i]; michael@0: uint8_t decorationStyle = dec.mStyle; michael@0: // If the style is solid, let's include decoration line rect of solid michael@0: // style since changing the style from none to solid/dotted/dashed michael@0: // doesn't cause reflow. michael@0: if (decorationStyle == NS_STYLE_TEXT_DECORATION_STYLE_NONE) { michael@0: decorationStyle = NS_STYLE_TEXT_DECORATION_STYLE_SOLID; michael@0: } michael@0: michael@0: float inflation = michael@0: GetInflationForTextDecorations(dec.mFrame, inflationMinFontSize); michael@0: const gfxFont::Metrics metrics = michael@0: GetFirstFontMetrics(GetFontGroupForFrame(dec.mFrame, inflation)); michael@0: michael@0: const nsRect decorationRect = michael@0: nsCSSRendering::GetTextDecorationRect(aPresContext, michael@0: gfxSize(gfxWidth, metrics.strikeoutSize), michael@0: ascent, metrics.strikeoutOffset, michael@0: NS_STYLE_TEXT_DECORATION_LINE_LINE_THROUGH, decorationStyle) + michael@0: nsPoint(0, -dec.mBaselineOffset); michael@0: top = std::min(decorationRect.y, top); michael@0: bottom = std::max(decorationRect.YMost(), bottom); michael@0: } michael@0: michael@0: aVisualOverflowRect->UnionRect(*aVisualOverflowRect, michael@0: nsRect(0, top, width, bottom - top)); michael@0: } michael@0: } michael@0: // When this frame is not selected, the text-decoration area must be in michael@0: // frame bounds. michael@0: if (!IsSelected() || michael@0: !CombineSelectionUnderlineRect(aPresContext, *aVisualOverflowRect)) michael@0: return; michael@0: AddStateBits(TEXT_SELECTION_UNDERLINE_OVERFLOWED); michael@0: } michael@0: michael@0: static gfxFloat michael@0: ComputeDescentLimitForSelectionUnderline(nsPresContext* aPresContext, michael@0: nsTextFrame* aFrame, michael@0: const gfxFont::Metrics& aFontMetrics) michael@0: { michael@0: gfxFloat app = aPresContext->AppUnitsPerDevPixel(); michael@0: nscoord lineHeightApp = michael@0: nsHTMLReflowState::CalcLineHeight(aFrame->GetContent(), michael@0: aFrame->StyleContext(), NS_AUTOHEIGHT, michael@0: aFrame->GetFontSizeInflation()); michael@0: gfxFloat lineHeight = gfxFloat(lineHeightApp) / app; michael@0: if (lineHeight <= aFontMetrics.maxHeight) { michael@0: return aFontMetrics.maxDescent; michael@0: } michael@0: return aFontMetrics.maxDescent + (lineHeight - aFontMetrics.maxHeight) / 2; michael@0: } michael@0: michael@0: michael@0: // Make sure this stays in sync with DrawSelectionDecorations below michael@0: static const SelectionType SelectionTypesWithDecorations = michael@0: nsISelectionController::SELECTION_SPELLCHECK | michael@0: nsISelectionController::SELECTION_IME_RAWINPUT | michael@0: nsISelectionController::SELECTION_IME_SELECTEDRAWTEXT | michael@0: nsISelectionController::SELECTION_IME_CONVERTEDTEXT | michael@0: nsISelectionController::SELECTION_IME_SELECTEDCONVERTEDTEXT; michael@0: michael@0: static gfxFloat michael@0: ComputeSelectionUnderlineHeight(nsPresContext* aPresContext, michael@0: const gfxFont::Metrics& aFontMetrics, michael@0: SelectionType aSelectionType) michael@0: { michael@0: switch (aSelectionType) { michael@0: case nsISelectionController::SELECTION_IME_RAWINPUT: michael@0: case nsISelectionController::SELECTION_IME_SELECTEDRAWTEXT: michael@0: case nsISelectionController::SELECTION_IME_CONVERTEDTEXT: michael@0: case nsISelectionController::SELECTION_IME_SELECTEDCONVERTEDTEXT: michael@0: return aFontMetrics.underlineSize; michael@0: case nsISelectionController::SELECTION_SPELLCHECK: { michael@0: // The thickness of the spellchecker underline shouldn't honor the font michael@0: // metrics. It should be constant pixels value which is decided from the michael@0: // default font size. Note that if the actual font size is smaller than michael@0: // the default font size, we should use the actual font size because the michael@0: // computed value from the default font size can be too thick for the michael@0: // current font size. michael@0: int32_t defaultFontSize = michael@0: aPresContext->AppUnitsToDevPixels(nsStyleFont(aPresContext).mFont.size); michael@0: gfxFloat fontSize = std::min(gfxFloat(defaultFontSize), michael@0: aFontMetrics.emHeight); michael@0: fontSize = std::max(fontSize, 1.0); michael@0: return ceil(fontSize / 20); michael@0: } michael@0: default: michael@0: NS_WARNING("Requested underline style is not valid"); michael@0: return aFontMetrics.underlineSize; michael@0: } michael@0: } michael@0: michael@0: enum DecorationType { michael@0: eNormalDecoration, michael@0: eSelectionDecoration michael@0: }; michael@0: michael@0: static void michael@0: PaintDecorationLine(nsIFrame* aFrame, michael@0: gfxContext* const aCtx, michael@0: const gfxRect& aDirtyRect, michael@0: nscolor aColor, michael@0: const nscolor* aOverrideColor, michael@0: const gfxPoint& aPt, michael@0: gfxFloat aXInFrame, michael@0: const gfxSize& aLineSize, michael@0: gfxFloat aAscent, michael@0: gfxFloat aOffset, michael@0: uint8_t aDecoration, michael@0: uint8_t aStyle, michael@0: DecorationType aDecorationType, michael@0: nsTextFrame::DrawPathCallbacks* aCallbacks, michael@0: gfxFloat aDescentLimit = -1.0) michael@0: { michael@0: nscolor lineColor = aOverrideColor ? *aOverrideColor : aColor; michael@0: if (aCallbacks) { michael@0: if (aDecorationType == eNormalDecoration) { michael@0: aCallbacks->NotifyBeforeDecorationLine(lineColor); michael@0: } else { michael@0: aCallbacks->NotifyBeforeSelectionDecorationLine(lineColor); michael@0: } michael@0: nsCSSRendering::DecorationLineToPath(aFrame, aCtx, aDirtyRect, lineColor, michael@0: aPt, aXInFrame, aLineSize, aAscent, aOffset, aDecoration, aStyle, michael@0: aDescentLimit); michael@0: if (aDecorationType == eNormalDecoration) { michael@0: aCallbacks->NotifyDecorationLinePathEmitted(); michael@0: } else { michael@0: aCallbacks->NotifySelectionDecorationLinePathEmitted(); michael@0: } michael@0: } else { michael@0: nsCSSRendering::PaintDecorationLine(aFrame, aCtx, aDirtyRect, lineColor, michael@0: aPt, aXInFrame, aLineSize, aAscent, aOffset, aDecoration, aStyle, michael@0: aDescentLimit); michael@0: } michael@0: } michael@0: michael@0: /** michael@0: * This, plus SelectionTypesWithDecorations, encapsulates all knowledge about michael@0: * drawing text decoration for selections. michael@0: */ michael@0: static void DrawSelectionDecorations(gfxContext* aContext, michael@0: const gfxRect& aDirtyRect, michael@0: SelectionType aType, michael@0: nsTextFrame* aFrame, michael@0: nsTextPaintStyle& aTextPaintStyle, michael@0: const TextRangeStyle &aRangeStyle, michael@0: const gfxPoint& aPt, gfxFloat aXInFrame, gfxFloat aWidth, michael@0: gfxFloat aAscent, const gfxFont::Metrics& aFontMetrics, michael@0: nsTextFrame::DrawPathCallbacks* aCallbacks) michael@0: { michael@0: gfxPoint pt(aPt); michael@0: gfxSize size(aWidth, michael@0: ComputeSelectionUnderlineHeight(aTextPaintStyle.PresContext(), michael@0: aFontMetrics, aType)); michael@0: gfxFloat descentLimit = michael@0: ComputeDescentLimitForSelectionUnderline(aTextPaintStyle.PresContext(), michael@0: aFrame, aFontMetrics); michael@0: michael@0: float relativeSize; michael@0: uint8_t style; michael@0: nscolor color; michael@0: int32_t index = michael@0: nsTextPaintStyle::GetUnderlineStyleIndexForSelectionType(aType); michael@0: bool weDefineSelectionUnderline = michael@0: aTextPaintStyle.GetSelectionUnderlineForPaint(index, &color, michael@0: &relativeSize, &style); michael@0: michael@0: switch (aType) { michael@0: case nsISelectionController::SELECTION_IME_RAWINPUT: michael@0: case nsISelectionController::SELECTION_IME_SELECTEDRAWTEXT: michael@0: case nsISelectionController::SELECTION_IME_CONVERTEDTEXT: michael@0: case nsISelectionController::SELECTION_IME_SELECTEDCONVERTEDTEXT: { michael@0: // IME decoration lines should not be drawn on the both ends, i.e., we michael@0: // need to cut both edges of the decoration lines. Because same style michael@0: // IME selections can adjoin, but the users need to be able to know michael@0: // where are the boundaries of the selections. michael@0: // michael@0: // X: underline michael@0: // michael@0: // IME selection #1 IME selection #2 IME selection #3 michael@0: // | | | michael@0: // | XXXXXXXXXXXXXXXXXXX | XXXXXXXXXXXXXXXXXXXX | XXXXXXXXXXXXXXXXXXX michael@0: // +---------------------+----------------------+-------------------- michael@0: // ^ ^ ^ ^ ^ michael@0: // gap gap gap michael@0: pt.x += 1.0; michael@0: size.width -= 2.0; michael@0: if (aRangeStyle.IsDefined()) { michael@0: // If IME defines the style, that should override our definition. michael@0: if (aRangeStyle.IsLineStyleDefined()) { michael@0: if (aRangeStyle.mLineStyle == TextRangeStyle::LINESTYLE_NONE) { michael@0: return; michael@0: } michael@0: style = aRangeStyle.mLineStyle; michael@0: relativeSize = aRangeStyle.mIsBoldLine ? 2.0f : 1.0f; michael@0: } else if (!weDefineSelectionUnderline) { michael@0: // There is no underline style definition. michael@0: return; michael@0: } michael@0: if (aRangeStyle.IsUnderlineColorDefined()) { michael@0: color = aRangeStyle.mUnderlineColor; michael@0: } else if (aRangeStyle.IsForegroundColorDefined()) { michael@0: color = aRangeStyle.mForegroundColor; michael@0: } else { michael@0: NS_ASSERTION(!aRangeStyle.IsBackgroundColorDefined(), michael@0: "Only the background color is defined"); michael@0: color = aTextPaintStyle.GetTextColor(); michael@0: } michael@0: } else if (!weDefineSelectionUnderline) { michael@0: // IME doesn't specify the selection style and we don't define selection michael@0: // underline. michael@0: return; michael@0: } michael@0: break; michael@0: } michael@0: case nsISelectionController::SELECTION_SPELLCHECK: michael@0: if (!weDefineSelectionUnderline) michael@0: return; michael@0: break; michael@0: default: michael@0: NS_WARNING("Requested selection decorations when there aren't any"); michael@0: return; michael@0: } michael@0: size.height *= relativeSize; michael@0: PaintDecorationLine(aFrame, aContext, aDirtyRect, color, nullptr, pt, michael@0: pt.x - aPt.x + aXInFrame, size, aAscent, aFontMetrics.underlineOffset, michael@0: NS_STYLE_TEXT_DECORATION_LINE_UNDERLINE, style, eSelectionDecoration, michael@0: aCallbacks, descentLimit); michael@0: } michael@0: michael@0: /** michael@0: * This function encapsulates all knowledge of how selections affect foreground michael@0: * and background colors. michael@0: * @return true if the selection affects colors, false otherwise michael@0: * @param aForeground the foreground color to use michael@0: * @param aBackground the background color to use, or RGBA(0,0,0,0) if no michael@0: * background should be painted michael@0: */ michael@0: static bool GetSelectionTextColors(SelectionType aType, michael@0: nsTextPaintStyle& aTextPaintStyle, michael@0: const TextRangeStyle &aRangeStyle, michael@0: nscolor* aForeground, nscolor* aBackground) michael@0: { michael@0: switch (aType) { michael@0: case nsISelectionController::SELECTION_NORMAL: michael@0: return aTextPaintStyle.GetSelectionColors(aForeground, aBackground); michael@0: case nsISelectionController::SELECTION_FIND: michael@0: aTextPaintStyle.GetHighlightColors(aForeground, aBackground); michael@0: return true; michael@0: case nsISelectionController::SELECTION_URLSECONDARY: michael@0: aTextPaintStyle.GetURLSecondaryColor(aForeground); michael@0: *aBackground = NS_RGBA(0,0,0,0); michael@0: return true; michael@0: case nsISelectionController::SELECTION_IME_RAWINPUT: michael@0: case nsISelectionController::SELECTION_IME_SELECTEDRAWTEXT: michael@0: case nsISelectionController::SELECTION_IME_CONVERTEDTEXT: michael@0: case nsISelectionController::SELECTION_IME_SELECTEDCONVERTEDTEXT: michael@0: if (aRangeStyle.IsDefined()) { michael@0: *aForeground = aTextPaintStyle.GetTextColor(); michael@0: *aBackground = NS_RGBA(0,0,0,0); michael@0: if (!aRangeStyle.IsForegroundColorDefined() && michael@0: !aRangeStyle.IsBackgroundColorDefined()) { michael@0: return false; michael@0: } michael@0: if (aRangeStyle.IsForegroundColorDefined()) { michael@0: *aForeground = aRangeStyle.mForegroundColor; michael@0: } michael@0: if (aRangeStyle.IsBackgroundColorDefined()) { michael@0: *aBackground = aRangeStyle.mBackgroundColor; michael@0: } michael@0: return true; michael@0: } michael@0: aTextPaintStyle.GetIMESelectionColors( michael@0: nsTextPaintStyle::GetUnderlineStyleIndexForSelectionType(aType), michael@0: aForeground, aBackground); michael@0: return true; michael@0: default: michael@0: *aForeground = aTextPaintStyle.GetTextColor(); michael@0: *aBackground = NS_RGBA(0,0,0,0); michael@0: return false; michael@0: } michael@0: } michael@0: michael@0: /** michael@0: * This sets *aShadow to the appropriate shadow, if any, for the given michael@0: * type of selection. Returns true if *aShadow was set. michael@0: * If text-shadow was not specified, *aShadow is left untouched michael@0: * (NOT reset to null), and the function returns false. michael@0: */ michael@0: static bool GetSelectionTextShadow(nsIFrame* aFrame, michael@0: SelectionType aType, michael@0: nsTextPaintStyle& aTextPaintStyle, michael@0: nsCSSShadowArray** aShadow) michael@0: { michael@0: switch (aType) { michael@0: case nsISelectionController::SELECTION_NORMAL: michael@0: return aTextPaintStyle.GetSelectionShadow(aShadow); michael@0: default: michael@0: return false; michael@0: } michael@0: } michael@0: michael@0: /** michael@0: * This class lets us iterate over chunks of text in a uniform selection state, michael@0: * observing cluster boundaries, in content order, maintaining the current michael@0: * x-offset as we go, and telling whether the text chunk has a hyphen after michael@0: * it or not. The caller is responsible for actually computing the advance michael@0: * width of each chunk. michael@0: */ michael@0: class SelectionIterator { michael@0: public: michael@0: /** michael@0: * aStart and aLength are in the original string. aSelectionDetails is michael@0: * according to the original string. michael@0: * @param aXOffset the offset from the origin of the frame to the start michael@0: * of the text (the left baseline origin for LTR, the right baseline origin michael@0: * for RTL) michael@0: */ michael@0: SelectionIterator(SelectionDetails** aSelectionDetails, michael@0: int32_t aStart, int32_t aLength, michael@0: PropertyProvider& aProvider, gfxTextRun* aTextRun, michael@0: gfxFloat aXOffset); michael@0: michael@0: /** michael@0: * Returns the next segment of uniformly selected (or not) text. michael@0: * @param aXOffset the offset from the origin of the frame to the start michael@0: * of the text (the left baseline origin for LTR, the right baseline origin michael@0: * for RTL) michael@0: * @param aOffset the transformed string offset of the text for this segment michael@0: * @param aLength the transformed string length of the text for this segment michael@0: * @param aHyphenWidth if a hyphen is to be rendered after the text, the michael@0: * width of the hyphen, otherwise zero michael@0: * @param aType the selection type for this segment michael@0: * @param aStyle the selection style for this segment michael@0: * @return false if there are no more segments michael@0: */ michael@0: bool GetNextSegment(gfxFloat* aXOffset, uint32_t* aOffset, uint32_t* aLength, michael@0: gfxFloat* aHyphenWidth, SelectionType* aType, michael@0: TextRangeStyle* aStyle); michael@0: void UpdateWithAdvance(gfxFloat aAdvance) { michael@0: mXOffset += aAdvance*mTextRun->GetDirection(); michael@0: } michael@0: michael@0: private: michael@0: SelectionDetails** mSelectionDetails; michael@0: PropertyProvider& mProvider; michael@0: gfxTextRun* mTextRun; michael@0: gfxSkipCharsIterator mIterator; michael@0: int32_t mOriginalStart; michael@0: int32_t mOriginalEnd; michael@0: gfxFloat mXOffset; michael@0: }; michael@0: michael@0: SelectionIterator::SelectionIterator(SelectionDetails** aSelectionDetails, michael@0: int32_t aStart, int32_t aLength, PropertyProvider& aProvider, michael@0: gfxTextRun* aTextRun, gfxFloat aXOffset) michael@0: : mSelectionDetails(aSelectionDetails), mProvider(aProvider), michael@0: mTextRun(aTextRun), mIterator(aProvider.GetStart()), michael@0: mOriginalStart(aStart), mOriginalEnd(aStart + aLength), michael@0: mXOffset(aXOffset) michael@0: { michael@0: mIterator.SetOriginalOffset(aStart); michael@0: } michael@0: michael@0: bool SelectionIterator::GetNextSegment(gfxFloat* aXOffset, michael@0: uint32_t* aOffset, uint32_t* aLength, gfxFloat* aHyphenWidth, michael@0: SelectionType* aType, TextRangeStyle* aStyle) michael@0: { michael@0: if (mIterator.GetOriginalOffset() >= mOriginalEnd) michael@0: return false; michael@0: michael@0: // save offset into transformed string now michael@0: uint32_t runOffset = mIterator.GetSkippedOffset(); michael@0: michael@0: int32_t index = mIterator.GetOriginalOffset() - mOriginalStart; michael@0: SelectionDetails* sdptr = mSelectionDetails[index]; michael@0: SelectionType type = michael@0: sdptr ? sdptr->mType : nsISelectionController::SELECTION_NONE; michael@0: TextRangeStyle style; michael@0: if (sdptr) { michael@0: style = sdptr->mTextRangeStyle; michael@0: } michael@0: for (++index; mOriginalStart + index < mOriginalEnd; ++index) { michael@0: if (sdptr != mSelectionDetails[index]) michael@0: break; michael@0: } michael@0: mIterator.SetOriginalOffset(index + mOriginalStart); michael@0: michael@0: // Advance to the next cluster boundary michael@0: while (mIterator.GetOriginalOffset() < mOriginalEnd && michael@0: !mIterator.IsOriginalCharSkipped() && michael@0: !mTextRun->IsClusterStart(mIterator.GetSkippedOffset())) { michael@0: mIterator.AdvanceOriginal(1); michael@0: } michael@0: michael@0: bool haveHyphenBreak = michael@0: (mProvider.GetFrame()->GetStateBits() & TEXT_HYPHEN_BREAK) != 0; michael@0: *aOffset = runOffset; michael@0: *aLength = mIterator.GetSkippedOffset() - runOffset; michael@0: *aXOffset = mXOffset; michael@0: *aHyphenWidth = 0; michael@0: if (mIterator.GetOriginalOffset() == mOriginalEnd && haveHyphenBreak) { michael@0: *aHyphenWidth = mProvider.GetHyphenWidth(); michael@0: } michael@0: *aType = type; michael@0: *aStyle = style; michael@0: return true; michael@0: } michael@0: michael@0: static void michael@0: AddHyphenToMetrics(nsTextFrame* aTextFrame, gfxTextRun* aBaseTextRun, michael@0: gfxTextRun::Metrics* aMetrics, michael@0: gfxFont::BoundingBoxType aBoundingBoxType, michael@0: gfxContext* aContext) michael@0: { michael@0: // Fix up metrics to include hyphen michael@0: nsAutoPtr hyphenTextRun( michael@0: GetHyphenTextRun(aBaseTextRun, aContext, aTextFrame)); michael@0: if (!hyphenTextRun.get()) michael@0: return; michael@0: michael@0: gfxTextRun::Metrics hyphenMetrics = michael@0: hyphenTextRun->MeasureText(0, hyphenTextRun->GetLength(), michael@0: aBoundingBoxType, aContext, nullptr); michael@0: aMetrics->CombineWith(hyphenMetrics, aBaseTextRun->IsRightToLeft()); michael@0: } michael@0: michael@0: void michael@0: nsTextFrame::PaintOneShadow(uint32_t aOffset, uint32_t aLength, michael@0: nsCSSShadowItem* aShadowDetails, michael@0: PropertyProvider* aProvider, const nsRect& aDirtyRect, michael@0: const gfxPoint& aFramePt, const gfxPoint& aTextBaselinePt, michael@0: gfxContext* aCtx, const nscolor& aForegroundColor, michael@0: const nsCharClipDisplayItem::ClipEdges& aClipEdges, michael@0: nscoord aLeftSideOffset, gfxRect& aBoundingBox) michael@0: { michael@0: PROFILER_LABEL("nsTextFrame", "PaintOneShadow"); michael@0: gfxPoint shadowOffset(aShadowDetails->mXOffset, aShadowDetails->mYOffset); michael@0: nscoord blurRadius = std::max(aShadowDetails->mRadius, 0); michael@0: michael@0: // This rect is the box which is equivalent to where the shadow will be painted. michael@0: // The origin of aBoundingBox is the text baseline left, so we must translate it by michael@0: // that much in order to make the origin the top-left corner of the text bounding box. michael@0: gfxRect shadowGfxRect = aBoundingBox + michael@0: gfxPoint(aFramePt.x + aLeftSideOffset, aTextBaselinePt.y) + shadowOffset; michael@0: nsRect shadowRect(NSToCoordRound(shadowGfxRect.X()), michael@0: NSToCoordRound(shadowGfxRect.Y()), michael@0: NSToCoordRound(shadowGfxRect.Width()), michael@0: NSToCoordRound(shadowGfxRect.Height())); michael@0: michael@0: nsContextBoxBlur contextBoxBlur; michael@0: gfxContext* shadowContext = contextBoxBlur.Init(shadowRect, 0, blurRadius, michael@0: PresContext()->AppUnitsPerDevPixel(), michael@0: aCtx, aDirtyRect, nullptr); michael@0: if (!shadowContext) michael@0: return; michael@0: michael@0: nscolor shadowColor; michael@0: const nscolor* decorationOverrideColor; michael@0: if (aShadowDetails->mHasColor) { michael@0: shadowColor = aShadowDetails->mColor; michael@0: decorationOverrideColor = &shadowColor; michael@0: } else { michael@0: shadowColor = aForegroundColor; michael@0: decorationOverrideColor = nullptr; michael@0: } michael@0: michael@0: aCtx->Save(); michael@0: aCtx->NewPath(); michael@0: aCtx->SetColor(gfxRGBA(shadowColor)); michael@0: michael@0: // Draw the text onto our alpha-only surface to capture the alpha values. michael@0: // Remember that the box blur context has a device offset on it, so we don't need to michael@0: // translate any coordinates to fit on the surface. michael@0: gfxFloat advanceWidth; michael@0: gfxRect dirtyRect(aDirtyRect.x, aDirtyRect.y, michael@0: aDirtyRect.width, aDirtyRect.height); michael@0: DrawText(shadowContext, dirtyRect, aFramePt + shadowOffset, michael@0: aTextBaselinePt + shadowOffset, aOffset, aLength, *aProvider, michael@0: nsTextPaintStyle(this), michael@0: aCtx == shadowContext ? shadowColor : NS_RGB(0, 0, 0), aClipEdges, michael@0: advanceWidth, (GetStateBits() & TEXT_HYPHEN_BREAK) != 0, michael@0: decorationOverrideColor); michael@0: michael@0: contextBoxBlur.DoPaint(); michael@0: aCtx->Restore(); michael@0: } michael@0: michael@0: // Paints selection backgrounds and text in the correct colors. Also computes michael@0: // aAllTypes, the union of all selection types that are applying to this text. michael@0: bool michael@0: nsTextFrame::PaintTextWithSelectionColors(gfxContext* aCtx, michael@0: const gfxPoint& aFramePt, const gfxPoint& aTextBaselinePt, michael@0: const gfxRect& aDirtyRect, michael@0: PropertyProvider& aProvider, michael@0: uint32_t aContentOffset, uint32_t aContentLength, michael@0: nsTextPaintStyle& aTextPaintStyle, SelectionDetails* aDetails, michael@0: SelectionType* aAllTypes, michael@0: const nsCharClipDisplayItem::ClipEdges& aClipEdges, michael@0: nsTextFrame::DrawPathCallbacks* aCallbacks) michael@0: { michael@0: // Figure out which selections control the colors to use for each character. michael@0: AutoFallibleTArray prevailingSelectionsBuffer; michael@0: SelectionDetails** prevailingSelections = michael@0: prevailingSelectionsBuffer.AppendElements(aContentLength); michael@0: if (!prevailingSelections) { michael@0: return false; michael@0: } michael@0: michael@0: SelectionType allTypes = 0; michael@0: for (uint32_t i = 0; i < aContentLength; ++i) { michael@0: prevailingSelections[i] = nullptr; michael@0: } michael@0: michael@0: SelectionDetails *sdptr = aDetails; michael@0: bool anyBackgrounds = false; michael@0: while (sdptr) { michael@0: int32_t start = std::max(0, sdptr->mStart - int32_t(aContentOffset)); michael@0: int32_t end = std::min(int32_t(aContentLength), michael@0: sdptr->mEnd - int32_t(aContentOffset)); michael@0: SelectionType type = sdptr->mType; michael@0: if (start < end) { michael@0: allTypes |= type; michael@0: // Ignore selections that don't set colors michael@0: nscolor foreground, background; michael@0: if (GetSelectionTextColors(type, aTextPaintStyle, sdptr->mTextRangeStyle, michael@0: &foreground, &background)) { michael@0: if (NS_GET_A(background) > 0) { michael@0: anyBackgrounds = true; michael@0: } michael@0: for (int32_t i = start; i < end; ++i) { michael@0: // Favour normal selection over IME selections michael@0: if (!prevailingSelections[i] || michael@0: type < prevailingSelections[i]->mType) { michael@0: prevailingSelections[i] = sdptr; michael@0: } michael@0: } michael@0: } michael@0: } michael@0: sdptr = sdptr->mNext; michael@0: } michael@0: *aAllTypes = allTypes; michael@0: michael@0: if (!allTypes) { michael@0: // Nothing is selected in the given text range. XXX can this still occur? michael@0: return false; michael@0: } michael@0: michael@0: const gfxFloat startXOffset = aTextBaselinePt.x - aFramePt.x; michael@0: gfxFloat xOffset, hyphenWidth; michael@0: uint32_t offset, length; // in transformed string michael@0: SelectionType type; michael@0: TextRangeStyle rangeStyle; michael@0: // Draw background colors michael@0: if (anyBackgrounds) { michael@0: SelectionIterator iterator(prevailingSelections, aContentOffset, aContentLength, michael@0: aProvider, mTextRun, startXOffset); michael@0: while (iterator.GetNextSegment(&xOffset, &offset, &length, &hyphenWidth, michael@0: &type, &rangeStyle)) { michael@0: nscolor foreground, background; michael@0: GetSelectionTextColors(type, aTextPaintStyle, rangeStyle, michael@0: &foreground, &background); michael@0: // Draw background color michael@0: gfxFloat advance = hyphenWidth + michael@0: mTextRun->GetAdvanceWidth(offset, length, &aProvider); michael@0: if (NS_GET_A(background) > 0) { michael@0: gfxFloat x = xOffset - (mTextRun->IsRightToLeft() ? advance : 0); michael@0: PaintSelectionBackground(aCtx, aTextPaintStyle.PresContext(), michael@0: background, aDirtyRect, michael@0: gfxRect(aFramePt.x + x, aFramePt.y, advance, michael@0: GetSize().height), aCallbacks); michael@0: } michael@0: iterator.UpdateWithAdvance(advance); michael@0: } michael@0: } michael@0: michael@0: // Draw text michael@0: const nsStyleText* textStyle = StyleText(); michael@0: nsRect dirtyRect(aDirtyRect.x, aDirtyRect.y, michael@0: aDirtyRect.width, aDirtyRect.height); michael@0: SelectionIterator iterator(prevailingSelections, aContentOffset, aContentLength, michael@0: aProvider, mTextRun, startXOffset); michael@0: while (iterator.GetNextSegment(&xOffset, &offset, &length, &hyphenWidth, michael@0: &type, &rangeStyle)) { michael@0: nscolor foreground, background; michael@0: GetSelectionTextColors(type, aTextPaintStyle, rangeStyle, michael@0: &foreground, &background); michael@0: gfxPoint textBaselinePt(aFramePt.x + xOffset, aTextBaselinePt.y); michael@0: michael@0: // Determine what shadow, if any, to draw - either from textStyle michael@0: // or from the ::-moz-selection pseudo-class if specified there michael@0: nsCSSShadowArray* shadow = textStyle->GetTextShadow(); michael@0: GetSelectionTextShadow(this, type, aTextPaintStyle, &shadow); michael@0: michael@0: // Draw shadows, if any michael@0: if (shadow) { michael@0: gfxTextRun::Metrics shadowMetrics = michael@0: mTextRun->MeasureText(offset, length, gfxFont::LOOSE_INK_EXTENTS, michael@0: nullptr, &aProvider); michael@0: if (GetStateBits() & TEXT_HYPHEN_BREAK) { michael@0: AddHyphenToMetrics(this, mTextRun, &shadowMetrics, michael@0: gfxFont::LOOSE_INK_EXTENTS, aCtx); michael@0: } michael@0: for (uint32_t i = shadow->Length(); i > 0; --i) { michael@0: PaintOneShadow(offset, length, michael@0: shadow->ShadowAt(i - 1), &aProvider, michael@0: dirtyRect, aFramePt, textBaselinePt, aCtx, michael@0: foreground, aClipEdges, michael@0: xOffset - (mTextRun->IsRightToLeft() ? michael@0: shadowMetrics.mBoundingBox.width : 0), michael@0: shadowMetrics.mBoundingBox); michael@0: } michael@0: } michael@0: michael@0: // Draw text segment michael@0: gfxFloat advance; michael@0: michael@0: DrawText(aCtx, aDirtyRect, aFramePt, textBaselinePt, michael@0: offset, length, aProvider, aTextPaintStyle, foreground, aClipEdges, michael@0: advance, hyphenWidth > 0, nullptr, nullptr, aCallbacks); michael@0: if (hyphenWidth) { michael@0: advance += hyphenWidth; michael@0: } michael@0: iterator.UpdateWithAdvance(advance); michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: void michael@0: nsTextFrame::PaintTextSelectionDecorations(gfxContext* aCtx, michael@0: const gfxPoint& aFramePt, michael@0: const gfxPoint& aTextBaselinePt, const gfxRect& aDirtyRect, michael@0: PropertyProvider& aProvider, michael@0: uint32_t aContentOffset, uint32_t aContentLength, michael@0: nsTextPaintStyle& aTextPaintStyle, SelectionDetails* aDetails, michael@0: SelectionType aSelectionType, michael@0: nsTextFrame::DrawPathCallbacks* aCallbacks) michael@0: { michael@0: // Hide text decorations if we're currently hiding @font-face fallback text michael@0: if (aProvider.GetFontGroup()->ShouldSkipDrawing()) michael@0: return; michael@0: michael@0: // Figure out which characters will be decorated for this selection. michael@0: AutoFallibleTArray selectedCharsBuffer; michael@0: SelectionDetails** selectedChars = michael@0: selectedCharsBuffer.AppendElements(aContentLength); michael@0: if (!selectedChars) { michael@0: return; michael@0: } michael@0: for (uint32_t i = 0; i < aContentLength; ++i) { michael@0: selectedChars[i] = nullptr; michael@0: } michael@0: michael@0: SelectionDetails *sdptr = aDetails; michael@0: while (sdptr) { michael@0: if (sdptr->mType == aSelectionType) { michael@0: int32_t start = std::max(0, sdptr->mStart - int32_t(aContentOffset)); michael@0: int32_t end = std::min(int32_t(aContentLength), michael@0: sdptr->mEnd - int32_t(aContentOffset)); michael@0: for (int32_t i = start; i < end; ++i) { michael@0: selectedChars[i] = sdptr; michael@0: } michael@0: } michael@0: sdptr = sdptr->mNext; michael@0: } michael@0: michael@0: gfxFont* firstFont = aProvider.GetFontGroup()->GetFontAt(0); michael@0: if (!firstFont) michael@0: return; // OOM michael@0: gfxFont::Metrics decorationMetrics(firstFont->GetMetrics()); michael@0: decorationMetrics.underlineOffset = michael@0: aProvider.GetFontGroup()->GetUnderlineOffset(); michael@0: michael@0: gfxFloat startXOffset = aTextBaselinePt.x - aFramePt.x; michael@0: SelectionIterator iterator(selectedChars, aContentOffset, aContentLength, michael@0: aProvider, mTextRun, startXOffset); michael@0: gfxFloat xOffset, hyphenWidth; michael@0: uint32_t offset, length; michael@0: int32_t app = aTextPaintStyle.PresContext()->AppUnitsPerDevPixel(); michael@0: // XXX aTextBaselinePt is in AppUnits, shouldn't it be nsFloatPoint? michael@0: gfxPoint pt(0.0, (aTextBaselinePt.y - mAscent) / app); michael@0: gfxRect dirtyRect(aDirtyRect.x / app, aDirtyRect.y / app, michael@0: aDirtyRect.width / app, aDirtyRect.height / app); michael@0: SelectionType type; michael@0: TextRangeStyle selectedStyle; michael@0: while (iterator.GetNextSegment(&xOffset, &offset, &length, &hyphenWidth, michael@0: &type, &selectedStyle)) { michael@0: gfxFloat advance = hyphenWidth + michael@0: mTextRun->GetAdvanceWidth(offset, length, &aProvider); michael@0: if (type == aSelectionType) { michael@0: pt.x = (aFramePt.x + xOffset - michael@0: (mTextRun->IsRightToLeft() ? advance : 0)) / app; michael@0: gfxFloat width = Abs(advance) / app; michael@0: gfxFloat xInFrame = pt.x - (aFramePt.x / app); michael@0: DrawSelectionDecorations(aCtx, dirtyRect, aSelectionType, this, michael@0: aTextPaintStyle, selectedStyle, pt, xInFrame, michael@0: width, mAscent / app, decorationMetrics, michael@0: aCallbacks); michael@0: } michael@0: iterator.UpdateWithAdvance(advance); michael@0: } michael@0: } michael@0: michael@0: bool michael@0: nsTextFrame::PaintTextWithSelection(gfxContext* aCtx, michael@0: const gfxPoint& aFramePt, michael@0: const gfxPoint& aTextBaselinePt, const gfxRect& aDirtyRect, michael@0: PropertyProvider& aProvider, michael@0: uint32_t aContentOffset, uint32_t aContentLength, michael@0: nsTextPaintStyle& aTextPaintStyle, michael@0: const nsCharClipDisplayItem::ClipEdges& aClipEdges, michael@0: gfxTextContextPaint* aContextPaint, michael@0: nsTextFrame::DrawPathCallbacks* aCallbacks) michael@0: { michael@0: NS_ASSERTION(GetContent()->IsSelectionDescendant(), "wrong paint path"); michael@0: michael@0: SelectionDetails* details = GetSelectionDetails(); michael@0: if (!details) { michael@0: return false; michael@0: } michael@0: michael@0: SelectionType allTypes; michael@0: if (!PaintTextWithSelectionColors(aCtx, aFramePt, aTextBaselinePt, aDirtyRect, michael@0: aProvider, aContentOffset, aContentLength, michael@0: aTextPaintStyle, details, &allTypes, michael@0: aClipEdges, aCallbacks)) { michael@0: DestroySelectionDetails(details); michael@0: return false; michael@0: } michael@0: // Iterate through just the selection types that paint decorations and michael@0: // paint decorations for any that actually occur in this frame. Paint michael@0: // higher-numbered selection types below lower-numered ones on the michael@0: // general principal that lower-numbered selections are higher priority. michael@0: allTypes &= SelectionTypesWithDecorations; michael@0: for (int32_t i = nsISelectionController::NUM_SELECTIONTYPES - 1; michael@0: i >= 1; --i) { michael@0: SelectionType type = 1 << (i - 1); michael@0: if (allTypes & type) { michael@0: // There is some selection of this type. Try to paint its decorations michael@0: // (there might not be any for this type but that's OK, michael@0: // PaintTextSelectionDecorations will exit early). michael@0: PaintTextSelectionDecorations(aCtx, aFramePt, aTextBaselinePt, aDirtyRect, michael@0: aProvider, aContentOffset, aContentLength, michael@0: aTextPaintStyle, details, type, michael@0: aCallbacks); michael@0: } michael@0: } michael@0: michael@0: DestroySelectionDetails(details); michael@0: return true; michael@0: } michael@0: michael@0: nscolor michael@0: nsTextFrame::GetCaretColorAt(int32_t aOffset) michael@0: { michael@0: NS_PRECONDITION(aOffset >= 0, "aOffset must be positive"); michael@0: michael@0: nscolor result = nsFrame::GetCaretColorAt(aOffset); michael@0: gfxSkipCharsIterator iter = EnsureTextRun(nsTextFrame::eInflated); michael@0: PropertyProvider provider(this, iter, nsTextFrame::eInflated); michael@0: int32_t contentOffset = provider.GetStart().GetOriginalOffset(); michael@0: int32_t contentLength = provider.GetOriginalLength(); michael@0: NS_PRECONDITION(aOffset >= contentOffset && michael@0: aOffset <= contentOffset + contentLength, michael@0: "aOffset must be in the frame's range"); michael@0: int32_t offsetInFrame = aOffset - contentOffset; michael@0: if (offsetInFrame < 0 || offsetInFrame >= contentLength) { michael@0: return result; michael@0: } michael@0: michael@0: bool isSolidTextColor = true; michael@0: if (IsSVGText()) { michael@0: const nsStyleSVG* style = StyleSVG(); michael@0: if (style->mFill.mType != eStyleSVGPaintType_None && michael@0: style->mFill.mType != eStyleSVGPaintType_Color) { michael@0: isSolidTextColor = false; michael@0: } michael@0: } michael@0: michael@0: nsTextPaintStyle textPaintStyle(this); michael@0: textPaintStyle.SetResolveColors(isSolidTextColor); michael@0: SelectionDetails* details = GetSelectionDetails(); michael@0: SelectionDetails* sdptr = details; michael@0: SelectionType type = 0; michael@0: while (sdptr) { michael@0: int32_t start = std::max(0, sdptr->mStart - contentOffset); michael@0: int32_t end = std::min(contentLength, sdptr->mEnd - contentOffset); michael@0: if (start <= offsetInFrame && offsetInFrame < end && michael@0: (type == 0 || sdptr->mType < type)) { michael@0: nscolor foreground, background; michael@0: if (GetSelectionTextColors(sdptr->mType, textPaintStyle, michael@0: sdptr->mTextRangeStyle, michael@0: &foreground, &background)) { michael@0: if (!isSolidTextColor && michael@0: NS_IS_SELECTION_SPECIAL_COLOR(foreground)) { michael@0: result = NS_RGBA(0, 0, 0, 255); michael@0: } else { michael@0: result = foreground; michael@0: } michael@0: type = sdptr->mType; michael@0: } michael@0: } michael@0: sdptr = sdptr->mNext; michael@0: } michael@0: michael@0: DestroySelectionDetails(details); michael@0: return result; michael@0: } michael@0: michael@0: static uint32_t michael@0: ComputeTransformedLength(PropertyProvider& aProvider) michael@0: { michael@0: gfxSkipCharsIterator iter(aProvider.GetStart()); michael@0: uint32_t start = iter.GetSkippedOffset(); michael@0: iter.AdvanceOriginal(aProvider.GetOriginalLength()); michael@0: return iter.GetSkippedOffset() - start; michael@0: } michael@0: michael@0: bool michael@0: nsTextFrame::MeasureCharClippedText(nscoord aLeftEdge, nscoord aRightEdge, michael@0: nscoord* aSnappedLeftEdge, michael@0: nscoord* aSnappedRightEdge) michael@0: { michael@0: // We need a *reference* rendering context (not one that might have a michael@0: // transform), so we don't have a rendering context argument. michael@0: // XXX get the block and line passed to us somehow! This is slow! michael@0: gfxSkipCharsIterator iter = EnsureTextRun(nsTextFrame::eInflated); michael@0: if (!mTextRun) michael@0: return false; michael@0: michael@0: PropertyProvider provider(this, iter, nsTextFrame::eInflated); michael@0: // Trim trailing whitespace michael@0: provider.InitializeForDisplay(true); michael@0: michael@0: uint32_t startOffset = provider.GetStart().GetSkippedOffset(); michael@0: uint32_t maxLength = ComputeTransformedLength(provider); michael@0: return MeasureCharClippedText(provider, aLeftEdge, aRightEdge, michael@0: &startOffset, &maxLength, michael@0: aSnappedLeftEdge, aSnappedRightEdge); michael@0: } michael@0: michael@0: static uint32_t GetClusterLength(gfxTextRun* aTextRun, michael@0: uint32_t aStartOffset, michael@0: uint32_t aMaxLength, michael@0: bool aIsRTL) michael@0: { michael@0: uint32_t clusterLength = aIsRTL ? 0 : 1; michael@0: while (clusterLength < aMaxLength) { michael@0: if (aTextRun->IsClusterStart(aStartOffset + clusterLength)) { michael@0: if (aIsRTL) { michael@0: ++clusterLength; michael@0: } michael@0: break; michael@0: } michael@0: ++clusterLength; michael@0: } michael@0: return clusterLength; michael@0: } michael@0: michael@0: bool michael@0: nsTextFrame::MeasureCharClippedText(PropertyProvider& aProvider, michael@0: nscoord aLeftEdge, nscoord aRightEdge, michael@0: uint32_t* aStartOffset, michael@0: uint32_t* aMaxLength, michael@0: nscoord* aSnappedLeftEdge, michael@0: nscoord* aSnappedRightEdge) michael@0: { michael@0: *aSnappedLeftEdge = 0; michael@0: *aSnappedRightEdge = 0; michael@0: if (aLeftEdge <= 0 && aRightEdge <= 0) { michael@0: return true; michael@0: } michael@0: michael@0: uint32_t offset = *aStartOffset; michael@0: uint32_t maxLength = *aMaxLength; michael@0: const nscoord frameWidth = GetSize().width; michael@0: const bool rtl = mTextRun->IsRightToLeft(); michael@0: gfxFloat advanceWidth = 0; michael@0: const nscoord startEdge = rtl ? aRightEdge : aLeftEdge; michael@0: if (startEdge > 0) { michael@0: const gfxFloat maxAdvance = gfxFloat(startEdge); michael@0: while (maxLength > 0) { michael@0: uint32_t clusterLength = michael@0: GetClusterLength(mTextRun, offset, maxLength, rtl); michael@0: advanceWidth += michael@0: mTextRun->GetAdvanceWidth(offset, clusterLength, &aProvider); michael@0: maxLength -= clusterLength; michael@0: offset += clusterLength; michael@0: if (advanceWidth >= maxAdvance) { michael@0: break; michael@0: } michael@0: } michael@0: nscoord* snappedStartEdge = rtl ? aSnappedRightEdge : aSnappedLeftEdge; michael@0: *snappedStartEdge = NSToCoordFloor(advanceWidth); michael@0: *aStartOffset = offset; michael@0: } michael@0: michael@0: const nscoord endEdge = rtl ? aLeftEdge : aRightEdge; michael@0: if (endEdge > 0) { michael@0: const gfxFloat maxAdvance = gfxFloat(frameWidth - endEdge); michael@0: while (maxLength > 0) { michael@0: uint32_t clusterLength = michael@0: GetClusterLength(mTextRun, offset, maxLength, rtl); michael@0: gfxFloat nextAdvance = advanceWidth + michael@0: mTextRun->GetAdvanceWidth(offset, clusterLength, &aProvider); michael@0: if (nextAdvance > maxAdvance) { michael@0: break; michael@0: } michael@0: // This cluster fits, include it. michael@0: advanceWidth = nextAdvance; michael@0: maxLength -= clusterLength; michael@0: offset += clusterLength; michael@0: } michael@0: maxLength = offset - *aStartOffset; michael@0: nscoord* snappedEndEdge = rtl ? aSnappedLeftEdge : aSnappedRightEdge; michael@0: *snappedEndEdge = NSToCoordFloor(gfxFloat(frameWidth) - advanceWidth); michael@0: } michael@0: *aMaxLength = maxLength; michael@0: return maxLength != 0; michael@0: } michael@0: michael@0: void michael@0: nsTextFrame::PaintText(nsRenderingContext* aRenderingContext, nsPoint aPt, michael@0: const nsRect& aDirtyRect, michael@0: const nsCharClipDisplayItem& aItem, michael@0: gfxTextContextPaint* aContextPaint, michael@0: nsTextFrame::DrawPathCallbacks* aCallbacks) michael@0: { michael@0: // Don't pass in aRenderingContext here, because we need a *reference* michael@0: // context and aRenderingContext might have some transform in it michael@0: // XXX get the block and line passed to us somehow! This is slow! michael@0: gfxSkipCharsIterator iter = EnsureTextRun(nsTextFrame::eInflated); michael@0: if (!mTextRun) michael@0: return; michael@0: michael@0: PropertyProvider provider(this, iter, nsTextFrame::eInflated); michael@0: // Trim trailing whitespace michael@0: provider.InitializeForDisplay(true); michael@0: michael@0: gfxContext* ctx = aRenderingContext->ThebesContext(); michael@0: const bool rtl = mTextRun->IsRightToLeft(); michael@0: const nscoord frameWidth = GetSize().width; michael@0: gfxPoint framePt(aPt.x, aPt.y); michael@0: gfxPoint textBaselinePt(rtl ? gfxFloat(aPt.x + frameWidth) : framePt.x, michael@0: nsLayoutUtils::GetSnappedBaselineY(this, ctx, aPt.y, mAscent)); michael@0: uint32_t startOffset = provider.GetStart().GetSkippedOffset(); michael@0: uint32_t maxLength = ComputeTransformedLength(provider); michael@0: nscoord snappedLeftEdge, snappedRightEdge; michael@0: if (!MeasureCharClippedText(provider, aItem.mLeftEdge, aItem.mRightEdge, michael@0: &startOffset, &maxLength, &snappedLeftEdge, &snappedRightEdge)) { michael@0: return; michael@0: } michael@0: textBaselinePt.x += rtl ? -snappedRightEdge : snappedLeftEdge; michael@0: nsCharClipDisplayItem::ClipEdges clipEdges(aItem, snappedLeftEdge, michael@0: snappedRightEdge); michael@0: nsTextPaintStyle textPaintStyle(this); michael@0: textPaintStyle.SetResolveColors(!aCallbacks); michael@0: michael@0: gfxRect dirtyRect(aDirtyRect.x, aDirtyRect.y, michael@0: aDirtyRect.width, aDirtyRect.height); michael@0: // Fork off to the (slower) paint-with-selection path if necessary. michael@0: if (IsSelected()) { michael@0: gfxSkipCharsIterator tmp(provider.GetStart()); michael@0: int32_t contentOffset = tmp.ConvertSkippedToOriginal(startOffset); michael@0: int32_t contentLength = michael@0: tmp.ConvertSkippedToOriginal(startOffset + maxLength) - contentOffset; michael@0: if (PaintTextWithSelection(ctx, framePt, textBaselinePt, dirtyRect, michael@0: provider, contentOffset, contentLength, michael@0: textPaintStyle, clipEdges, aContextPaint, michael@0: aCallbacks)) { michael@0: return; michael@0: } michael@0: } michael@0: michael@0: nscolor foregroundColor = textPaintStyle.GetTextColor(); michael@0: if (!aCallbacks) { michael@0: const nsStyleText* textStyle = StyleText(); michael@0: if (textStyle->HasTextShadow()) { michael@0: // Text shadow happens with the last value being painted at the back, michael@0: // ie. it is painted first. michael@0: gfxTextRun::Metrics shadowMetrics = michael@0: mTextRun->MeasureText(startOffset, maxLength, gfxFont::LOOSE_INK_EXTENTS, michael@0: nullptr, &provider); michael@0: for (uint32_t i = textStyle->mTextShadow->Length(); i > 0; --i) { michael@0: PaintOneShadow(startOffset, maxLength, michael@0: textStyle->mTextShadow->ShadowAt(i - 1), &provider, michael@0: aDirtyRect, framePt, textBaselinePt, ctx, michael@0: foregroundColor, clipEdges, michael@0: snappedLeftEdge, shadowMetrics.mBoundingBox); michael@0: } michael@0: } michael@0: } michael@0: michael@0: gfxFloat advanceWidth; michael@0: DrawText(ctx, dirtyRect, framePt, textBaselinePt, startOffset, maxLength, provider, michael@0: textPaintStyle, foregroundColor, clipEdges, advanceWidth, michael@0: (GetStateBits() & TEXT_HYPHEN_BREAK) != 0, michael@0: nullptr, aContextPaint, aCallbacks); michael@0: } michael@0: michael@0: static void michael@0: DrawTextRun(gfxTextRun* aTextRun, michael@0: gfxContext* const aCtx, michael@0: const gfxPoint& aTextBaselinePt, michael@0: uint32_t aOffset, uint32_t aLength, michael@0: PropertyProvider* aProvider, michael@0: nscolor aTextColor, michael@0: gfxFloat* aAdvanceWidth, michael@0: gfxTextContextPaint* aContextPaint, michael@0: nsTextFrame::DrawPathCallbacks* aCallbacks) michael@0: { michael@0: DrawMode drawMode = aCallbacks ? DrawMode::GLYPH_PATH : michael@0: DrawMode::GLYPH_FILL; michael@0: if (aCallbacks) { michael@0: aCallbacks->NotifyBeforeText(aTextColor); michael@0: aTextRun->Draw(aCtx, aTextBaselinePt, drawMode, aOffset, aLength, michael@0: aProvider, aAdvanceWidth, aContextPaint, aCallbacks); michael@0: aCallbacks->NotifyAfterText(); michael@0: } else { michael@0: aCtx->SetColor(gfxRGBA(aTextColor)); michael@0: aTextRun->Draw(aCtx, aTextBaselinePt, drawMode, aOffset, aLength, michael@0: aProvider, aAdvanceWidth, aContextPaint); michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsTextFrame::DrawTextRun(gfxContext* const aCtx, michael@0: const gfxPoint& aTextBaselinePt, michael@0: uint32_t aOffset, uint32_t aLength, michael@0: PropertyProvider& aProvider, michael@0: nscolor aTextColor, michael@0: gfxFloat& aAdvanceWidth, michael@0: bool aDrawSoftHyphen, michael@0: gfxTextContextPaint* aContextPaint, michael@0: nsTextFrame::DrawPathCallbacks* aCallbacks) michael@0: { michael@0: ::DrawTextRun(mTextRun, aCtx, aTextBaselinePt, aOffset, aLength, &aProvider, michael@0: aTextColor, &aAdvanceWidth, aContextPaint, aCallbacks); michael@0: michael@0: if (aDrawSoftHyphen) { michael@0: // Don't use ctx as the context, because we need a reference context here, michael@0: // ctx may be transformed. michael@0: nsAutoPtr hyphenTextRun(GetHyphenTextRun(mTextRun, nullptr, this)); michael@0: if (hyphenTextRun.get()) { michael@0: // For right-to-left text runs, the soft-hyphen is positioned at the left michael@0: // of the text, minus its own width michael@0: gfxFloat hyphenBaselineX = aTextBaselinePt.x + mTextRun->GetDirection() * aAdvanceWidth - michael@0: (mTextRun->IsRightToLeft() ? hyphenTextRun->GetAdvanceWidth(0, hyphenTextRun->GetLength(), nullptr) : 0); michael@0: ::DrawTextRun(hyphenTextRun.get(), aCtx, michael@0: gfxPoint(hyphenBaselineX, aTextBaselinePt.y), michael@0: 0, hyphenTextRun->GetLength(), michael@0: nullptr, aTextColor, nullptr, aContextPaint, aCallbacks); michael@0: } michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsTextFrame::DrawTextRunAndDecorations( michael@0: gfxContext* const aCtx, const gfxRect& aDirtyRect, michael@0: const gfxPoint& aFramePt, const gfxPoint& aTextBaselinePt, michael@0: uint32_t aOffset, uint32_t aLength, michael@0: PropertyProvider& aProvider, michael@0: const nsTextPaintStyle& aTextStyle, michael@0: nscolor aTextColor, michael@0: const nsCharClipDisplayItem::ClipEdges& aClipEdges, michael@0: gfxFloat& aAdvanceWidth, michael@0: bool aDrawSoftHyphen, michael@0: const TextDecorations& aDecorations, michael@0: const nscolor* const aDecorationOverrideColor, michael@0: gfxTextContextPaint* aContextPaint, michael@0: nsTextFrame::DrawPathCallbacks* aCallbacks) michael@0: { michael@0: const gfxFloat app = aTextStyle.PresContext()->AppUnitsPerDevPixel(); michael@0: michael@0: // XXX aFramePt is in AppUnits, shouldn't it be nsFloatPoint? michael@0: nscoord x = NSToCoordRound(aFramePt.x); michael@0: nscoord width = GetRect().width; michael@0: aClipEdges.Intersect(&x, &width); michael@0: michael@0: gfxPoint decPt(x / app, 0); michael@0: gfxSize decSize(width / app, 0); michael@0: const gfxFloat ascent = gfxFloat(mAscent) / app; michael@0: const gfxFloat frameTop = aFramePt.y; michael@0: michael@0: gfxRect dirtyRect(aDirtyRect.x / app, aDirtyRect.y / app, michael@0: aDirtyRect.Width() / app, aDirtyRect.Height() / app); michael@0: michael@0: nscoord inflationMinFontSize = michael@0: nsLayoutUtils::InflationMinFontSizeFor(this); michael@0: michael@0: // Underlines michael@0: for (uint32_t i = aDecorations.mUnderlines.Length(); i-- > 0; ) { michael@0: const LineDecoration& dec = aDecorations.mUnderlines[i]; michael@0: if (dec.mStyle == NS_STYLE_TEXT_DECORATION_STYLE_NONE) { michael@0: continue; michael@0: } michael@0: michael@0: float inflation = michael@0: GetInflationForTextDecorations(dec.mFrame, inflationMinFontSize); michael@0: const gfxFont::Metrics metrics = michael@0: GetFirstFontMetrics(GetFontGroupForFrame(dec.mFrame, inflation)); michael@0: michael@0: decSize.height = metrics.underlineSize; michael@0: decPt.y = (frameTop - dec.mBaselineOffset) / app; michael@0: michael@0: PaintDecorationLine(this, aCtx, dirtyRect, dec.mColor, michael@0: aDecorationOverrideColor, decPt, 0.0, decSize, ascent, michael@0: metrics.underlineOffset, NS_STYLE_TEXT_DECORATION_LINE_UNDERLINE, michael@0: dec.mStyle, eNormalDecoration, aCallbacks); michael@0: } michael@0: // Overlines michael@0: for (uint32_t i = aDecorations.mOverlines.Length(); i-- > 0; ) { michael@0: const LineDecoration& dec = aDecorations.mOverlines[i]; michael@0: if (dec.mStyle == NS_STYLE_TEXT_DECORATION_STYLE_NONE) { michael@0: continue; michael@0: } michael@0: michael@0: float inflation = michael@0: GetInflationForTextDecorations(dec.mFrame, inflationMinFontSize); michael@0: const gfxFont::Metrics metrics = michael@0: GetFirstFontMetrics(GetFontGroupForFrame(dec.mFrame, inflation)); michael@0: michael@0: decSize.height = metrics.underlineSize; michael@0: decPt.y = (frameTop - dec.mBaselineOffset) / app; michael@0: michael@0: PaintDecorationLine(this, aCtx, dirtyRect, dec.mColor, michael@0: aDecorationOverrideColor, decPt, 0.0, decSize, ascent, michael@0: metrics.maxAscent, NS_STYLE_TEXT_DECORATION_LINE_OVERLINE, dec.mStyle, michael@0: eNormalDecoration, aCallbacks); michael@0: } michael@0: michael@0: // CSS 2.1 mandates that text be painted after over/underlines, and *then* michael@0: // line-throughs michael@0: DrawTextRun(aCtx, aTextBaselinePt, aOffset, aLength, aProvider, aTextColor, michael@0: aAdvanceWidth, aDrawSoftHyphen, aContextPaint, aCallbacks); michael@0: michael@0: // Line-throughs michael@0: for (uint32_t i = aDecorations.mStrikes.Length(); i-- > 0; ) { michael@0: const LineDecoration& dec = aDecorations.mStrikes[i]; michael@0: if (dec.mStyle == NS_STYLE_TEXT_DECORATION_STYLE_NONE) { michael@0: continue; michael@0: } michael@0: michael@0: float inflation = michael@0: GetInflationForTextDecorations(dec.mFrame, inflationMinFontSize); michael@0: const gfxFont::Metrics metrics = michael@0: GetFirstFontMetrics(GetFontGroupForFrame(dec.mFrame, inflation)); michael@0: michael@0: decSize.height = metrics.strikeoutSize; michael@0: decPt.y = (frameTop - dec.mBaselineOffset) / app; michael@0: michael@0: PaintDecorationLine(this, aCtx, dirtyRect, dec.mColor, michael@0: aDecorationOverrideColor, decPt, 0.0, decSize, ascent, michael@0: metrics.strikeoutOffset, NS_STYLE_TEXT_DECORATION_LINE_LINE_THROUGH, michael@0: dec.mStyle, eNormalDecoration, aCallbacks); michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsTextFrame::DrawText( michael@0: gfxContext* const aCtx, const gfxRect& aDirtyRect, michael@0: const gfxPoint& aFramePt, const gfxPoint& aTextBaselinePt, michael@0: uint32_t aOffset, uint32_t aLength, michael@0: PropertyProvider& aProvider, michael@0: const nsTextPaintStyle& aTextStyle, michael@0: nscolor aTextColor, michael@0: const nsCharClipDisplayItem::ClipEdges& aClipEdges, michael@0: gfxFloat& aAdvanceWidth, michael@0: bool aDrawSoftHyphen, michael@0: const nscolor* const aDecorationOverrideColor, michael@0: gfxTextContextPaint* aContextPaint, michael@0: nsTextFrame::DrawPathCallbacks* aCallbacks) michael@0: { michael@0: TextDecorations decorations; michael@0: GetTextDecorations(aTextStyle.PresContext(), michael@0: aCallbacks ? eUnresolvedColors : eResolvedColors, michael@0: decorations); michael@0: michael@0: // Hide text decorations if we're currently hiding @font-face fallback text michael@0: const bool drawDecorations = !aProvider.GetFontGroup()->ShouldSkipDrawing() && michael@0: decorations.HasDecorationLines(); michael@0: if (drawDecorations) { michael@0: DrawTextRunAndDecorations(aCtx, aDirtyRect, aFramePt, aTextBaselinePt, aOffset, aLength, michael@0: aProvider, aTextStyle, aTextColor, aClipEdges, aAdvanceWidth, michael@0: aDrawSoftHyphen, decorations, michael@0: aDecorationOverrideColor, aContextPaint, aCallbacks); michael@0: } else { michael@0: DrawTextRun(aCtx, aTextBaselinePt, aOffset, aLength, aProvider, michael@0: aTextColor, aAdvanceWidth, aDrawSoftHyphen, aContextPaint, aCallbacks); michael@0: } michael@0: } michael@0: michael@0: int16_t michael@0: nsTextFrame::GetSelectionStatus(int16_t* aSelectionFlags) michael@0: { michael@0: // get the selection controller michael@0: nsCOMPtr selectionController; michael@0: nsresult rv = GetSelectionController(PresContext(), michael@0: getter_AddRefs(selectionController)); michael@0: if (NS_FAILED(rv) || !selectionController) michael@0: return nsISelectionController::SELECTION_OFF; michael@0: michael@0: selectionController->GetSelectionFlags(aSelectionFlags); michael@0: michael@0: int16_t selectionValue; michael@0: selectionController->GetDisplaySelection(&selectionValue); michael@0: michael@0: return selectionValue; michael@0: } michael@0: michael@0: bool michael@0: nsTextFrame::IsVisibleInSelection(nsISelection* aSelection) michael@0: { michael@0: // Check the quick way first michael@0: if (!GetContent()->IsSelectionDescendant()) michael@0: return false; michael@0: michael@0: SelectionDetails* details = GetSelectionDetails(); michael@0: bool found = false; michael@0: michael@0: // where are the selection points "really" michael@0: SelectionDetails *sdptr = details; michael@0: while (sdptr) { michael@0: if (sdptr->mEnd > GetContentOffset() && michael@0: sdptr->mStart < GetContentEnd() && michael@0: sdptr->mType == nsISelectionController::SELECTION_NORMAL) { michael@0: found = true; michael@0: break; michael@0: } michael@0: sdptr = sdptr->mNext; michael@0: } michael@0: DestroySelectionDetails(details); michael@0: michael@0: return found; michael@0: } michael@0: michael@0: /** michael@0: * Compute the longest prefix of text whose width is <= aWidth. Return michael@0: * the length of the prefix. Also returns the width of the prefix in aFitWidth. michael@0: */ michael@0: static uint32_t michael@0: CountCharsFit(gfxTextRun* aTextRun, uint32_t aStart, uint32_t aLength, michael@0: gfxFloat aWidth, PropertyProvider* aProvider, michael@0: gfxFloat* aFitWidth) michael@0: { michael@0: uint32_t last = 0; michael@0: gfxFloat width = 0; michael@0: for (uint32_t i = 1; i <= aLength; ++i) { michael@0: if (i == aLength || aTextRun->IsClusterStart(aStart + i)) { michael@0: gfxFloat nextWidth = width + michael@0: aTextRun->GetAdvanceWidth(aStart + last, i - last, aProvider); michael@0: if (nextWidth > aWidth) michael@0: break; michael@0: last = i; michael@0: width = nextWidth; michael@0: } michael@0: } michael@0: *aFitWidth = width; michael@0: return last; michael@0: } michael@0: michael@0: nsIFrame::ContentOffsets michael@0: nsTextFrame::CalcContentOffsetsFromFramePoint(nsPoint aPoint) michael@0: { michael@0: return GetCharacterOffsetAtFramePointInternal(aPoint, true); michael@0: } michael@0: michael@0: nsIFrame::ContentOffsets michael@0: nsTextFrame::GetCharacterOffsetAtFramePoint(const nsPoint &aPoint) michael@0: { michael@0: return GetCharacterOffsetAtFramePointInternal(aPoint, false); michael@0: } michael@0: michael@0: nsIFrame::ContentOffsets michael@0: nsTextFrame::GetCharacterOffsetAtFramePointInternal(nsPoint aPoint, michael@0: bool aForInsertionPoint) michael@0: { michael@0: ContentOffsets offsets; michael@0: michael@0: gfxSkipCharsIterator iter = EnsureTextRun(nsTextFrame::eInflated); michael@0: if (!mTextRun) michael@0: return offsets; michael@0: michael@0: PropertyProvider provider(this, iter, nsTextFrame::eInflated); michael@0: // Trim leading but not trailing whitespace if possible michael@0: provider.InitializeForDisplay(false); michael@0: gfxFloat width = mTextRun->IsRightToLeft() ? mRect.width - aPoint.x : aPoint.x; michael@0: gfxFloat fitWidth; michael@0: uint32_t skippedLength = ComputeTransformedLength(provider); michael@0: michael@0: uint32_t charsFit = CountCharsFit(mTextRun, michael@0: provider.GetStart().GetSkippedOffset(), skippedLength, width, &provider, &fitWidth); michael@0: michael@0: int32_t selectedOffset; michael@0: if (charsFit < skippedLength) { michael@0: // charsFit characters fitted, but no more could fit. See if we're michael@0: // more than halfway through the cluster.. If we are, choose the next michael@0: // cluster. michael@0: gfxSkipCharsIterator extraCluster(provider.GetStart()); michael@0: extraCluster.AdvanceSkipped(charsFit); michael@0: gfxSkipCharsIterator extraClusterLastChar(extraCluster); michael@0: FindClusterEnd(mTextRun, michael@0: provider.GetStart().GetOriginalOffset() + provider.GetOriginalLength(), michael@0: &extraClusterLastChar); michael@0: gfxFloat charWidth = michael@0: mTextRun->GetAdvanceWidth(extraCluster.GetSkippedOffset(), michael@0: GetSkippedDistance(extraCluster, extraClusterLastChar) + 1, michael@0: &provider); michael@0: selectedOffset = !aForInsertionPoint || width <= fitWidth + charWidth/2 michael@0: ? extraCluster.GetOriginalOffset() michael@0: : extraClusterLastChar.GetOriginalOffset() + 1; michael@0: } else { michael@0: // All characters fitted, we're at (or beyond) the end of the text. michael@0: // XXX This could be some pathological situation where negative spacing michael@0: // caused characters to move backwards. We can't really handle that michael@0: // in the current frame system because frames can't have negative michael@0: // intrinsic widths. michael@0: selectedOffset = michael@0: provider.GetStart().GetOriginalOffset() + provider.GetOriginalLength(); michael@0: // If we're at the end of a preformatted line which has a terminating michael@0: // linefeed, we want to reduce the offset by one to make sure that the michael@0: // selection is placed before the linefeed character. michael@0: if (HasSignificantTerminalNewline()) { michael@0: --selectedOffset; michael@0: } michael@0: } michael@0: michael@0: offsets.content = GetContent(); michael@0: offsets.offset = offsets.secondaryOffset = selectedOffset; michael@0: offsets.associateWithNext = mContentOffset == offsets.offset; michael@0: return offsets; michael@0: } michael@0: michael@0: bool michael@0: nsTextFrame::CombineSelectionUnderlineRect(nsPresContext* aPresContext, michael@0: nsRect& aRect) michael@0: { michael@0: if (aRect.IsEmpty()) michael@0: return false; michael@0: michael@0: nsRect givenRect = aRect; michael@0: michael@0: nsRefPtr fm; michael@0: nsLayoutUtils::GetFontMetricsForFrame(this, getter_AddRefs(fm), michael@0: GetFontSizeInflation()); michael@0: gfxFontGroup* fontGroup = fm->GetThebesFontGroup(); michael@0: gfxFont* firstFont = fontGroup->GetFontAt(0); michael@0: if (!firstFont) michael@0: return false; // OOM michael@0: const gfxFont::Metrics& metrics = firstFont->GetMetrics(); michael@0: gfxFloat underlineOffset = fontGroup->GetUnderlineOffset(); michael@0: gfxFloat ascent = aPresContext->AppUnitsToGfxUnits(mAscent); michael@0: gfxFloat descentLimit = michael@0: ComputeDescentLimitForSelectionUnderline(aPresContext, this, metrics); michael@0: michael@0: SelectionDetails *details = GetSelectionDetails(); michael@0: for (SelectionDetails *sd = details; sd; sd = sd->mNext) { michael@0: if (sd->mStart == sd->mEnd || !(sd->mType & SelectionTypesWithDecorations)) michael@0: continue; michael@0: michael@0: uint8_t style; michael@0: float relativeSize; michael@0: int32_t index = michael@0: nsTextPaintStyle::GetUnderlineStyleIndexForSelectionType(sd->mType); michael@0: if (sd->mType == nsISelectionController::SELECTION_SPELLCHECK) { michael@0: if (!nsTextPaintStyle::GetSelectionUnderline(aPresContext, index, nullptr, michael@0: &relativeSize, &style)) { michael@0: continue; michael@0: } michael@0: } else { michael@0: // IME selections michael@0: TextRangeStyle& rangeStyle = sd->mTextRangeStyle; michael@0: if (rangeStyle.IsDefined()) { michael@0: if (!rangeStyle.IsLineStyleDefined() || michael@0: rangeStyle.mLineStyle == TextRangeStyle::LINESTYLE_NONE) { michael@0: continue; michael@0: } michael@0: style = rangeStyle.mLineStyle; michael@0: relativeSize = rangeStyle.mIsBoldLine ? 2.0f : 1.0f; michael@0: } else if (!nsTextPaintStyle::GetSelectionUnderline(aPresContext, index, michael@0: nullptr, &relativeSize, michael@0: &style)) { michael@0: continue; michael@0: } michael@0: } michael@0: nsRect decorationArea; michael@0: gfxSize size(aPresContext->AppUnitsToGfxUnits(aRect.width), michael@0: ComputeSelectionUnderlineHeight(aPresContext, michael@0: metrics, sd->mType)); michael@0: relativeSize = std::max(relativeSize, 1.0f); michael@0: size.height *= relativeSize; michael@0: decorationArea = michael@0: nsCSSRendering::GetTextDecorationRect(aPresContext, size, michael@0: ascent, underlineOffset, NS_STYLE_TEXT_DECORATION_LINE_UNDERLINE, michael@0: style, descentLimit); michael@0: aRect.UnionRect(aRect, decorationArea); michael@0: } michael@0: DestroySelectionDetails(details); michael@0: michael@0: return !aRect.IsEmpty() && !givenRect.Contains(aRect); michael@0: } michael@0: michael@0: bool michael@0: nsTextFrame::IsFrameSelected() const michael@0: { michael@0: NS_ASSERTION(!GetContent() || GetContent()->IsSelectionDescendant(), michael@0: "use the public IsSelected() instead"); michael@0: return nsRange::IsNodeSelected(GetContent(), GetContentOffset(), michael@0: GetContentEnd()); michael@0: } michael@0: michael@0: void michael@0: nsTextFrame::SetSelectedRange(uint32_t aStart, uint32_t aEnd, bool aSelected, michael@0: SelectionType aType) michael@0: { michael@0: NS_ASSERTION(!GetPrevContinuation(), "Should only be called for primary frame"); michael@0: DEBUG_VERIFY_NOT_DIRTY(mState); michael@0: michael@0: // Selection is collapsed, which can't affect text frame rendering michael@0: if (aStart == aEnd) michael@0: return; michael@0: michael@0: nsTextFrame* f = this; michael@0: while (f && f->GetContentEnd() <= int32_t(aStart)) { michael@0: f = static_cast(f->GetNextContinuation()); michael@0: } michael@0: michael@0: nsPresContext* presContext = PresContext(); michael@0: while (f && f->GetContentOffset() < int32_t(aEnd)) { michael@0: // We may need to reflow to recompute the overflow area for michael@0: // spellchecking or IME underline if their underline is thicker than michael@0: // the normal decoration line. michael@0: if (aType & SelectionTypesWithDecorations) { michael@0: bool didHaveOverflowingSelection = michael@0: (f->GetStateBits() & TEXT_SELECTION_UNDERLINE_OVERFLOWED) != 0; michael@0: nsRect r(nsPoint(0, 0), GetSize()); michael@0: bool willHaveOverflowingSelection = michael@0: aSelected && f->CombineSelectionUnderlineRect(presContext, r); michael@0: if (didHaveOverflowingSelection || willHaveOverflowingSelection) { michael@0: presContext->PresShell()->FrameNeedsReflow(f, michael@0: nsIPresShell::eStyleChange, michael@0: NS_FRAME_IS_DIRTY); michael@0: } michael@0: } michael@0: // Selection might change anything. Invalidate the overflow area. michael@0: f->InvalidateFrame(); michael@0: michael@0: f = static_cast(f->GetNextContinuation()); michael@0: } michael@0: } michael@0: michael@0: nsresult michael@0: nsTextFrame::GetPointFromOffset(int32_t inOffset, michael@0: nsPoint* outPoint) michael@0: { michael@0: if (!outPoint) michael@0: return NS_ERROR_NULL_POINTER; michael@0: michael@0: outPoint->x = 0; michael@0: outPoint->y = 0; michael@0: michael@0: DEBUG_VERIFY_NOT_DIRTY(mState); michael@0: if (mState & NS_FRAME_IS_DIRTY) michael@0: return NS_ERROR_UNEXPECTED; michael@0: michael@0: if (GetContentLength() <= 0) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: gfxSkipCharsIterator iter = EnsureTextRun(nsTextFrame::eInflated); michael@0: if (!mTextRun) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: PropertyProvider properties(this, iter, nsTextFrame::eInflated); michael@0: // Don't trim trailing whitespace, we want the caret to appear in the right michael@0: // place if it's positioned there michael@0: properties.InitializeForDisplay(false); michael@0: michael@0: if (inOffset < GetContentOffset()){ michael@0: NS_WARNING("offset before this frame's content"); michael@0: inOffset = GetContentOffset(); michael@0: } else if (inOffset > GetContentEnd()) { michael@0: NS_WARNING("offset after this frame's content"); michael@0: inOffset = GetContentEnd(); michael@0: } michael@0: int32_t trimmedOffset = properties.GetStart().GetOriginalOffset(); michael@0: int32_t trimmedEnd = trimmedOffset + properties.GetOriginalLength(); michael@0: inOffset = std::max(inOffset, trimmedOffset); michael@0: inOffset = std::min(inOffset, trimmedEnd); michael@0: michael@0: iter.SetOriginalOffset(inOffset); michael@0: michael@0: if (inOffset < trimmedEnd && michael@0: !iter.IsOriginalCharSkipped() && michael@0: !mTextRun->IsClusterStart(iter.GetSkippedOffset())) { michael@0: NS_WARNING("GetPointFromOffset called for non-cluster boundary"); michael@0: FindClusterStart(mTextRun, trimmedOffset, &iter); michael@0: } michael@0: michael@0: gfxFloat advanceWidth = michael@0: mTextRun->GetAdvanceWidth(properties.GetStart().GetSkippedOffset(), michael@0: GetSkippedDistance(properties.GetStart(), iter), michael@0: &properties); michael@0: nscoord width = NSToCoordCeilClamped(advanceWidth); michael@0: michael@0: if (mTextRun->IsRightToLeft()) { michael@0: outPoint->x = mRect.width - width; michael@0: } else { michael@0: outPoint->x = width; michael@0: } michael@0: outPoint->y = 0; michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsTextFrame::GetChildFrameContainingOffset(int32_t aContentOffset, michael@0: bool aHint, michael@0: int32_t* aOutOffset, michael@0: nsIFrame**aOutFrame) michael@0: { michael@0: DEBUG_VERIFY_NOT_DIRTY(mState); michael@0: #if 0 //XXXrbs disable due to bug 310227 michael@0: if (mState & NS_FRAME_IS_DIRTY) michael@0: return NS_ERROR_UNEXPECTED; michael@0: #endif michael@0: michael@0: NS_ASSERTION(aOutOffset && aOutFrame, "Bad out parameters"); michael@0: NS_ASSERTION(aContentOffset >= 0, "Negative content offset, existing code was very broken!"); michael@0: nsIFrame* primaryFrame = mContent->GetPrimaryFrame(); michael@0: if (this != primaryFrame) { michael@0: // This call needs to happen on the primary frame michael@0: return primaryFrame->GetChildFrameContainingOffset(aContentOffset, aHint, michael@0: aOutOffset, aOutFrame); michael@0: } michael@0: michael@0: nsTextFrame* f = this; michael@0: int32_t offset = mContentOffset; michael@0: michael@0: // Try to look up the offset to frame property michael@0: nsTextFrame* cachedFrame = static_cast michael@0: (Properties().Get(OffsetToFrameProperty())); michael@0: michael@0: if (cachedFrame) { michael@0: f = cachedFrame; michael@0: offset = f->GetContentOffset(); michael@0: michael@0: f->RemoveStateBits(TEXT_IN_OFFSET_CACHE); michael@0: } michael@0: michael@0: if ((aContentOffset >= offset) && michael@0: (aHint || aContentOffset != offset)) { michael@0: while (true) { michael@0: nsTextFrame* next = static_cast(f->GetNextContinuation()); michael@0: if (!next || aContentOffset < next->GetContentOffset()) michael@0: break; michael@0: if (aContentOffset == next->GetContentOffset()) { michael@0: if (aHint) { michael@0: f = next; michael@0: if (f->GetContentLength() == 0) { michael@0: continue; // use the last of the empty frames with this offset michael@0: } michael@0: } michael@0: break; michael@0: } michael@0: f = next; michael@0: } michael@0: } else { michael@0: while (true) { michael@0: nsTextFrame* prev = static_cast(f->GetPrevContinuation()); michael@0: if (!prev || aContentOffset > f->GetContentOffset()) michael@0: break; michael@0: if (aContentOffset == f->GetContentOffset()) { michael@0: if (!aHint) { michael@0: f = prev; michael@0: if (f->GetContentLength() == 0) { michael@0: continue; // use the first of the empty frames with this offset michael@0: } michael@0: } michael@0: break; michael@0: } michael@0: f = prev; michael@0: } michael@0: } michael@0: michael@0: *aOutOffset = aContentOffset - f->GetContentOffset(); michael@0: *aOutFrame = f; michael@0: michael@0: // cache the frame we found michael@0: Properties().Set(OffsetToFrameProperty(), f); michael@0: f->AddStateBits(TEXT_IN_OFFSET_CACHE); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsIFrame::FrameSearchResult michael@0: nsTextFrame::PeekOffsetNoAmount(bool aForward, int32_t* aOffset) michael@0: { michael@0: NS_ASSERTION(aOffset && *aOffset <= GetContentLength(), "aOffset out of range"); michael@0: michael@0: gfxSkipCharsIterator iter = EnsureTextRun(nsTextFrame::eInflated); michael@0: if (!mTextRun) michael@0: return CONTINUE_EMPTY; michael@0: michael@0: TrimmedOffsets trimmed = GetTrimmedOffsets(mContent->GetText(), true); michael@0: // Check whether there are nonskipped characters in the trimmmed range michael@0: return (iter.ConvertOriginalToSkipped(trimmed.GetEnd()) > michael@0: iter.ConvertOriginalToSkipped(trimmed.mStart)) ? FOUND : CONTINUE; michael@0: } michael@0: michael@0: /** michael@0: * This class iterates through the clusters before or after the given michael@0: * aPosition (which is a content offset). You can test each cluster michael@0: * to see if it's whitespace (as far as selection/caret movement is concerned), michael@0: * or punctuation, or if there is a word break before the cluster. ("Before" michael@0: * is interpreted according to aDirection, so if aDirection is -1, "before" michael@0: * means actually *after* the cluster content.) michael@0: */ michael@0: class MOZ_STACK_CLASS ClusterIterator { michael@0: public: michael@0: ClusterIterator(nsTextFrame* aTextFrame, int32_t aPosition, int32_t aDirection, michael@0: nsString& aContext); michael@0: michael@0: bool NextCluster(); michael@0: bool IsWhitespace(); michael@0: bool IsPunctuation(); michael@0: bool HaveWordBreakBefore() { return mHaveWordBreak; } michael@0: int32_t GetAfterOffset(); michael@0: int32_t GetBeforeOffset(); michael@0: michael@0: private: michael@0: gfxSkipCharsIterator mIterator; michael@0: const nsTextFragment* mFrag; michael@0: nsTextFrame* mTextFrame; michael@0: int32_t mDirection; michael@0: int32_t mCharIndex; michael@0: nsTextFrame::TrimmedOffsets mTrimmed; michael@0: nsTArray mWordBreaks; michael@0: bool mHaveWordBreak; michael@0: }; michael@0: michael@0: static bool michael@0: IsAcceptableCaretPosition(const gfxSkipCharsIterator& aIter, michael@0: bool aRespectClusters, michael@0: gfxTextRun* aTextRun, michael@0: nsIFrame* aFrame) michael@0: { michael@0: if (aIter.IsOriginalCharSkipped()) michael@0: return false; michael@0: uint32_t index = aIter.GetSkippedOffset(); michael@0: if (aRespectClusters && !aTextRun->IsClusterStart(index)) michael@0: return false; michael@0: if (index > 0) { michael@0: // Check whether the proposed position is in between the two halves of a michael@0: // surrogate pair; if so, this is not a valid character boundary. michael@0: // (In the case where we are respecting clusters, we won't actually get michael@0: // this far because the low surrogate is also marked as non-clusterStart michael@0: // so we'll return FALSE above.) michael@0: if (aTextRun->CharIsLowSurrogate(index)) { michael@0: return false; michael@0: } michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: nsIFrame::FrameSearchResult michael@0: nsTextFrame::PeekOffsetCharacter(bool aForward, int32_t* aOffset, michael@0: bool aRespectClusters) michael@0: { michael@0: int32_t contentLength = GetContentLength(); michael@0: NS_ASSERTION(aOffset && *aOffset <= contentLength, "aOffset out of range"); michael@0: michael@0: bool selectable; michael@0: uint8_t selectStyle; michael@0: IsSelectable(&selectable, &selectStyle); michael@0: if (selectStyle == NS_STYLE_USER_SELECT_ALL) michael@0: return CONTINUE_UNSELECTABLE; michael@0: michael@0: gfxSkipCharsIterator iter = EnsureTextRun(nsTextFrame::eInflated); michael@0: if (!mTextRun) michael@0: return CONTINUE_EMPTY; michael@0: michael@0: TrimmedOffsets trimmed = GetTrimmedOffsets(mContent->GetText(), false); michael@0: michael@0: // A negative offset means "end of frame". michael@0: int32_t startOffset = GetContentOffset() + (*aOffset < 0 ? contentLength : *aOffset); michael@0: michael@0: if (!aForward) { michael@0: // If at the beginning of the line, look at the previous continuation michael@0: for (int32_t i = std::min(trimmed.GetEnd(), startOffset) - 1; michael@0: i >= trimmed.mStart; --i) { michael@0: iter.SetOriginalOffset(i); michael@0: if (IsAcceptableCaretPosition(iter, aRespectClusters, mTextRun, this)) { michael@0: *aOffset = i - mContentOffset; michael@0: return FOUND; michael@0: } michael@0: } michael@0: *aOffset = 0; michael@0: } else { michael@0: // If we're at the end of a line, look at the next continuation michael@0: iter.SetOriginalOffset(startOffset); michael@0: if (startOffset <= trimmed.GetEnd() && michael@0: !(startOffset < trimmed.GetEnd() && michael@0: StyleText()->NewlineIsSignificant() && michael@0: iter.GetSkippedOffset() < mTextRun->GetLength() && michael@0: mTextRun->CharIsNewline(iter.GetSkippedOffset()))) { michael@0: for (int32_t i = startOffset + 1; i <= trimmed.GetEnd(); ++i) { michael@0: iter.SetOriginalOffset(i); michael@0: if (i == trimmed.GetEnd() || michael@0: IsAcceptableCaretPosition(iter, aRespectClusters, mTextRun, this)) { michael@0: *aOffset = i - mContentOffset; michael@0: return FOUND; michael@0: } michael@0: } michael@0: } michael@0: *aOffset = contentLength; michael@0: } michael@0: michael@0: return CONTINUE; michael@0: } michael@0: michael@0: bool michael@0: ClusterIterator::IsWhitespace() michael@0: { michael@0: NS_ASSERTION(mCharIndex >= 0, "No cluster selected"); michael@0: return IsSelectionSpace(mFrag, mCharIndex); michael@0: } michael@0: michael@0: bool michael@0: ClusterIterator::IsPunctuation() michael@0: { michael@0: NS_ASSERTION(mCharIndex >= 0, "No cluster selected"); michael@0: nsIUGenCategory::nsUGenCategory c = michael@0: mozilla::unicode::GetGenCategory(mFrag->CharAt(mCharIndex)); michael@0: return c == nsIUGenCategory::kPunctuation || c == nsIUGenCategory::kSymbol; michael@0: } michael@0: michael@0: int32_t michael@0: ClusterIterator::GetBeforeOffset() michael@0: { michael@0: NS_ASSERTION(mCharIndex >= 0, "No cluster selected"); michael@0: return mCharIndex + (mDirection > 0 ? 0 : 1); michael@0: } michael@0: michael@0: int32_t michael@0: ClusterIterator::GetAfterOffset() michael@0: { michael@0: NS_ASSERTION(mCharIndex >= 0, "No cluster selected"); michael@0: return mCharIndex + (mDirection > 0 ? 1 : 0); michael@0: } michael@0: michael@0: bool michael@0: ClusterIterator::NextCluster() michael@0: { michael@0: if (!mDirection) michael@0: return false; michael@0: gfxTextRun* textRun = mTextFrame->GetTextRun(nsTextFrame::eInflated); michael@0: michael@0: mHaveWordBreak = false; michael@0: while (true) { michael@0: bool keepGoing = false; michael@0: if (mDirection > 0) { michael@0: if (mIterator.GetOriginalOffset() >= mTrimmed.GetEnd()) michael@0: return false; michael@0: keepGoing = mIterator.IsOriginalCharSkipped() || michael@0: mIterator.GetOriginalOffset() < mTrimmed.mStart || michael@0: !textRun->IsClusterStart(mIterator.GetSkippedOffset()); michael@0: mCharIndex = mIterator.GetOriginalOffset(); michael@0: mIterator.AdvanceOriginal(1); michael@0: } else { michael@0: if (mIterator.GetOriginalOffset() <= mTrimmed.mStart) michael@0: return false; michael@0: mIterator.AdvanceOriginal(-1); michael@0: keepGoing = mIterator.IsOriginalCharSkipped() || michael@0: mIterator.GetOriginalOffset() >= mTrimmed.GetEnd() || michael@0: !textRun->IsClusterStart(mIterator.GetSkippedOffset()); michael@0: mCharIndex = mIterator.GetOriginalOffset(); michael@0: } michael@0: michael@0: if (mWordBreaks[GetBeforeOffset() - mTextFrame->GetContentOffset()]) { michael@0: mHaveWordBreak = true; michael@0: } michael@0: if (!keepGoing) michael@0: return true; michael@0: } michael@0: } michael@0: michael@0: ClusterIterator::ClusterIterator(nsTextFrame* aTextFrame, int32_t aPosition, michael@0: int32_t aDirection, nsString& aContext) michael@0: : mTextFrame(aTextFrame), mDirection(aDirection), mCharIndex(-1) michael@0: { michael@0: mIterator = aTextFrame->EnsureTextRun(nsTextFrame::eInflated); michael@0: if (!aTextFrame->GetTextRun(nsTextFrame::eInflated)) { michael@0: mDirection = 0; // signal failure michael@0: return; michael@0: } michael@0: mIterator.SetOriginalOffset(aPosition); michael@0: michael@0: mFrag = aTextFrame->GetContent()->GetText(); michael@0: mTrimmed = aTextFrame->GetTrimmedOffsets(mFrag, true); michael@0: michael@0: int32_t textOffset = aTextFrame->GetContentOffset(); michael@0: int32_t textLen = aTextFrame->GetContentLength(); michael@0: if (!mWordBreaks.AppendElements(textLen + 1)) { michael@0: mDirection = 0; // signal failure michael@0: return; michael@0: } michael@0: memset(mWordBreaks.Elements(), false, (textLen + 1)*sizeof(bool)); michael@0: int32_t textStart; michael@0: if (aDirection > 0) { michael@0: if (aContext.IsEmpty()) { michael@0: // No previous context, so it must be the start of a line or text run michael@0: mWordBreaks[0] = true; michael@0: } michael@0: textStart = aContext.Length(); michael@0: mFrag->AppendTo(aContext, textOffset, textLen); michael@0: } else { michael@0: if (aContext.IsEmpty()) { michael@0: // No following context, so it must be the end of a line or text run michael@0: mWordBreaks[textLen] = true; michael@0: } michael@0: textStart = 0; michael@0: nsAutoString str; michael@0: mFrag->AppendTo(str, textOffset, textLen); michael@0: aContext.Insert(str, 0); michael@0: } michael@0: nsIWordBreaker* wordBreaker = nsContentUtils::WordBreaker(); michael@0: for (int32_t i = 0; i <= textLen; ++i) { michael@0: int32_t indexInText = i + textStart; michael@0: mWordBreaks[i] |= michael@0: wordBreaker->BreakInBetween(aContext.get(), indexInText, michael@0: aContext.get() + indexInText, michael@0: aContext.Length() - indexInText); michael@0: } michael@0: } michael@0: michael@0: nsIFrame::FrameSearchResult michael@0: nsTextFrame::PeekOffsetWord(bool aForward, bool aWordSelectEatSpace, bool aIsKeyboardSelect, michael@0: int32_t* aOffset, PeekWordState* aState) michael@0: { michael@0: int32_t contentLength = GetContentLength(); michael@0: NS_ASSERTION (aOffset && *aOffset <= contentLength, "aOffset out of range"); michael@0: michael@0: bool selectable; michael@0: uint8_t selectStyle; michael@0: IsSelectable(&selectable, &selectStyle); michael@0: if (selectStyle == NS_STYLE_USER_SELECT_ALL) michael@0: return CONTINUE_UNSELECTABLE; michael@0: michael@0: int32_t offset = GetContentOffset() + (*aOffset < 0 ? contentLength : *aOffset); michael@0: ClusterIterator cIter(this, offset, aForward ? 1 : -1, aState->mContext); michael@0: michael@0: if (!cIter.NextCluster()) michael@0: return CONTINUE_EMPTY; michael@0: michael@0: do { michael@0: bool isPunctuation = cIter.IsPunctuation(); michael@0: bool isWhitespace = cIter.IsWhitespace(); michael@0: bool isWordBreakBefore = cIter.HaveWordBreakBefore(); michael@0: if (aWordSelectEatSpace == isWhitespace && !aState->mSawBeforeType) { michael@0: aState->SetSawBeforeType(); michael@0: aState->Update(isPunctuation, isWhitespace); michael@0: continue; michael@0: } michael@0: // See if we can break before the current cluster michael@0: if (!aState->mAtStart) { michael@0: bool canBreak; michael@0: if (isPunctuation != aState->mLastCharWasPunctuation) { michael@0: canBreak = BreakWordBetweenPunctuation(aState, aForward, michael@0: isPunctuation, isWhitespace, aIsKeyboardSelect); michael@0: } else if (!aState->mLastCharWasWhitespace && michael@0: !isWhitespace && !isPunctuation && isWordBreakBefore) { michael@0: // if both the previous and the current character are not white michael@0: // space but this can be word break before, we don't need to eat michael@0: // a white space in this case. This case happens in some languages michael@0: // that their words are not separated by white spaces. E.g., michael@0: // Japanese and Chinese. michael@0: canBreak = true; michael@0: } else { michael@0: canBreak = isWordBreakBefore && aState->mSawBeforeType && michael@0: (aWordSelectEatSpace != isWhitespace); michael@0: } michael@0: if (canBreak) { michael@0: *aOffset = cIter.GetBeforeOffset() - mContentOffset; michael@0: return FOUND; michael@0: } michael@0: } michael@0: aState->Update(isPunctuation, isWhitespace); michael@0: } while (cIter.NextCluster()); michael@0: michael@0: *aOffset = cIter.GetAfterOffset() - mContentOffset; michael@0: return CONTINUE; michael@0: } michael@0: michael@0: // TODO this needs to be deCOMtaminated with the interface fixed in michael@0: // nsIFrame.h, but we won't do that until the old textframe is gone. michael@0: nsresult michael@0: nsTextFrame::CheckVisibility(nsPresContext* aContext, int32_t aStartIndex, michael@0: int32_t aEndIndex, bool aRecurse, bool *aFinished, bool *aRetval) michael@0: { michael@0: if (!aRetval) michael@0: return NS_ERROR_NULL_POINTER; michael@0: michael@0: // Text in the range is visible if there is at least one character in the range michael@0: // that is not skipped and is mapped by this frame (which is the primary frame) michael@0: // or one of its continuations. michael@0: for (nsTextFrame* f = this; f; michael@0: f = static_cast(GetNextContinuation())) { michael@0: int32_t dummyOffset = 0; michael@0: if (f->PeekOffsetNoAmount(true, &dummyOffset) == FOUND) { michael@0: *aRetval = true; michael@0: return NS_OK; michael@0: } michael@0: } michael@0: michael@0: *aRetval = false; michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsTextFrame::GetOffsets(int32_t &start, int32_t &end) const michael@0: { michael@0: start = GetContentOffset(); michael@0: end = GetContentEnd(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: static int32_t michael@0: FindEndOfPunctuationRun(const nsTextFragment* aFrag, michael@0: gfxTextRun* aTextRun, michael@0: gfxSkipCharsIterator* aIter, michael@0: int32_t aOffset, michael@0: int32_t aStart, michael@0: int32_t aEnd) michael@0: { michael@0: int32_t i; michael@0: michael@0: for (i = aStart; i < aEnd - aOffset; ++i) { michael@0: if (nsContentUtils::IsFirstLetterPunctuationAt(aFrag, aOffset + i)) { michael@0: aIter->SetOriginalOffset(aOffset + i); michael@0: FindClusterEnd(aTextRun, aEnd, aIter); michael@0: i = aIter->GetOriginalOffset() - aOffset; michael@0: } else { michael@0: break; michael@0: } michael@0: } michael@0: return i; michael@0: } michael@0: michael@0: /** michael@0: * Returns true if this text frame completes the first-letter, false michael@0: * if it does not contain a true "letter". michael@0: * If returns true, then it also updates aLength to cover just the first-letter michael@0: * text. michael@0: * michael@0: * XXX :first-letter should be handled during frame construction michael@0: * (and it has a good bit in common with nextBidi) michael@0: * michael@0: * @param aLength an in/out parameter: on entry contains the maximum length to michael@0: * return, on exit returns length of the first-letter fragment (which may michael@0: * include leading and trailing punctuation, for example) michael@0: */ michael@0: static bool michael@0: FindFirstLetterRange(const nsTextFragment* aFrag, michael@0: gfxTextRun* aTextRun, michael@0: int32_t aOffset, const gfxSkipCharsIterator& aIter, michael@0: int32_t* aLength) michael@0: { michael@0: int32_t i; michael@0: int32_t length = *aLength; michael@0: int32_t endOffset = aOffset + length; michael@0: gfxSkipCharsIterator iter(aIter); michael@0: michael@0: // skip leading whitespace, then consume clusters that start with punctuation michael@0: i = FindEndOfPunctuationRun(aFrag, aTextRun, &iter, aOffset, michael@0: GetTrimmableWhitespaceCount(aFrag, aOffset, length, 1), michael@0: endOffset); michael@0: if (i == length) michael@0: return false; michael@0: michael@0: // If the next character is not a letter or number, there is no first-letter. michael@0: // Return true so that we don't go on looking, but set aLength to 0. michael@0: if (!nsContentUtils::IsAlphanumericAt(aFrag, aOffset + i)) { michael@0: *aLength = 0; michael@0: return true; michael@0: } michael@0: michael@0: // consume another cluster (the actual first letter) michael@0: iter.SetOriginalOffset(aOffset + i); michael@0: FindClusterEnd(aTextRun, endOffset, &iter); michael@0: i = iter.GetOriginalOffset() - aOffset; michael@0: if (i + 1 == length) michael@0: return true; michael@0: michael@0: // consume clusters that start with punctuation michael@0: i = FindEndOfPunctuationRun(aFrag, aTextRun, &iter, aOffset, i + 1, endOffset); michael@0: if (i < length) michael@0: *aLength = i; michael@0: return true; michael@0: } michael@0: michael@0: static uint32_t michael@0: FindStartAfterSkippingWhitespace(PropertyProvider* aProvider, michael@0: nsIFrame::InlineIntrinsicWidthData* aData, michael@0: const nsStyleText* aTextStyle, michael@0: gfxSkipCharsIterator* aIterator, michael@0: uint32_t aFlowEndInTextRun) michael@0: { michael@0: if (aData->skipWhitespace) { michael@0: while (aIterator->GetSkippedOffset() < aFlowEndInTextRun && michael@0: IsTrimmableSpace(aProvider->GetFragment(), aIterator->GetOriginalOffset(), aTextStyle)) { michael@0: aIterator->AdvanceOriginal(1); michael@0: } michael@0: } michael@0: return aIterator->GetSkippedOffset(); michael@0: } michael@0: michael@0: union VoidPtrOrFloat { michael@0: VoidPtrOrFloat() : p(nullptr) {} michael@0: michael@0: void *p; michael@0: float f; michael@0: }; michael@0: michael@0: float michael@0: nsTextFrame::GetFontSizeInflation() const michael@0: { michael@0: if (!HasFontSizeInflation()) { michael@0: return 1.0f; michael@0: } michael@0: VoidPtrOrFloat u; michael@0: u.p = Properties().Get(FontSizeInflationProperty()); michael@0: return u.f; michael@0: } michael@0: michael@0: void michael@0: nsTextFrame::SetFontSizeInflation(float aInflation) michael@0: { michael@0: if (aInflation == 1.0f) { michael@0: if (HasFontSizeInflation()) { michael@0: RemoveStateBits(TEXT_HAS_FONT_INFLATION); michael@0: Properties().Delete(FontSizeInflationProperty()); michael@0: } michael@0: return; michael@0: } michael@0: michael@0: AddStateBits(TEXT_HAS_FONT_INFLATION); michael@0: VoidPtrOrFloat u; michael@0: u.f = aInflation; michael@0: Properties().Set(FontSizeInflationProperty(), u.p); michael@0: } michael@0: michael@0: /* virtual */ michael@0: void nsTextFrame::MarkIntrinsicWidthsDirty() michael@0: { michael@0: ClearTextRuns(); michael@0: nsFrame::MarkIntrinsicWidthsDirty(); michael@0: } michael@0: michael@0: // XXX this doesn't handle characters shaped by line endings. We need to michael@0: // temporarily override the "current line ending" settings. michael@0: void michael@0: nsTextFrame::AddInlineMinWidthForFlow(nsRenderingContext *aRenderingContext, michael@0: nsIFrame::InlineMinWidthData *aData, michael@0: TextRunType aTextRunType) michael@0: { michael@0: uint32_t flowEndInTextRun; michael@0: gfxContext* ctx = aRenderingContext->ThebesContext(); michael@0: gfxSkipCharsIterator iter = michael@0: EnsureTextRun(aTextRunType, ctx, aData->lineContainer, michael@0: aData->line, &flowEndInTextRun); michael@0: gfxTextRun *textRun = GetTextRun(aTextRunType); michael@0: if (!textRun) michael@0: return; michael@0: michael@0: // Pass null for the line container. This will disable tab spacing, but that's michael@0: // OK since we can't really handle tabs for intrinsic sizing anyway. michael@0: const nsStyleText* textStyle = StyleText(); michael@0: const nsTextFragment* frag = mContent->GetText(); michael@0: michael@0: // If we're hyphenating, the PropertyProvider needs the actual length; michael@0: // otherwise we can just pass INT32_MAX to mean "all the text" michael@0: int32_t len = INT32_MAX; michael@0: bool hyphenating = frag->GetLength() > 0 && michael@0: (textStyle->mHyphens == NS_STYLE_HYPHENS_AUTO || michael@0: (textStyle->mHyphens == NS_STYLE_HYPHENS_MANUAL && michael@0: (textRun->GetFlags() & gfxTextRunFactory::TEXT_ENABLE_HYPHEN_BREAKS) != 0)); michael@0: if (hyphenating) { michael@0: gfxSkipCharsIterator tmp(iter); michael@0: len = std::min(GetContentOffset() + GetInFlowContentLength(), michael@0: tmp.ConvertSkippedToOriginal(flowEndInTextRun)) - iter.GetOriginalOffset(); michael@0: } michael@0: PropertyProvider provider(textRun, textStyle, frag, this, michael@0: iter, len, nullptr, 0, aTextRunType); michael@0: michael@0: bool collapseWhitespace = !textStyle->WhiteSpaceIsSignificant(); michael@0: bool preformatNewlines = textStyle->NewlineIsSignificant(); michael@0: bool preformatTabs = textStyle->WhiteSpaceIsSignificant(); michael@0: gfxFloat tabWidth = -1; michael@0: uint32_t start = michael@0: FindStartAfterSkippingWhitespace(&provider, aData, textStyle, &iter, flowEndInTextRun); michael@0: michael@0: AutoFallibleTArray hyphBuffer; michael@0: bool *hyphBreakBefore = nullptr; michael@0: if (hyphenating) { michael@0: hyphBreakBefore = hyphBuffer.AppendElements(flowEndInTextRun - start); michael@0: if (hyphBreakBefore) { michael@0: provider.GetHyphenationBreaks(start, flowEndInTextRun - start, michael@0: hyphBreakBefore); michael@0: } michael@0: } michael@0: michael@0: for (uint32_t i = start, wordStart = start; i <= flowEndInTextRun; ++i) { michael@0: bool preformattedNewline = false; michael@0: bool preformattedTab = false; michael@0: if (i < flowEndInTextRun) { michael@0: // XXXldb Shouldn't we be including the newline as part of the michael@0: // segment that it ends rather than part of the segment that it michael@0: // starts? michael@0: preformattedNewline = preformatNewlines && textRun->CharIsNewline(i); michael@0: preformattedTab = preformatTabs && textRun->CharIsTab(i); michael@0: if (!textRun->CanBreakLineBefore(i) && michael@0: !preformattedNewline && michael@0: !preformattedTab && michael@0: (!hyphBreakBefore || !hyphBreakBefore[i - start])) michael@0: { michael@0: // we can't break here (and it's not the end of the flow) michael@0: continue; michael@0: } michael@0: } michael@0: michael@0: if (i > wordStart) { michael@0: nscoord width = michael@0: NSToCoordCeilClamped(textRun->GetAdvanceWidth(wordStart, i - wordStart, &provider)); michael@0: aData->currentLine = NSCoordSaturatingAdd(aData->currentLine, width); michael@0: aData->atStartOfLine = false; michael@0: michael@0: if (collapseWhitespace) { michael@0: uint32_t trimStart = GetEndOfTrimmedText(frag, textStyle, wordStart, i, &iter); michael@0: if (trimStart == start) { michael@0: // This is *all* trimmable whitespace, so whatever trailingWhitespace michael@0: // we saw previously is still trailing... michael@0: aData->trailingWhitespace += width; michael@0: } else { michael@0: // Some non-whitespace so the old trailingWhitespace is no longer trailing michael@0: aData->trailingWhitespace = michael@0: NSToCoordCeilClamped(textRun->GetAdvanceWidth(trimStart, i - trimStart, &provider)); michael@0: } michael@0: } else { michael@0: aData->trailingWhitespace = 0; michael@0: } michael@0: } michael@0: michael@0: if (preformattedTab) { michael@0: PropertyProvider::Spacing spacing; michael@0: provider.GetSpacing(i, 1, &spacing); michael@0: aData->currentLine += nscoord(spacing.mBefore); michael@0: gfxFloat afterTab = michael@0: AdvanceToNextTab(aData->currentLine, this, michael@0: textRun, &tabWidth); michael@0: aData->currentLine = nscoord(afterTab + spacing.mAfter); michael@0: wordStart = i + 1; michael@0: } else if (i < flowEndInTextRun || michael@0: (i == textRun->GetLength() && michael@0: (textRun->GetFlags() & nsTextFrameUtils::TEXT_HAS_TRAILING_BREAK))) { michael@0: if (preformattedNewline) { michael@0: aData->ForceBreak(aRenderingContext); michael@0: } else if (i < flowEndInTextRun && hyphBreakBefore && michael@0: hyphBreakBefore[i - start]) michael@0: { michael@0: aData->OptionallyBreak(aRenderingContext, michael@0: NSToCoordRound(provider.GetHyphenWidth())); michael@0: } else { michael@0: aData->OptionallyBreak(aRenderingContext); michael@0: } michael@0: wordStart = i; michael@0: } michael@0: } michael@0: michael@0: if (start < flowEndInTextRun) { michael@0: // Check if we have collapsible whitespace at the end michael@0: aData->skipWhitespace = michael@0: IsTrimmableSpace(provider.GetFragment(), michael@0: iter.ConvertSkippedToOriginal(flowEndInTextRun - 1), michael@0: textStyle); michael@0: } michael@0: } michael@0: michael@0: bool nsTextFrame::IsCurrentFontInflation(float aInflation) const { michael@0: return fabsf(aInflation - GetFontSizeInflation()) < 1e-6; michael@0: } michael@0: michael@0: // XXX Need to do something here to avoid incremental reflow bugs due to michael@0: // first-line and first-letter changing min-width michael@0: /* virtual */ void michael@0: nsTextFrame::AddInlineMinWidth(nsRenderingContext *aRenderingContext, michael@0: nsIFrame::InlineMinWidthData *aData) michael@0: { michael@0: float inflation = nsLayoutUtils::FontSizeInflationFor(this); michael@0: TextRunType trtype = (inflation == 1.0f) ? eNotInflated : eInflated; michael@0: michael@0: if (trtype == eInflated && !IsCurrentFontInflation(inflation)) { michael@0: // FIXME: Ideally, if we already have a text run, we'd move it to be michael@0: // the uninflated text run. michael@0: ClearTextRun(nullptr, nsTextFrame::eInflated); michael@0: } michael@0: michael@0: nsTextFrame* f; michael@0: gfxTextRun* lastTextRun = nullptr; michael@0: // nsContinuingTextFrame does nothing for AddInlineMinWidth; all text frames michael@0: // in the flow are handled right here. michael@0: for (f = this; f; f = static_cast(f->GetNextContinuation())) { michael@0: // f->GetTextRun(nsTextFrame::eNotInflated) could be null if we michael@0: // haven't set up textruns yet for f. Except in OOM situations, michael@0: // lastTextRun will only be null for the first text frame. michael@0: if (f == this || f->GetTextRun(trtype) != lastTextRun) { michael@0: nsIFrame* lc; michael@0: if (aData->lineContainer && michael@0: aData->lineContainer != (lc = FindLineContainer(f))) { michael@0: NS_ASSERTION(f != this, "wrong InlineMinWidthData container" michael@0: " for first continuation"); michael@0: aData->line = nullptr; michael@0: aData->lineContainer = lc; michael@0: } michael@0: michael@0: // This will process all the text frames that share the same textrun as f. michael@0: f->AddInlineMinWidthForFlow(aRenderingContext, aData, trtype); michael@0: lastTextRun = f->GetTextRun(trtype); michael@0: } michael@0: } michael@0: } michael@0: michael@0: // XXX this doesn't handle characters shaped by line endings. We need to michael@0: // temporarily override the "current line ending" settings. michael@0: void michael@0: nsTextFrame::AddInlinePrefWidthForFlow(nsRenderingContext *aRenderingContext, michael@0: nsIFrame::InlinePrefWidthData *aData, michael@0: TextRunType aTextRunType) michael@0: { michael@0: uint32_t flowEndInTextRun; michael@0: gfxContext* ctx = aRenderingContext->ThebesContext(); michael@0: gfxSkipCharsIterator iter = michael@0: EnsureTextRun(aTextRunType, ctx, aData->lineContainer, michael@0: aData->line, &flowEndInTextRun); michael@0: gfxTextRun *textRun = GetTextRun(aTextRunType); michael@0: if (!textRun) michael@0: return; michael@0: michael@0: // Pass null for the line container. This will disable tab spacing, but that's michael@0: // OK since we can't really handle tabs for intrinsic sizing anyway. michael@0: michael@0: const nsStyleText* textStyle = StyleText(); michael@0: const nsTextFragment* frag = mContent->GetText(); michael@0: PropertyProvider provider(textRun, textStyle, frag, this, michael@0: iter, INT32_MAX, nullptr, 0, aTextRunType); michael@0: michael@0: bool collapseWhitespace = !textStyle->WhiteSpaceIsSignificant(); michael@0: bool preformatNewlines = textStyle->NewlineIsSignificant(); michael@0: bool preformatTabs = textStyle->WhiteSpaceIsSignificant(); michael@0: gfxFloat tabWidth = -1; michael@0: uint32_t start = michael@0: FindStartAfterSkippingWhitespace(&provider, aData, textStyle, &iter, flowEndInTextRun); michael@0: michael@0: // XXX Should we consider hyphenation here? michael@0: // If newlines and tabs aren't preformatted, nothing to do inside michael@0: // the loop so make i skip to the end michael@0: uint32_t loopStart = (preformatNewlines || preformatTabs) ? start : flowEndInTextRun; michael@0: for (uint32_t i = loopStart, lineStart = start; i <= flowEndInTextRun; ++i) { michael@0: bool preformattedNewline = false; michael@0: bool preformattedTab = false; michael@0: if (i < flowEndInTextRun) { michael@0: // XXXldb Shouldn't we be including the newline as part of the michael@0: // segment that it ends rather than part of the segment that it michael@0: // starts? michael@0: NS_ASSERTION(preformatNewlines || textStyle->NewlineIsDiscarded(), michael@0: "We can't be here unless newlines are hard breaks or are discarded"); michael@0: preformattedNewline = preformatNewlines && textRun->CharIsNewline(i); michael@0: preformattedTab = preformatTabs && textRun->CharIsTab(i); michael@0: if (!preformattedNewline && !preformattedTab) { michael@0: // we needn't break here (and it's not the end of the flow) michael@0: continue; michael@0: } michael@0: } michael@0: michael@0: if (i > lineStart) { michael@0: nscoord width = michael@0: NSToCoordCeilClamped(textRun->GetAdvanceWidth(lineStart, i - lineStart, &provider)); michael@0: aData->currentLine = NSCoordSaturatingAdd(aData->currentLine, width); michael@0: michael@0: if (collapseWhitespace) { michael@0: uint32_t trimStart = GetEndOfTrimmedText(frag, textStyle, lineStart, i, &iter); michael@0: if (trimStart == start) { michael@0: // This is *all* trimmable whitespace, so whatever trailingWhitespace michael@0: // we saw previously is still trailing... michael@0: aData->trailingWhitespace += width; michael@0: } else { michael@0: // Some non-whitespace so the old trailingWhitespace is no longer trailing michael@0: aData->trailingWhitespace = michael@0: NSToCoordCeilClamped(textRun->GetAdvanceWidth(trimStart, i - trimStart, &provider)); michael@0: } michael@0: } else { michael@0: aData->trailingWhitespace = 0; michael@0: } michael@0: } michael@0: michael@0: if (preformattedTab) { michael@0: PropertyProvider::Spacing spacing; michael@0: provider.GetSpacing(i, 1, &spacing); michael@0: aData->currentLine += nscoord(spacing.mBefore); michael@0: gfxFloat afterTab = michael@0: AdvanceToNextTab(aData->currentLine, this, michael@0: textRun, &tabWidth); michael@0: aData->currentLine = nscoord(afterTab + spacing.mAfter); michael@0: lineStart = i + 1; michael@0: } else if (preformattedNewline) { michael@0: aData->ForceBreak(aRenderingContext); michael@0: lineStart = i; michael@0: } michael@0: } michael@0: michael@0: // Check if we have collapsible whitespace at the end michael@0: if (start < flowEndInTextRun) { michael@0: aData->skipWhitespace = michael@0: IsTrimmableSpace(provider.GetFragment(), michael@0: iter.ConvertSkippedToOriginal(flowEndInTextRun - 1), michael@0: textStyle); michael@0: } michael@0: } michael@0: michael@0: // XXX Need to do something here to avoid incremental reflow bugs due to michael@0: // first-line and first-letter changing pref-width michael@0: /* virtual */ void michael@0: nsTextFrame::AddInlinePrefWidth(nsRenderingContext *aRenderingContext, michael@0: nsIFrame::InlinePrefWidthData *aData) michael@0: { michael@0: float inflation = nsLayoutUtils::FontSizeInflationFor(this); michael@0: TextRunType trtype = (inflation == 1.0f) ? eNotInflated : eInflated; michael@0: michael@0: if (trtype == eInflated && !IsCurrentFontInflation(inflation)) { michael@0: // FIXME: Ideally, if we already have a text run, we'd move it to be michael@0: // the uninflated text run. michael@0: ClearTextRun(nullptr, nsTextFrame::eInflated); michael@0: } michael@0: michael@0: nsTextFrame* f; michael@0: gfxTextRun* lastTextRun = nullptr; michael@0: // nsContinuingTextFrame does nothing for AddInlineMinWidth; all text frames michael@0: // in the flow are handled right here. michael@0: for (f = this; f; f = static_cast(f->GetNextContinuation())) { michael@0: // f->GetTextRun(nsTextFrame::eNotInflated) could be null if we michael@0: // haven't set up textruns yet for f. Except in OOM situations, michael@0: // lastTextRun will only be null for the first text frame. michael@0: if (f == this || f->GetTextRun(trtype) != lastTextRun) { michael@0: nsIFrame* lc; michael@0: if (aData->lineContainer && michael@0: aData->lineContainer != (lc = FindLineContainer(f))) { michael@0: NS_ASSERTION(f != this, "wrong InlinePrefWidthData container" michael@0: " for first continuation"); michael@0: aData->line = nullptr; michael@0: aData->lineContainer = lc; michael@0: } michael@0: michael@0: // This will process all the text frames that share the same textrun as f. michael@0: f->AddInlinePrefWidthForFlow(aRenderingContext, aData, trtype); michael@0: lastTextRun = f->GetTextRun(trtype); michael@0: } michael@0: } michael@0: } michael@0: michael@0: /* virtual */ nsSize michael@0: nsTextFrame::ComputeSize(nsRenderingContext *aRenderingContext, michael@0: nsSize aCBSize, nscoord aAvailableWidth, michael@0: nsSize aMargin, nsSize aBorder, nsSize aPadding, michael@0: uint32_t aFlags) michael@0: { michael@0: // Inlines and text don't compute size before reflow. michael@0: return nsSize(NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE); michael@0: } michael@0: michael@0: static nsRect michael@0: RoundOut(const gfxRect& aRect) michael@0: { michael@0: nsRect r; michael@0: r.x = NSToCoordFloor(aRect.X()); michael@0: r.y = NSToCoordFloor(aRect.Y()); michael@0: r.width = NSToCoordCeil(aRect.XMost()) - r.x; michael@0: r.height = NSToCoordCeil(aRect.YMost()) - r.y; michael@0: return r; michael@0: } michael@0: michael@0: nsRect michael@0: nsTextFrame::ComputeTightBounds(gfxContext* aContext) const michael@0: { michael@0: if (StyleContext()->HasTextDecorationLines() || michael@0: (GetStateBits() & TEXT_HYPHEN_BREAK)) { michael@0: // This is conservative, but OK. michael@0: return GetVisualOverflowRect(); michael@0: } michael@0: michael@0: gfxSkipCharsIterator iter = michael@0: const_cast(this)->EnsureTextRun(nsTextFrame::eInflated); michael@0: if (!mTextRun) michael@0: return nsRect(0, 0, 0, 0); michael@0: michael@0: PropertyProvider provider(const_cast(this), iter, michael@0: nsTextFrame::eInflated); michael@0: // Trim trailing whitespace michael@0: provider.InitializeForDisplay(true); michael@0: michael@0: gfxTextRun::Metrics metrics = michael@0: mTextRun->MeasureText(provider.GetStart().GetSkippedOffset(), michael@0: ComputeTransformedLength(provider), michael@0: gfxFont::TIGHT_HINTED_OUTLINE_EXTENTS, michael@0: aContext, &provider); michael@0: // mAscent should be the same as metrics.mAscent, but it's what we use to michael@0: // paint so that's the one we'll use. michael@0: return RoundOut(metrics.mBoundingBox) + nsPoint(0, mAscent); michael@0: } michael@0: michael@0: /* virtual */ nsresult michael@0: nsTextFrame::GetPrefWidthTightBounds(nsRenderingContext* aContext, michael@0: nscoord* aX, michael@0: nscoord* aXMost) michael@0: { michael@0: gfxSkipCharsIterator iter = michael@0: const_cast(this)->EnsureTextRun(nsTextFrame::eInflated); michael@0: if (!mTextRun) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: PropertyProvider provider(const_cast(this), iter, michael@0: nsTextFrame::eInflated); michael@0: provider.InitializeForMeasure(); michael@0: michael@0: gfxTextRun::Metrics metrics = michael@0: mTextRun->MeasureText(provider.GetStart().GetSkippedOffset(), michael@0: ComputeTransformedLength(provider), michael@0: gfxFont::TIGHT_HINTED_OUTLINE_EXTENTS, michael@0: aContext->ThebesContext(), &provider); michael@0: // Round it like nsTextFrame::ComputeTightBounds() to ensure consistency. michael@0: *aX = NSToCoordFloor(metrics.mBoundingBox.x); michael@0: *aXMost = NSToCoordCeil(metrics.mBoundingBox.XMost()); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: static bool michael@0: HasSoftHyphenBefore(const nsTextFragment* aFrag, gfxTextRun* aTextRun, michael@0: int32_t aStartOffset, const gfxSkipCharsIterator& aIter) michael@0: { michael@0: if (aIter.GetSkippedOffset() < aTextRun->GetLength() && michael@0: aTextRun->CanHyphenateBefore(aIter.GetSkippedOffset())) { michael@0: return true; michael@0: } michael@0: if (!(aTextRun->GetFlags() & nsTextFrameUtils::TEXT_HAS_SHY)) michael@0: return false; michael@0: gfxSkipCharsIterator iter = aIter; michael@0: while (iter.GetOriginalOffset() > aStartOffset) { michael@0: iter.AdvanceOriginal(-1); michael@0: if (!iter.IsOriginalCharSkipped()) michael@0: break; michael@0: if (aFrag->CharAt(iter.GetOriginalOffset()) == CH_SHY) michael@0: return true; michael@0: } michael@0: return false; michael@0: } michael@0: michael@0: /** michael@0: * Removes all frames from aFrame up to (but not including) aFirstToNotRemove, michael@0: * because their text has all been taken and reflowed by earlier frames. michael@0: */ michael@0: static void michael@0: RemoveEmptyInFlows(nsTextFrame* aFrame, nsTextFrame* aFirstToNotRemove) michael@0: { michael@0: NS_PRECONDITION(aFrame != aFirstToNotRemove, "This will go very badly"); michael@0: // We have to be careful here, because some RemoveFrame implementations michael@0: // remove and destroy not only the passed-in frame but also all its following michael@0: // in-flows (and sometimes all its following continuations in general). So michael@0: // we remove |f| and everything up to but not including firstToNotRemove from michael@0: // the flow first, to make sure that only the things we want destroyed are michael@0: // destroyed. michael@0: michael@0: // This sadly duplicates some of the logic from michael@0: // nsSplittableFrame::RemoveFromFlow. We can get away with not duplicating michael@0: // all of it, because we know that the prev-continuation links of michael@0: // firstToNotRemove and f are fluid, and non-null. michael@0: NS_ASSERTION(aFirstToNotRemove->GetPrevContinuation() == michael@0: aFirstToNotRemove->GetPrevInFlow() && michael@0: aFirstToNotRemove->GetPrevInFlow() != nullptr, michael@0: "aFirstToNotRemove should have a fluid prev continuation"); michael@0: NS_ASSERTION(aFrame->GetPrevContinuation() == michael@0: aFrame->GetPrevInFlow() && michael@0: aFrame->GetPrevInFlow() != nullptr, michael@0: "aFrame should have a fluid prev continuation"); michael@0: michael@0: nsIFrame* prevContinuation = aFrame->GetPrevContinuation(); michael@0: nsIFrame* lastRemoved = aFirstToNotRemove->GetPrevContinuation(); michael@0: michael@0: for (nsTextFrame* f = aFrame; f != aFirstToNotRemove; michael@0: f = static_cast(f->GetNextContinuation())) { michael@0: // f is going to be destroyed soon, after it is unlinked from the michael@0: // continuation chain. If its textrun is going to be destroyed we need to michael@0: // do it now, before we unlink the frames to remove from the flow, michael@0: // because DestroyFrom calls ClearTextRuns() and that will start at the michael@0: // first frame with the text run and walk the continuations. michael@0: if (f->IsInTextRunUserData()) { michael@0: f->ClearTextRuns(); michael@0: } else { michael@0: f->DisconnectTextRuns(); michael@0: } michael@0: } michael@0: michael@0: prevContinuation->SetNextInFlow(aFirstToNotRemove); michael@0: aFirstToNotRemove->SetPrevInFlow(prevContinuation); michael@0: michael@0: aFrame->SetPrevInFlow(nullptr); michael@0: lastRemoved->SetNextInFlow(nullptr); michael@0: michael@0: nsIFrame* parent = aFrame->GetParent(); michael@0: nsBlockFrame* parentBlock = nsLayoutUtils::GetAsBlock(parent); michael@0: if (parentBlock) { michael@0: // Manually call DoRemoveFrame so we can tell it that we're michael@0: // removing empty frames; this will keep it from blowing away michael@0: // text runs. michael@0: parentBlock->DoRemoveFrame(aFrame, nsBlockFrame::FRAMES_ARE_EMPTY); michael@0: } else { michael@0: // Just remove it normally; use kNoReflowPrincipalList to avoid posting michael@0: // new reflows. michael@0: parent->RemoveFrame(nsIFrame::kNoReflowPrincipalList, aFrame); michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsTextFrame::SetLength(int32_t aLength, nsLineLayout* aLineLayout, michael@0: uint32_t aSetLengthFlags) michael@0: { michael@0: mContentLengthHint = aLength; michael@0: int32_t end = GetContentOffset() + aLength; michael@0: nsTextFrame* f = static_cast(GetNextInFlow()); michael@0: if (!f) michael@0: return; michael@0: michael@0: // If our end offset is moving, then even if frames are not being pushed or michael@0: // pulled, content is moving to or from the next line and the next line michael@0: // must be reflowed. michael@0: // If the next-continuation is dirty, then we should dirty the next line now michael@0: // because we may have skipped doing it if we dirtied it in michael@0: // CharacterDataChanged. This is ugly but teaching FrameNeedsReflow michael@0: // and ChildIsDirty to handle a range of frames would be worse. michael@0: if (aLineLayout && michael@0: (end != f->mContentOffset || (f->GetStateBits() & NS_FRAME_IS_DIRTY))) { michael@0: aLineLayout->SetDirtyNextLine(); michael@0: } michael@0: michael@0: if (end < f->mContentOffset) { michael@0: // Our frame is shrinking. Give the text to our next in flow. michael@0: if (aLineLayout && michael@0: HasSignificantTerminalNewline() && michael@0: GetParent()->GetType() != nsGkAtoms::letterFrame && michael@0: (aSetLengthFlags & ALLOW_FRAME_CREATION_AND_DESTRUCTION)) { michael@0: // Whatever text we hand to our next-in-flow will end up in a frame all of michael@0: // its own, since it ends in a forced linebreak. Might as well just put michael@0: // it in a separate frame now. This is important to prevent text run michael@0: // churn; if we did not do that, then we'd likely end up rebuilding michael@0: // textruns for all our following continuations. michael@0: // We skip this optimization when the parent is a first-letter frame michael@0: // because it doesn't deal well with more than one child frame. michael@0: // We also skip this optimization if we were called during bidi michael@0: // resolution, so as not to create a new frame which doesn't appear in michael@0: // the bidi resolver's list of frames michael@0: nsPresContext* presContext = PresContext(); michael@0: nsIFrame* newFrame = presContext->PresShell()->FrameConstructor()-> michael@0: CreateContinuingFrame(presContext, this, GetParent()); michael@0: nsTextFrame* next = static_cast(newFrame); michael@0: nsFrameList temp(next, next); michael@0: GetParent()->InsertFrames(kNoReflowPrincipalList, this, temp); michael@0: f = next; michael@0: } michael@0: michael@0: f->mContentOffset = end; michael@0: if (f->GetTextRun(nsTextFrame::eInflated) != mTextRun) { michael@0: ClearTextRuns(); michael@0: f->ClearTextRuns(); michael@0: } michael@0: return; michael@0: } michael@0: // Our frame is growing. Take text from our in-flow(s). michael@0: // We can take text from frames in lines beyond just the next line. michael@0: // We don't dirty those lines. That's OK, because when we reflow michael@0: // our empty next-in-flow, it will take text from its next-in-flow and michael@0: // dirty that line. michael@0: michael@0: // Note that in the process we may end up removing some frames from michael@0: // the flow if they end up empty. michael@0: nsTextFrame* framesToRemove = nullptr; michael@0: while (f && f->mContentOffset < end) { michael@0: f->mContentOffset = end; michael@0: if (f->GetTextRun(nsTextFrame::eInflated) != mTextRun) { michael@0: ClearTextRuns(); michael@0: f->ClearTextRuns(); michael@0: } michael@0: nsTextFrame* next = static_cast(f->GetNextInFlow()); michael@0: // Note: the "f->GetNextSibling() == next" check below is to restrict michael@0: // this optimization to the case where they are on the same child list. michael@0: // Otherwise we might remove the only child of a nsFirstLetterFrame michael@0: // for example and it can't handle that. See bug 597627 for details. michael@0: if (next && next->mContentOffset <= end && f->GetNextSibling() == next && michael@0: (aSetLengthFlags & ALLOW_FRAME_CREATION_AND_DESTRUCTION)) { michael@0: // |f| is now empty. We may as well remove it, instead of copying all michael@0: // the text from |next| into it instead; the latter leads to use michael@0: // rebuilding textruns for all following continuations. michael@0: // We skip this optimization if we were called during bidi resolution, michael@0: // since the bidi resolver may try to handle the destroyed frame later michael@0: // and crash michael@0: if (!framesToRemove) { michael@0: // Remember that we have to remove this frame. michael@0: framesToRemove = f; michael@0: } michael@0: } else if (framesToRemove) { michael@0: RemoveEmptyInFlows(framesToRemove, f); michael@0: framesToRemove = nullptr; michael@0: } michael@0: f = next; michael@0: } michael@0: NS_POSTCONDITION(!framesToRemove || (f && f->mContentOffset == end), michael@0: "How did we exit the loop if we null out framesToRemove if " michael@0: "!next || next->mContentOffset > end ?"); michael@0: if (framesToRemove) { michael@0: // We are guaranteed that we exited the loop with f not null, per the michael@0: // postcondition above michael@0: RemoveEmptyInFlows(framesToRemove, f); michael@0: } michael@0: michael@0: #ifdef DEBUG michael@0: f = this; michael@0: int32_t iterations = 0; michael@0: while (f && iterations < 10) { michael@0: f->GetContentLength(); // Assert if negative length michael@0: f = static_cast(f->GetNextContinuation()); michael@0: ++iterations; michael@0: } michael@0: f = this; michael@0: iterations = 0; michael@0: while (f && iterations < 10) { michael@0: f->GetContentLength(); // Assert if negative length michael@0: f = static_cast(f->GetPrevContinuation()); michael@0: ++iterations; michael@0: } michael@0: #endif michael@0: } michael@0: michael@0: bool michael@0: nsTextFrame::IsFloatingFirstLetterChild() const michael@0: { michael@0: nsIFrame* frame = GetParent(); michael@0: return frame && frame->IsFloating() && michael@0: frame->GetType() == nsGkAtoms::letterFrame; michael@0: } michael@0: michael@0: struct NewlineProperty { michael@0: int32_t mStartOffset; michael@0: // The offset of the first \n after mStartOffset, or -1 if there is none michael@0: int32_t mNewlineOffset; michael@0: }; michael@0: michael@0: nsresult michael@0: nsTextFrame::Reflow(nsPresContext* aPresContext, michael@0: nsHTMLReflowMetrics& aMetrics, michael@0: const nsHTMLReflowState& aReflowState, michael@0: nsReflowStatus& aStatus) michael@0: { michael@0: DO_GLOBAL_REFLOW_COUNT("nsTextFrame"); michael@0: DISPLAY_REFLOW(aPresContext, this, aReflowState, aMetrics, aStatus); michael@0: michael@0: // XXX If there's no line layout, we shouldn't even have created this michael@0: // frame. This may happen if, for example, this is text inside a table michael@0: // but not inside a cell. For now, just don't reflow. michael@0: if (!aReflowState.mLineLayout) { michael@0: ClearMetrics(aMetrics); michael@0: aStatus = NS_FRAME_COMPLETE; michael@0: return NS_OK; michael@0: } michael@0: michael@0: ReflowText(*aReflowState.mLineLayout, aReflowState.AvailableWidth(), michael@0: aReflowState.rendContext, aMetrics, aStatus); michael@0: michael@0: NS_FRAME_SET_TRUNCATION(aStatus, aReflowState, aMetrics); michael@0: return NS_OK; michael@0: } michael@0: michael@0: #ifdef ACCESSIBILITY michael@0: /** michael@0: * Notifies accessibility about text reflow. Used by nsTextFrame::ReflowText. michael@0: */ michael@0: class MOZ_STACK_CLASS ReflowTextA11yNotifier michael@0: { michael@0: public: michael@0: ReflowTextA11yNotifier(nsPresContext* aPresContext, nsIContent* aContent) : michael@0: mContent(aContent), mPresContext(aPresContext) michael@0: { michael@0: } michael@0: ~ReflowTextA11yNotifier() michael@0: { michael@0: nsAccessibilityService* accService = nsIPresShell::AccService(); michael@0: if (accService) { michael@0: accService->UpdateText(mPresContext->PresShell(), mContent); michael@0: } michael@0: } michael@0: private: michael@0: ReflowTextA11yNotifier(); michael@0: ReflowTextA11yNotifier(const ReflowTextA11yNotifier&); michael@0: ReflowTextA11yNotifier& operator =(const ReflowTextA11yNotifier&); michael@0: michael@0: nsIContent* mContent; michael@0: nsPresContext* mPresContext; michael@0: }; michael@0: #endif michael@0: michael@0: void michael@0: nsTextFrame::ReflowText(nsLineLayout& aLineLayout, nscoord aAvailableWidth, michael@0: nsRenderingContext* aRenderingContext, michael@0: nsHTMLReflowMetrics& aMetrics, michael@0: nsReflowStatus& aStatus) michael@0: { michael@0: #ifdef NOISY_REFLOW michael@0: ListTag(stdout); michael@0: printf(": BeginReflow: availableWidth=%d\n", aAvailableWidth); michael@0: #endif michael@0: michael@0: nsPresContext* presContext = PresContext(); michael@0: michael@0: #ifdef ACCESSIBILITY michael@0: // Schedule the update of accessible tree since rendered text might be changed. michael@0: ReflowTextA11yNotifier(presContext, mContent); michael@0: #endif michael@0: michael@0: ///////////////////////////////////////////////////////////////////// michael@0: // Set up flags and clear out state michael@0: ///////////////////////////////////////////////////////////////////// michael@0: michael@0: // Clear out the reflow state flags in mState. We also clear the whitespace michael@0: // flags because this can change whether the frame maps whitespace-only text michael@0: // or not. michael@0: RemoveStateBits(TEXT_REFLOW_FLAGS | TEXT_WHITESPACE_FLAGS); michael@0: michael@0: // Temporarily map all possible content while we construct our new textrun. michael@0: // so that when doing reflow our styles prevail over any part of the michael@0: // textrun we look at. Note that next-in-flows may be mapping the same michael@0: // content; gfxTextRun construction logic will ensure that we take priority. michael@0: int32_t maxContentLength = GetInFlowContentLength(); michael@0: michael@0: // We don't need to reflow if there is no content. michael@0: if (!maxContentLength) { michael@0: ClearMetrics(aMetrics); michael@0: aStatus = NS_FRAME_COMPLETE; michael@0: return; michael@0: } michael@0: michael@0: #ifdef NOISY_BIDI michael@0: printf("Reflowed textframe\n"); michael@0: #endif michael@0: michael@0: const nsStyleText* textStyle = StyleText(); michael@0: michael@0: bool atStartOfLine = aLineLayout.LineAtStart(); michael@0: if (atStartOfLine) { michael@0: AddStateBits(TEXT_START_OF_LINE); michael@0: } michael@0: michael@0: uint32_t flowEndInTextRun; michael@0: nsIFrame* lineContainer = aLineLayout.LineContainerFrame(); michael@0: gfxContext* ctx = aRenderingContext->ThebesContext(); michael@0: const nsTextFragment* frag = mContent->GetText(); michael@0: michael@0: // DOM offsets of the text range we need to measure, after trimming michael@0: // whitespace, restricting to first-letter, and restricting preformatted text michael@0: // to nearest newline michael@0: int32_t length = maxContentLength; michael@0: int32_t offset = GetContentOffset(); michael@0: michael@0: // Restrict preformatted text to the nearest newline michael@0: int32_t newLineOffset = -1; // this will be -1 or a content offset michael@0: int32_t contentNewLineOffset = -1; michael@0: // Pointer to the nsGkAtoms::newline set on this frame's element michael@0: NewlineProperty* cachedNewlineOffset = nullptr; michael@0: if (textStyle->NewlineIsSignificant()) { michael@0: cachedNewlineOffset = michael@0: static_cast(mContent->GetProperty(nsGkAtoms::newline)); michael@0: if (cachedNewlineOffset && cachedNewlineOffset->mStartOffset <= offset && michael@0: (cachedNewlineOffset->mNewlineOffset == -1 || michael@0: cachedNewlineOffset->mNewlineOffset >= offset)) { michael@0: contentNewLineOffset = cachedNewlineOffset->mNewlineOffset; michael@0: } else { michael@0: contentNewLineOffset = FindChar(frag, offset, michael@0: mContent->TextLength() - offset, '\n'); michael@0: } michael@0: if (contentNewLineOffset < offset + length) { michael@0: /* michael@0: The new line offset could be outside this frame if the frame has been michael@0: split by bidi resolution. In that case we won't use it in this reflow michael@0: (newLineOffset will remain -1), but we will still cache it in mContent michael@0: */ michael@0: newLineOffset = contentNewLineOffset; michael@0: } michael@0: if (newLineOffset >= 0) { michael@0: length = newLineOffset + 1 - offset; michael@0: } michael@0: } michael@0: if ((atStartOfLine && !textStyle->WhiteSpaceIsSignificant()) || michael@0: (GetStateBits() & TEXT_IS_IN_TOKEN_MATHML)) { michael@0: // Skip leading whitespace. Make sure we don't skip a 'pre-line' michael@0: // newline if there is one. michael@0: int32_t skipLength = newLineOffset >= 0 ? length - 1 : length; michael@0: int32_t whitespaceCount = michael@0: GetTrimmableWhitespaceCount(frag, offset, skipLength, 1); michael@0: if (whitespaceCount) { michael@0: offset += whitespaceCount; michael@0: length -= whitespaceCount; michael@0: // Make sure this frame maps the trimmable whitespace. michael@0: if (MOZ_UNLIKELY(offset > GetContentEnd())) { michael@0: SetLength(offset - GetContentOffset(), &aLineLayout, michael@0: ALLOW_FRAME_CREATION_AND_DESTRUCTION); michael@0: } michael@0: } michael@0: } michael@0: michael@0: bool completedFirstLetter = false; michael@0: // Layout dependent styles are a problem because we need to reconstruct michael@0: // the gfxTextRun based on our layout. michael@0: if (aLineLayout.GetInFirstLetter() || aLineLayout.GetInFirstLine()) { michael@0: SetLength(maxContentLength, &aLineLayout, michael@0: ALLOW_FRAME_CREATION_AND_DESTRUCTION); michael@0: michael@0: if (aLineLayout.GetInFirstLetter()) { michael@0: // floating first-letter boundaries are significant in textrun michael@0: // construction, so clear the textrun out every time we hit a first-letter michael@0: // and have changed our length (which controls the first-letter boundary) michael@0: ClearTextRuns(); michael@0: // Find the length of the first-letter. We need a textrun for this. michael@0: // REVIEW: maybe-bogus inflation should be ok (fixed below) michael@0: gfxSkipCharsIterator iter = michael@0: EnsureTextRun(nsTextFrame::eInflated, ctx, michael@0: lineContainer, aLineLayout.GetLine(), michael@0: &flowEndInTextRun); michael@0: michael@0: if (mTextRun) { michael@0: int32_t firstLetterLength = length; michael@0: if (aLineLayout.GetFirstLetterStyleOK()) { michael@0: completedFirstLetter = michael@0: FindFirstLetterRange(frag, mTextRun, offset, iter, &firstLetterLength); michael@0: if (newLineOffset >= 0) { michael@0: // Don't allow a preformatted newline to be part of a first-letter. michael@0: firstLetterLength = std::min(firstLetterLength, length - 1); michael@0: if (length == 1) { michael@0: // There is no text to be consumed by the first-letter before the michael@0: // preformatted newline. Note that the first letter is therefore michael@0: // complete (FindFirstLetterRange will have returned false). michael@0: completedFirstLetter = true; michael@0: } michael@0: } michael@0: } else { michael@0: // We're in a first-letter frame's first in flow, so if there michael@0: // was a first-letter, we'd be it. However, for one reason michael@0: // or another (e.g., preformatted line break before this text), michael@0: // we're not actually supposed to have first-letter style. So michael@0: // just make a zero-length first-letter. michael@0: firstLetterLength = 0; michael@0: completedFirstLetter = true; michael@0: } michael@0: length = firstLetterLength; michael@0: if (length) { michael@0: AddStateBits(TEXT_FIRST_LETTER); michael@0: } michael@0: // Change this frame's length to the first-letter length right now michael@0: // so that when we rebuild the textrun it will be built with the michael@0: // right first-letter boundary michael@0: SetLength(offset + length - GetContentOffset(), &aLineLayout, michael@0: ALLOW_FRAME_CREATION_AND_DESTRUCTION); michael@0: // Ensure that the textrun will be rebuilt michael@0: ClearTextRuns(); michael@0: } michael@0: } michael@0: } michael@0: michael@0: float fontSizeInflation = nsLayoutUtils::FontSizeInflationFor(this); michael@0: michael@0: if (!IsCurrentFontInflation(fontSizeInflation)) { michael@0: // FIXME: Ideally, if we already have a text run, we'd move it to be michael@0: // the uninflated text run. michael@0: ClearTextRun(nullptr, nsTextFrame::eInflated); michael@0: } michael@0: michael@0: gfxSkipCharsIterator iter = michael@0: EnsureTextRun(nsTextFrame::eInflated, ctx, michael@0: lineContainer, aLineLayout.GetLine(), &flowEndInTextRun); michael@0: michael@0: NS_ASSERTION(IsCurrentFontInflation(fontSizeInflation), michael@0: "EnsureTextRun should have set font size inflation"); michael@0: michael@0: if (mTextRun && iter.GetOriginalEnd() < offset + length) { michael@0: // The textrun does not map enough text for this frame. This can happen michael@0: // when the textrun was ended in the middle of a text node because a michael@0: // preformatted newline was encountered, and prev-in-flow frames have michael@0: // consumed all the text of the textrun. We need a new textrun. michael@0: ClearTextRuns(); michael@0: iter = EnsureTextRun(nsTextFrame::eInflated, ctx, michael@0: lineContainer, aLineLayout.GetLine(), michael@0: &flowEndInTextRun); michael@0: } michael@0: michael@0: if (!mTextRun) { michael@0: ClearMetrics(aMetrics); michael@0: aStatus = NS_FRAME_COMPLETE; michael@0: return; michael@0: } michael@0: michael@0: NS_ASSERTION(gfxSkipCharsIterator(iter).ConvertOriginalToSkipped(offset + length) michael@0: <= mTextRun->GetLength(), michael@0: "Text run does not map enough text for our reflow"); michael@0: michael@0: ///////////////////////////////////////////////////////////////////// michael@0: // See how much text should belong to this text frame, and measure it michael@0: ///////////////////////////////////////////////////////////////////// michael@0: michael@0: iter.SetOriginalOffset(offset); michael@0: nscoord xOffsetForTabs = (mTextRun->GetFlags() & nsTextFrameUtils::TEXT_HAS_TAB) ? michael@0: (aLineLayout.GetCurrentFrameInlineDistanceFromBlock() - michael@0: lineContainer->GetUsedBorderAndPadding().left) michael@0: : -1; michael@0: PropertyProvider provider(mTextRun, textStyle, frag, this, iter, length, michael@0: lineContainer, xOffsetForTabs, nsTextFrame::eInflated); michael@0: michael@0: uint32_t transformedOffset = provider.GetStart().GetSkippedOffset(); michael@0: michael@0: // The metrics for the text go in here michael@0: gfxTextRun::Metrics textMetrics; michael@0: gfxFont::BoundingBoxType boundingBoxType = IsFloatingFirstLetterChild() ? michael@0: gfxFont::TIGHT_HINTED_OUTLINE_EXTENTS : michael@0: gfxFont::LOOSE_INK_EXTENTS; michael@0: NS_ASSERTION(!(NS_REFLOW_CALC_BOUNDING_METRICS & aMetrics.mFlags), michael@0: "We shouldn't be passed NS_REFLOW_CALC_BOUNDING_METRICS anymore"); michael@0: michael@0: int32_t limitLength = length; michael@0: int32_t forceBreak = aLineLayout.GetForcedBreakPosition(mContent); michael@0: bool forceBreakAfter = false; michael@0: if (forceBreak >= offset + length) { michael@0: forceBreakAfter = forceBreak == offset + length; michael@0: // The break is not within the text considered for this textframe. michael@0: forceBreak = -1; michael@0: } michael@0: if (forceBreak >= 0) { michael@0: limitLength = forceBreak - offset; michael@0: NS_ASSERTION(limitLength >= 0, "Weird break found!"); michael@0: } michael@0: // This is the heart of text reflow right here! We don't know where michael@0: // to break, so we need to see how much text fits in the available width. michael@0: uint32_t transformedLength; michael@0: if (offset + limitLength >= int32_t(frag->GetLength())) { michael@0: NS_ASSERTION(offset + limitLength == int32_t(frag->GetLength()), michael@0: "Content offset/length out of bounds"); michael@0: NS_ASSERTION(flowEndInTextRun >= transformedOffset, michael@0: "Negative flow length?"); michael@0: transformedLength = flowEndInTextRun - transformedOffset; michael@0: } else { michael@0: // we're not looking at all the content, so we need to compute the michael@0: // length of the transformed substring we're looking at michael@0: gfxSkipCharsIterator iter(provider.GetStart()); michael@0: iter.SetOriginalOffset(offset + limitLength); michael@0: transformedLength = iter.GetSkippedOffset() - transformedOffset; michael@0: } michael@0: uint32_t transformedLastBreak = 0; michael@0: bool usedHyphenation; michael@0: gfxFloat trimmedWidth = 0; michael@0: gfxFloat availWidth = aAvailableWidth; michael@0: bool canTrimTrailingWhitespace = !textStyle->WhiteSpaceIsSignificant() || michael@0: (GetStateBits() & TEXT_IS_IN_TOKEN_MATHML); michael@0: int32_t unusedOffset; michael@0: gfxBreakPriority breakPriority; michael@0: aLineLayout.GetLastOptionalBreakPosition(&unusedOffset, &breakPriority); michael@0: uint32_t transformedCharsFit = michael@0: mTextRun->BreakAndMeasureText(transformedOffset, transformedLength, michael@0: (GetStateBits() & TEXT_START_OF_LINE) != 0, michael@0: availWidth, michael@0: &provider, !aLineLayout.LineIsBreakable(), michael@0: canTrimTrailingWhitespace ? &trimmedWidth : nullptr, michael@0: &textMetrics, boundingBoxType, ctx, michael@0: &usedHyphenation, &transformedLastBreak, michael@0: textStyle->WordCanWrap(this), &breakPriority); michael@0: if (!length && !textMetrics.mAscent && !textMetrics.mDescent) { michael@0: // If we're measuring a zero-length piece of text, update michael@0: // the height manually. michael@0: nsFontMetrics* fm = provider.GetFontMetrics(); michael@0: if (fm) { michael@0: textMetrics.mAscent = gfxFloat(fm->MaxAscent()); michael@0: textMetrics.mDescent = gfxFloat(fm->MaxDescent()); michael@0: } michael@0: } michael@0: // The "end" iterator points to the first character after the string mapped michael@0: // by this frame. Basically, its original-string offset is offset+charsFit michael@0: // after we've computed charsFit. michael@0: gfxSkipCharsIterator end(provider.GetEndHint()); michael@0: end.SetSkippedOffset(transformedOffset + transformedCharsFit); michael@0: int32_t charsFit = end.GetOriginalOffset() - offset; michael@0: if (offset + charsFit == newLineOffset) { michael@0: // We broke before a trailing preformatted '\n'. The newline should michael@0: // be assigned to this frame. Note that newLineOffset will be -1 if michael@0: // there was no preformatted newline, so we wouldn't get here in that michael@0: // case. michael@0: ++charsFit; michael@0: } michael@0: // That might have taken us beyond our assigned content range (because michael@0: // we might have advanced over some skipped chars that extend outside michael@0: // this frame), so get back in. michael@0: int32_t lastBreak = -1; michael@0: if (charsFit >= limitLength) { michael@0: charsFit = limitLength; michael@0: if (transformedLastBreak != UINT32_MAX) { michael@0: // lastBreak is needed. michael@0: // This may set lastBreak greater than 'length', but that's OK michael@0: lastBreak = end.ConvertSkippedToOriginal(transformedOffset + transformedLastBreak); michael@0: } michael@0: end.SetOriginalOffset(offset + charsFit); michael@0: // If we were forced to fit, and the break position is after a soft hyphen, michael@0: // note that this is a hyphenation break. michael@0: if ((forceBreak >= 0 || forceBreakAfter) && michael@0: HasSoftHyphenBefore(frag, mTextRun, offset, end)) { michael@0: usedHyphenation = true; michael@0: } michael@0: } michael@0: if (usedHyphenation) { michael@0: // Fix up metrics to include hyphen michael@0: AddHyphenToMetrics(this, mTextRun, &textMetrics, boundingBoxType, ctx); michael@0: AddStateBits(TEXT_HYPHEN_BREAK | TEXT_HAS_NONCOLLAPSED_CHARACTERS); michael@0: } michael@0: michael@0: gfxFloat trimmableWidth = 0; michael@0: bool brokeText = forceBreak >= 0 || transformedCharsFit < transformedLength; michael@0: if (canTrimTrailingWhitespace) { michael@0: // Optimization: if we trimmed trailing whitespace, and we can be sure michael@0: // this frame will be at the end of the line, then leave it trimmed off. michael@0: // Otherwise we have to undo the trimming, in case we're not at the end of michael@0: // the line. (If we actually do end up at the end of the line, we'll have michael@0: // to trim it off again in TrimTrailingWhiteSpace, and we'd like to avoid michael@0: // having to re-do it.) michael@0: if (brokeText || michael@0: (GetStateBits() & TEXT_IS_IN_TOKEN_MATHML)) { michael@0: // We're definitely going to break so our trailing whitespace should michael@0: // definitely be trimmed. Record that we've already done it. michael@0: AddStateBits(TEXT_TRIMMED_TRAILING_WHITESPACE); michael@0: } else if (!(GetStateBits() & TEXT_IS_IN_TOKEN_MATHML)) { michael@0: // We might not be at the end of the line. (Note that even if this frame michael@0: // ends in breakable whitespace, it might not be at the end of the line michael@0: // because it might be followed by breakable, but preformatted, whitespace.) michael@0: // Undo the trimming. michael@0: textMetrics.mAdvanceWidth += trimmedWidth; michael@0: trimmableWidth = trimmedWidth; michael@0: if (mTextRun->IsRightToLeft()) { michael@0: // Space comes before text, so the bounding box is moved to the michael@0: // right by trimmdWidth michael@0: textMetrics.mBoundingBox.MoveBy(gfxPoint(trimmedWidth, 0)); michael@0: } michael@0: } michael@0: } michael@0: michael@0: if (!brokeText && lastBreak >= 0) { michael@0: // Since everything fit and no break was forced, michael@0: // record the last break opportunity michael@0: NS_ASSERTION(textMetrics.mAdvanceWidth - trimmableWidth <= aAvailableWidth, michael@0: "If the text doesn't fit, and we have a break opportunity, why didn't MeasureText use it?"); michael@0: aLineLayout.NotifyOptionalBreakPosition(mContent, lastBreak, true, breakPriority); michael@0: } michael@0: michael@0: int32_t contentLength = offset + charsFit - GetContentOffset(); michael@0: michael@0: ///////////////////////////////////////////////////////////////////// michael@0: // Compute output metrics michael@0: ///////////////////////////////////////////////////////////////////// michael@0: michael@0: // first-letter frames should use the tight bounding box metrics for ascent/descent michael@0: // for good drop-cap effects michael@0: if (GetStateBits() & TEXT_FIRST_LETTER) { michael@0: textMetrics.mAscent = std::max(gfxFloat(0.0), -textMetrics.mBoundingBox.Y()); michael@0: textMetrics.mDescent = std::max(gfxFloat(0.0), textMetrics.mBoundingBox.YMost()); michael@0: } michael@0: michael@0: // Setup metrics for caller michael@0: // Disallow negative widths michael@0: aMetrics.Width() = NSToCoordCeil(std::max(gfxFloat(0.0), textMetrics.mAdvanceWidth)); michael@0: michael@0: if (transformedCharsFit == 0 && !usedHyphenation) { michael@0: aMetrics.SetTopAscent(0); michael@0: aMetrics.Height() = 0; michael@0: } else if (boundingBoxType != gfxFont::LOOSE_INK_EXTENTS) { michael@0: // Use actual text metrics for floating first letter frame. michael@0: aMetrics.SetTopAscent(NSToCoordCeil(textMetrics.mAscent)); michael@0: aMetrics.Height() = aMetrics.TopAscent() + NSToCoordCeil(textMetrics.mDescent); michael@0: } else { michael@0: // Otherwise, ascent should contain the overline drawable area. michael@0: // And also descent should contain the underline drawable area. michael@0: // nsFontMetrics::GetMaxAscent/GetMaxDescent contains them. michael@0: nsFontMetrics* fm = provider.GetFontMetrics(); michael@0: nscoord fontAscent = fm->MaxAscent(); michael@0: nscoord fontDescent = fm->MaxDescent(); michael@0: aMetrics.SetTopAscent(std::max(NSToCoordCeil(textMetrics.mAscent), fontAscent)); michael@0: nscoord descent = std::max(NSToCoordCeil(textMetrics.mDescent), fontDescent); michael@0: aMetrics.Height() = aMetrics.TopAscent() + descent; michael@0: } michael@0: michael@0: NS_ASSERTION(aMetrics.TopAscent() >= 0, "Negative ascent???"); michael@0: NS_ASSERTION(aMetrics.Height() - aMetrics.TopAscent() >= 0, "Negative descent???"); michael@0: michael@0: mAscent = aMetrics.TopAscent(); michael@0: michael@0: // Handle text that runs outside its normal bounds. michael@0: nsRect boundingBox = RoundOut(textMetrics.mBoundingBox) + nsPoint(0, mAscent); michael@0: aMetrics.SetOverflowAreasToDesiredBounds(); michael@0: aMetrics.VisualOverflow().UnionRect(aMetrics.VisualOverflow(), boundingBox); michael@0: michael@0: // When we have text decorations, we don't need to compute their overflow now michael@0: // because we're guaranteed to do it later michael@0: // (see nsLineLayout::RelativePositionFrames) michael@0: UnionAdditionalOverflow(presContext, *aLineLayout.LineContainerRS(), michael@0: provider, &aMetrics.VisualOverflow(), false); michael@0: michael@0: ///////////////////////////////////////////////////////////////////// michael@0: // Clean up, update state michael@0: ///////////////////////////////////////////////////////////////////// michael@0: michael@0: // If all our characters are discarded or collapsed, then trimmable width michael@0: // from the last textframe should be preserved. Otherwise the trimmable width michael@0: // from this textframe overrides. (Currently in CSS trimmable width can be michael@0: // at most one space so there's no way for trimmable width from a previous michael@0: // frame to accumulate with trimmable width from this frame.) michael@0: if (transformedCharsFit > 0) { michael@0: aLineLayout.SetTrimmableWidth(NSToCoordFloor(trimmableWidth)); michael@0: AddStateBits(TEXT_HAS_NONCOLLAPSED_CHARACTERS); michael@0: } michael@0: if (charsFit > 0 && charsFit == length && michael@0: textStyle->mHyphens != NS_STYLE_HYPHENS_NONE && michael@0: HasSoftHyphenBefore(frag, mTextRun, offset, end)) { michael@0: // Record a potential break after final soft hyphen michael@0: aLineLayout.NotifyOptionalBreakPosition(mContent, offset + length, michael@0: textMetrics.mAdvanceWidth + provider.GetHyphenWidth() <= availWidth, michael@0: gfxBreakPriority::eNormalBreak); michael@0: } michael@0: bool breakAfter = forceBreakAfter; michael@0: // length == 0 means either the text is empty or it's all collapsed away michael@0: bool emptyTextAtStartOfLine = atStartOfLine && length == 0; michael@0: if (!breakAfter && charsFit == length && !emptyTextAtStartOfLine && michael@0: transformedOffset + transformedLength == mTextRun->GetLength() && michael@0: (mTextRun->GetFlags() & nsTextFrameUtils::TEXT_HAS_TRAILING_BREAK)) { michael@0: // We placed all the text in the textrun and we have a break opportunity at michael@0: // the end of the textrun. We need to record it because the following michael@0: // content may not care about nsLineBreaker. michael@0: michael@0: // Note that because we didn't break, we can be sure that (thanks to the michael@0: // code up above) textMetrics.mAdvanceWidth includes the width of any michael@0: // trailing whitespace. So we need to subtract trimmableWidth here michael@0: // because if we did break at this point, that much width would be trimmed. michael@0: if (textMetrics.mAdvanceWidth - trimmableWidth > availWidth) { michael@0: breakAfter = true; michael@0: } else { michael@0: aLineLayout.NotifyOptionalBreakPosition(mContent, offset + length, michael@0: true, gfxBreakPriority::eNormalBreak); michael@0: } michael@0: } michael@0: michael@0: // Compute reflow status michael@0: aStatus = contentLength == maxContentLength michael@0: ? NS_FRAME_COMPLETE : NS_FRAME_NOT_COMPLETE; michael@0: michael@0: if (charsFit == 0 && length > 0 && !usedHyphenation) { michael@0: // Couldn't place any text michael@0: aStatus = NS_INLINE_LINE_BREAK_BEFORE(); michael@0: } else if (contentLength > 0 && mContentOffset + contentLength - 1 == newLineOffset) { michael@0: // Ends in \n michael@0: aStatus = NS_INLINE_LINE_BREAK_AFTER(aStatus); michael@0: aLineLayout.SetLineEndsInBR(true); michael@0: } else if (breakAfter) { michael@0: aStatus = NS_INLINE_LINE_BREAK_AFTER(aStatus); michael@0: } michael@0: if (completedFirstLetter) { michael@0: aLineLayout.SetFirstLetterStyleOK(false); michael@0: aStatus |= NS_INLINE_BREAK_FIRST_LETTER_COMPLETE; michael@0: } michael@0: michael@0: // Updated the cached NewlineProperty, or delete it. michael@0: if (contentLength < maxContentLength && michael@0: textStyle->NewlineIsSignificant() && michael@0: (contentNewLineOffset < 0 || michael@0: mContentOffset + contentLength <= contentNewLineOffset)) { michael@0: if (!cachedNewlineOffset) { michael@0: cachedNewlineOffset = new NewlineProperty; michael@0: if (NS_FAILED(mContent->SetProperty(nsGkAtoms::newline, cachedNewlineOffset, michael@0: nsINode::DeleteProperty))) { michael@0: delete cachedNewlineOffset; michael@0: cachedNewlineOffset = nullptr; michael@0: } michael@0: } michael@0: if (cachedNewlineOffset) { michael@0: cachedNewlineOffset->mStartOffset = offset; michael@0: cachedNewlineOffset->mNewlineOffset = contentNewLineOffset; michael@0: } michael@0: } else if (cachedNewlineOffset) { michael@0: mContent->DeleteProperty(nsGkAtoms::newline); michael@0: } michael@0: michael@0: // Compute space and letter counts for justification, if required michael@0: if (!textStyle->WhiteSpaceIsSignificant() && michael@0: (lineContainer->StyleText()->mTextAlign == NS_STYLE_TEXT_ALIGN_JUSTIFY || michael@0: lineContainer->StyleText()->mTextAlignLast == NS_STYLE_TEXT_ALIGN_JUSTIFY) && michael@0: !lineContainer->IsSVGText()) { michael@0: AddStateBits(TEXT_JUSTIFICATION_ENABLED); // This will include a space for trailing whitespace, if any is present. michael@0: // This is corrected for in nsLineLayout::TrimWhiteSpaceIn. michael@0: int32_t numJustifiableCharacters = michael@0: provider.ComputeJustifiableCharacters(offset, charsFit); michael@0: michael@0: NS_ASSERTION(numJustifiableCharacters <= charsFit, michael@0: "Bad justifiable character count"); michael@0: aLineLayout.SetTextJustificationWeights(numJustifiableCharacters, michael@0: charsFit - numJustifiableCharacters); michael@0: } michael@0: michael@0: SetLength(contentLength, &aLineLayout, ALLOW_FRAME_CREATION_AND_DESTRUCTION); michael@0: michael@0: InvalidateFrame(); michael@0: michael@0: #ifdef NOISY_REFLOW michael@0: ListTag(stdout); michael@0: printf(": desiredSize=%d,%d(b=%d) status=%x\n", michael@0: aMetrics.Width(), aMetrics.Height(), aMetrics.TopAscent(), michael@0: aStatus); michael@0: #endif michael@0: } michael@0: michael@0: /* virtual */ bool michael@0: nsTextFrame::CanContinueTextRun() const michael@0: { michael@0: // We can continue a text run through a text frame michael@0: return true; michael@0: } michael@0: michael@0: nsTextFrame::TrimOutput michael@0: nsTextFrame::TrimTrailingWhiteSpace(nsRenderingContext* aRC) michael@0: { michael@0: TrimOutput result; michael@0: result.mChanged = false; michael@0: result.mLastCharIsJustifiable = false; michael@0: result.mDeltaWidth = 0; michael@0: michael@0: AddStateBits(TEXT_END_OF_LINE); michael@0: michael@0: int32_t contentLength = GetContentLength(); michael@0: if (!contentLength) michael@0: return result; michael@0: michael@0: gfxContext* ctx = aRC->ThebesContext(); michael@0: gfxSkipCharsIterator start = michael@0: EnsureTextRun(nsTextFrame::eInflated, ctx); michael@0: NS_ENSURE_TRUE(mTextRun, result); michael@0: michael@0: uint32_t trimmedStart = start.GetSkippedOffset(); michael@0: michael@0: const nsTextFragment* frag = mContent->GetText(); michael@0: TrimmedOffsets trimmed = GetTrimmedOffsets(frag, true); michael@0: gfxSkipCharsIterator trimmedEndIter = start; michael@0: const nsStyleText* textStyle = StyleText(); michael@0: gfxFloat delta = 0; michael@0: uint32_t trimmedEnd = trimmedEndIter.ConvertOriginalToSkipped(trimmed.GetEnd()); michael@0: michael@0: if (GetStateBits() & TEXT_TRIMMED_TRAILING_WHITESPACE) { michael@0: // We pre-trimmed this frame, so the last character is justifiable michael@0: result.mLastCharIsJustifiable = true; michael@0: } else if (trimmed.GetEnd() < GetContentEnd()) { michael@0: gfxSkipCharsIterator end = trimmedEndIter; michael@0: uint32_t endOffset = end.ConvertOriginalToSkipped(GetContentOffset() + contentLength); michael@0: if (trimmedEnd < endOffset) { michael@0: // We can't be dealing with tabs here ... they wouldn't be trimmed. So it's michael@0: // OK to pass null for the line container. michael@0: PropertyProvider provider(mTextRun, textStyle, frag, this, start, contentLength, michael@0: nullptr, 0, nsTextFrame::eInflated); michael@0: delta = mTextRun->GetAdvanceWidth(trimmedEnd, endOffset - trimmedEnd, &provider); michael@0: // non-compressed whitespace being skipped at end of line -> justifiable michael@0: // XXX should we actually *count* justifiable characters that should be michael@0: // removed from the overall count? I think so... michael@0: result.mLastCharIsJustifiable = true; michael@0: result.mChanged = true; michael@0: } michael@0: } michael@0: michael@0: if (!result.mLastCharIsJustifiable && michael@0: (GetStateBits() & TEXT_JUSTIFICATION_ENABLED)) { michael@0: // Check if any character in the last cluster is justifiable michael@0: PropertyProvider provider(mTextRun, textStyle, frag, this, start, contentLength, michael@0: nullptr, 0, nsTextFrame::eInflated); michael@0: bool isCJK = IsChineseOrJapanese(this); michael@0: gfxSkipCharsIterator justificationStart(start), justificationEnd(trimmedEndIter); michael@0: provider.FindJustificationRange(&justificationStart, &justificationEnd); michael@0: michael@0: for (int32_t i = justificationEnd.GetOriginalOffset(); michael@0: i < trimmed.GetEnd(); ++i) { michael@0: if (IsJustifiableCharacter(frag, i, isCJK)) { michael@0: result.mLastCharIsJustifiable = true; michael@0: } michael@0: } michael@0: } michael@0: michael@0: gfxFloat advanceDelta; michael@0: mTextRun->SetLineBreaks(trimmedStart, trimmedEnd - trimmedStart, michael@0: (GetStateBits() & TEXT_START_OF_LINE) != 0, true, michael@0: &advanceDelta, ctx); michael@0: if (advanceDelta != 0) { michael@0: result.mChanged = true; michael@0: } michael@0: michael@0: // aDeltaWidth is *subtracted* from our width. michael@0: // If advanceDelta is positive then setting the line break made us longer, michael@0: // so aDeltaWidth could go negative. michael@0: result.mDeltaWidth = NSToCoordFloor(delta - advanceDelta); michael@0: // If aDeltaWidth goes negative, that means this frame might not actually fit michael@0: // anymore!!! We need higher level line layout to recover somehow. michael@0: // If it's because the frame has a soft hyphen that is now being displayed, michael@0: // this should actually be OK, because our reflow recorded the break michael@0: // opportunity that allowed the soft hyphen to be used, and we wouldn't michael@0: // have recorded the opportunity unless the hyphen fit (or was the first michael@0: // opportunity on the line). michael@0: // Otherwise this can/ really only happen when we have glyphs with special michael@0: // shapes at the end of lines, I think. Breaking inside a kerning pair won't michael@0: // do it because that would mean we broke inside this textrun, and michael@0: // BreakAndMeasureText should make sure the resulting shaped substring fits. michael@0: // Maybe if we passed a maxTextLength? But that only happens at direction michael@0: // changes (so we wouldn't kern across the boundary) or for first-letter michael@0: // (which always fits because it starts the line!). michael@0: NS_WARN_IF_FALSE(result.mDeltaWidth >= 0, michael@0: "Negative deltawidth, something odd is happening"); michael@0: michael@0: #ifdef NOISY_TRIM michael@0: ListTag(stdout); michael@0: printf(": trim => %d\n", result.mDeltaWidth); michael@0: #endif michael@0: return result; michael@0: } michael@0: michael@0: nsOverflowAreas michael@0: nsTextFrame::RecomputeOverflow(const nsHTMLReflowState& aBlockReflowState) michael@0: { michael@0: nsRect bounds(nsPoint(0, 0), GetSize()); michael@0: nsOverflowAreas result(bounds, bounds); michael@0: michael@0: gfxSkipCharsIterator iter = EnsureTextRun(nsTextFrame::eInflated); michael@0: if (!mTextRun) michael@0: return result; michael@0: michael@0: PropertyProvider provider(this, iter, nsTextFrame::eInflated); michael@0: provider.InitializeForDisplay(true); michael@0: michael@0: gfxTextRun::Metrics textMetrics = michael@0: mTextRun->MeasureText(provider.GetStart().GetSkippedOffset(), michael@0: ComputeTransformedLength(provider), michael@0: gfxFont::LOOSE_INK_EXTENTS, nullptr, michael@0: &provider); michael@0: nsRect &vis = result.VisualOverflow(); michael@0: vis.UnionRect(vis, RoundOut(textMetrics.mBoundingBox) + nsPoint(0, mAscent)); michael@0: UnionAdditionalOverflow(PresContext(), aBlockReflowState, provider, michael@0: &vis, true); michael@0: return result; michael@0: } michael@0: static char16_t TransformChar(const nsStyleText* aStyle, gfxTextRun* aTextRun, michael@0: uint32_t aSkippedOffset, char16_t aChar) michael@0: { michael@0: if (aChar == '\n') { michael@0: return aStyle->NewlineIsSignificant() || aStyle->NewlineIsDiscarded() ? michael@0: aChar : ' '; michael@0: } michael@0: switch (aStyle->mTextTransform) { michael@0: case NS_STYLE_TEXT_TRANSFORM_LOWERCASE: michael@0: aChar = ToLowerCase(aChar); michael@0: break; michael@0: case NS_STYLE_TEXT_TRANSFORM_UPPERCASE: michael@0: aChar = ToUpperCase(aChar); michael@0: break; michael@0: case NS_STYLE_TEXT_TRANSFORM_CAPITALIZE: michael@0: if (aTextRun->CanBreakLineBefore(aSkippedOffset)) { michael@0: aChar = ToTitleCase(aChar); michael@0: } michael@0: break; michael@0: } michael@0: michael@0: return aChar; michael@0: } michael@0: michael@0: nsresult nsTextFrame::GetRenderedText(nsAString* aAppendToString, michael@0: gfxSkipChars* aSkipChars, michael@0: gfxSkipCharsIterator* aSkipIter, michael@0: uint32_t aSkippedStartOffset, michael@0: uint32_t aSkippedMaxLength) michael@0: { michael@0: // The handling of aSkippedStartOffset and aSkippedMaxLength could be more efficient... michael@0: gfxSkipChars skipChars; michael@0: nsTextFrame* textFrame; michael@0: const nsTextFragment* textFrag = mContent->GetText(); michael@0: uint32_t keptCharsLength = 0; michael@0: uint32_t validCharsLength = 0; michael@0: michael@0: // Build skipChars and copy text, for each text frame in this continuation block michael@0: for (textFrame = this; textFrame; michael@0: textFrame = static_cast(textFrame->GetNextContinuation())) { michael@0: // For each text frame continuation in this block ... michael@0: michael@0: if (textFrame->GetStateBits() & NS_FRAME_IS_DIRTY) { michael@0: // We don't trust dirty frames, expecially when computing rendered text. michael@0: break; michael@0: } michael@0: michael@0: // Ensure the text run and grab the gfxSkipCharsIterator for it michael@0: gfxSkipCharsIterator iter = michael@0: textFrame->EnsureTextRun(nsTextFrame::eInflated); michael@0: if (!textFrame->mTextRun) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: // Skip to the start of the text run, past ignored chars at start of line michael@0: // XXX In the future we may decide to trim extra spaces before a hard line michael@0: // break, in which case we need to accurately detect those sitations and michael@0: // call GetTrimmedOffsets() with true to trim whitespace at the line's end michael@0: TrimmedOffsets trimmedContentOffsets = textFrame->GetTrimmedOffsets(textFrag, false); michael@0: int32_t startOfLineSkipChars = trimmedContentOffsets.mStart - textFrame->mContentOffset; michael@0: if (startOfLineSkipChars > 0) { michael@0: skipChars.SkipChars(startOfLineSkipChars); michael@0: iter.SetOriginalOffset(trimmedContentOffsets.mStart); michael@0: } michael@0: michael@0: // Keep and copy the appropriate chars withing the caller's requested range michael@0: const nsStyleText* textStyle = textFrame->StyleText(); michael@0: while (iter.GetOriginalOffset() < trimmedContentOffsets.GetEnd() && michael@0: keptCharsLength < aSkippedMaxLength) { michael@0: // For each original char from content text michael@0: if (iter.IsOriginalCharSkipped() || ++validCharsLength <= aSkippedStartOffset) { michael@0: skipChars.SkipChar(); michael@0: } else { michael@0: ++keptCharsLength; michael@0: skipChars.KeepChar(); michael@0: if (aAppendToString) { michael@0: aAppendToString->Append( michael@0: TransformChar(textStyle, textFrame->mTextRun, iter.GetSkippedOffset(), michael@0: textFrag->CharAt(iter.GetOriginalOffset()))); michael@0: } michael@0: } michael@0: iter.AdvanceOriginal(1); michael@0: } michael@0: if (keptCharsLength >= aSkippedMaxLength) { michael@0: break; // Already past the end, don't build string or gfxSkipCharsIter anymore michael@0: } michael@0: } michael@0: michael@0: if (aSkipChars) { michael@0: aSkipChars->TakeFrom(&skipChars); // Copy skipChars into aSkipChars michael@0: if (aSkipIter) { michael@0: // Caller must provide both pointers in order to retrieve a gfxSkipCharsIterator, michael@0: // because the gfxSkipCharsIterator holds a weak pointer to the gfxSkipChars. michael@0: *aSkipIter = gfxSkipCharsIterator(*aSkipChars, GetContentLength()); michael@0: } michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsIAtom* michael@0: nsTextFrame::GetType() const michael@0: { michael@0: return nsGkAtoms::textFrame; michael@0: } michael@0: michael@0: /* virtual */ bool michael@0: nsTextFrame::IsEmpty() michael@0: { michael@0: NS_ASSERTION(!(mState & TEXT_IS_ONLY_WHITESPACE) || michael@0: !(mState & TEXT_ISNOT_ONLY_WHITESPACE), michael@0: "Invalid state"); michael@0: michael@0: // XXXldb Should this check compatibility mode as well??? michael@0: const nsStyleText* textStyle = StyleText(); michael@0: if (textStyle->WhiteSpaceIsSignificant() && michael@0: textStyle->mWhiteSpace != NS_STYLE_WHITESPACE_PRE_DISCARD_NEWLINES) { michael@0: // XXX shouldn't we return true if the length is zero? michael@0: return false; michael@0: } michael@0: michael@0: if (mState & TEXT_ISNOT_ONLY_WHITESPACE) { michael@0: return false; michael@0: } michael@0: michael@0: if (mState & TEXT_IS_ONLY_WHITESPACE) { michael@0: return true; michael@0: } michael@0: michael@0: bool isEmpty = michael@0: textStyle->mWhiteSpace == NS_STYLE_WHITESPACE_PRE_DISCARD_NEWLINES ? michael@0: IsAllNewlines(mContent->GetText()) : michael@0: IsAllWhitespace(mContent->GetText(), michael@0: textStyle->mWhiteSpace != NS_STYLE_WHITESPACE_PRE_LINE); michael@0: mState |= (isEmpty ? TEXT_IS_ONLY_WHITESPACE : TEXT_ISNOT_ONLY_WHITESPACE); michael@0: return isEmpty; michael@0: } michael@0: michael@0: #ifdef DEBUG_FRAME_DUMP michael@0: // Translate the mapped content into a string that's printable michael@0: void michael@0: nsTextFrame::ToCString(nsCString& aBuf, int32_t* aTotalContentLength) const michael@0: { michael@0: // Get the frames text content michael@0: const nsTextFragment* frag = mContent->GetText(); michael@0: if (!frag) { michael@0: return; michael@0: } michael@0: michael@0: // Compute the total length of the text content. michael@0: *aTotalContentLength = frag->GetLength(); michael@0: michael@0: int32_t contentLength = GetContentLength(); michael@0: // Set current fragment and current fragment offset michael@0: if (0 == contentLength) { michael@0: return; michael@0: } michael@0: int32_t fragOffset = GetContentOffset(); michael@0: int32_t n = fragOffset + contentLength; michael@0: while (fragOffset < n) { michael@0: char16_t ch = frag->CharAt(fragOffset++); michael@0: if (ch == '\r') { michael@0: aBuf.AppendLiteral("\\r"); michael@0: } else if (ch == '\n') { michael@0: aBuf.AppendLiteral("\\n"); michael@0: } else if (ch == '\t') { michael@0: aBuf.AppendLiteral("\\t"); michael@0: } else if ((ch < ' ') || (ch >= 127)) { michael@0: aBuf.Append(nsPrintfCString("\\u%04x", ch)); michael@0: } else { michael@0: aBuf.Append(ch); michael@0: } michael@0: } michael@0: } michael@0: michael@0: nsresult michael@0: nsTextFrame::GetFrameName(nsAString& aResult) const michael@0: { michael@0: MakeFrameName(NS_LITERAL_STRING("Text"), aResult); michael@0: int32_t totalContentLength; michael@0: nsAutoCString tmp; michael@0: ToCString(tmp, &totalContentLength); michael@0: tmp.SetLength(std::min(tmp.Length(), 50u)); michael@0: aResult += NS_LITERAL_STRING("\"") + NS_ConvertASCIItoUTF16(tmp) + NS_LITERAL_STRING("\""); michael@0: return NS_OK; michael@0: } michael@0: michael@0: void michael@0: nsTextFrame::List(FILE* out, const char* aPrefix, uint32_t aFlags) const michael@0: { michael@0: nsCString str; michael@0: ListGeneric(str, aPrefix, aFlags); michael@0: michael@0: str += nsPrintfCString(" [run=%p]", static_cast(mTextRun)); michael@0: michael@0: // Output the first/last content offset and prev/next in flow info michael@0: bool isComplete = uint32_t(GetContentEnd()) == GetContent()->TextLength(); michael@0: str += nsPrintfCString("[%d,%d,%c] ", GetContentOffset(), GetContentLength(), michael@0: isComplete ? 'T':'F'); michael@0: michael@0: if (IsSelected()) { michael@0: str += " SELECTED"; michael@0: } michael@0: fprintf_stderr(out, "%s\n", str.get()); michael@0: } michael@0: #endif michael@0: michael@0: #ifdef DEBUG michael@0: nsFrameState michael@0: nsTextFrame::GetDebugStateBits() const michael@0: { michael@0: // mask out our emptystate flags; those are just caches michael@0: return nsFrame::GetDebugStateBits() & michael@0: ~(TEXT_WHITESPACE_FLAGS | TEXT_REFLOW_FLAGS); michael@0: } michael@0: #endif michael@0: michael@0: void michael@0: nsTextFrame::AdjustOffsetsForBidi(int32_t aStart, int32_t aEnd) michael@0: { michael@0: AddStateBits(NS_FRAME_IS_BIDI); michael@0: mContent->DeleteProperty(nsGkAtoms::flowlength); michael@0: michael@0: /* michael@0: * After Bidi resolution we may need to reassign text runs. michael@0: * This is called during bidi resolution from the block container, so we michael@0: * shouldn't be holding a local reference to a textrun anywhere. michael@0: */ michael@0: ClearTextRuns(); michael@0: michael@0: nsTextFrame* prev = static_cast(GetPrevContinuation()); michael@0: if (prev) { michael@0: // the bidi resolver can be very evil when columns/pages are involved. Don't michael@0: // let it violate our invariants. michael@0: int32_t prevOffset = prev->GetContentOffset(); michael@0: aStart = std::max(aStart, prevOffset); michael@0: aEnd = std::max(aEnd, prevOffset); michael@0: prev->ClearTextRuns(); michael@0: } michael@0: michael@0: mContentOffset = aStart; michael@0: SetLength(aEnd - aStart, nullptr, 0); michael@0: michael@0: /** michael@0: * After inserting text the caret Bidi level must be set to the level of the michael@0: * inserted text.This is difficult, because we cannot know what the level is michael@0: * until after the Bidi algorithm is applied to the whole paragraph. michael@0: * michael@0: * So we set the caret Bidi level to UNDEFINED here, and the caret code will michael@0: * set it correctly later michael@0: */ michael@0: nsRefPtr frameSelection = GetFrameSelection(); michael@0: if (frameSelection) { michael@0: frameSelection->UndefineCaretBidiLevel(); michael@0: } michael@0: } michael@0: michael@0: /** michael@0: * @return true if this text frame ends with a newline character. It should return michael@0: * false if it is not a text frame. michael@0: */ michael@0: bool michael@0: nsTextFrame::HasSignificantTerminalNewline() const michael@0: { michael@0: return ::HasTerminalNewline(this) && StyleText()->NewlineIsSignificant(); michael@0: } michael@0: michael@0: bool michael@0: nsTextFrame::IsAtEndOfLine() const michael@0: { michael@0: return (GetStateBits() & TEXT_END_OF_LINE) != 0; michael@0: } michael@0: michael@0: nscoord michael@0: nsTextFrame::GetBaseline() const michael@0: { michael@0: return mAscent; michael@0: } michael@0: michael@0: bool michael@0: nsTextFrame::HasAnyNoncollapsedCharacters() michael@0: { michael@0: gfxSkipCharsIterator iter = EnsureTextRun(nsTextFrame::eInflated); michael@0: int32_t offset = GetContentOffset(), michael@0: offsetEnd = GetContentEnd(); michael@0: int32_t skippedOffset = iter.ConvertOriginalToSkipped(offset); michael@0: int32_t skippedOffsetEnd = iter.ConvertOriginalToSkipped(offsetEnd); michael@0: return skippedOffset != skippedOffsetEnd; michael@0: }