/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=8 sts=2 et sw=2 tw=80: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #ifndef nsTextFrame_h__ #define nsTextFrame_h__ #include "mozilla/Attributes.h" #include "mozilla/UniquePtr.h" #include "mozilla/dom/Text.h" #include "mozilla/gfx/2D.h" #include "nsIFrame.h" #include "nsISelectionController.h" #include "nsSplittableFrame.h" #include "gfxSkipChars.h" #include "gfxTextRun.h" #include "JustificationUtils.h" // Undo the windows.h damage #if defined(XP_WIN) && defined(DrawText) # undef DrawText #endif class nsTextPaintStyle; class nsLineList_iterator; struct SelectionDetails; class nsTextFragment; namespace mozilla { class SVGContextPaint; class SVGTextFrame; class nsDisplayTextGeometry; class nsDisplayText; } // namespace mozilla class nsTextFrame : public nsIFrame { typedef mozilla::LayoutDeviceRect LayoutDeviceRect; typedef mozilla::SelectionTypeMask SelectionTypeMask; typedef mozilla::SelectionType SelectionType; typedef mozilla::TextRangeStyle TextRangeStyle; typedef mozilla::gfx::DrawTarget DrawTarget; typedef mozilla::gfx::Point Point; typedef mozilla::gfx::Rect Rect; typedef mozilla::gfx::Size Size; typedef gfxTextRun::Range Range; public: enum TextRunType : uint8_t; struct TabWidthStore; /** * An implementation of gfxTextRun::PropertyProvider that computes spacing and * hyphenation based on CSS properties for a text frame. */ class MOZ_STACK_CLASS PropertyProvider final : public gfxTextRun::PropertyProvider { typedef gfxTextRun::Range Range; typedef gfxTextRun::HyphenType HyphenType; typedef mozilla::gfx::DrawTarget DrawTarget; public: /** * Use this constructor for reflow, when we don't know what text is * really mapped by the frame and we have a lot of other data around. * * @param aLength can be INT32_MAX to indicate we cover all the text * associated with aFrame up to where its flow chain ends in the given * textrun. If INT32_MAX is passed, justification and hyphen-related methods * cannot be called, nor can GetOriginalLength(). */ PropertyProvider(gfxTextRun* aTextRun, const nsStyleText* aTextStyle, const nsTextFragment* aFrag, nsTextFrame* aFrame, const gfxSkipCharsIterator& aStart, int32_t aLength, nsIFrame* aLineContainer, nscoord aOffsetFromBlockOriginForTabs, nsTextFrame::TextRunType aWhichTextRun); /** * Use this constructor after the frame has been reflowed and we don't * have other data around. Gets everything from the frame. EnsureTextRun * *must* be called before this!!! */ PropertyProvider(nsTextFrame* aFrame, const gfxSkipCharsIterator& aStart, nsTextFrame::TextRunType aWhichTextRun, nsFontMetrics* aFontMetrics); /** * As above, but assuming we want the inflated text run and associated * metrics. */ PropertyProvider(nsTextFrame* aFrame, const gfxSkipCharsIterator& aStart) : PropertyProvider(aFrame, aStart, nsTextFrame::eInflated, aFrame->InflatedFontMetrics()) {} // Call this after construction if you're not going to reflow the text void InitializeForDisplay(bool aTrimAfter); void InitializeForMeasure(); void GetSpacing(Range aRange, Spacing* aSpacing) const final; gfxFloat GetHyphenWidth() const final; void GetHyphenationBreaks(Range aRange, HyphenType* aBreakBefore) const final; mozilla::StyleHyphens GetHyphensOption() const final { return mTextStyle->mHyphens; } mozilla::gfx::ShapedTextFlags GetShapedTextFlags() const final; already_AddRefed GetDrawTarget() const final; uint32_t GetAppUnitsPerDevUnit() const final { return mTextRun->GetAppUnitsPerDevUnit(); } void GetSpacingInternal(Range aRange, Spacing* aSpacing, bool aIgnoreTabs) const; /** * Compute the justification information in given DOM range, return * justification info and assignments if requested. */ mozilla::JustificationInfo ComputeJustification( Range aRange, nsTArray* aAssignments = nullptr); const nsTextFrame* GetFrame() const { return mFrame; } // This may not be equal to the frame offset/length in because we may have // adjusted for whitespace trimming according to the state bits set in the // frame (for the static provider) const gfxSkipCharsIterator& GetStart() const { return mStart; } // May return INT32_MAX if that was given to the constructor uint32_t GetOriginalLength() const { NS_ASSERTION(mLength != INT32_MAX, "Length not known"); return mLength; } const nsTextFragment* GetFragment() const { return mFrag; } gfxFontGroup* GetFontGroup() const { if (!mFontGroup) { mFontGroup = GetFontMetrics()->GetThebesFontGroup(); } return mFontGroup; } nsFontMetrics* GetFontMetrics() const { if (!mFontMetrics) { InitFontGroupAndFontMetrics(); } return mFontMetrics; } void CalcTabWidths(Range aTransformedRange, gfxFloat aTabWidth) const; gfxFloat MinTabAdvance() const; const gfxSkipCharsIterator& GetEndHint() const { return mTempIterator; } protected: void SetupJustificationSpacing(bool aPostReflow); void InitFontGroupAndFontMetrics() const; const RefPtr mTextRun; mutable gfxFontGroup* mFontGroup; mutable RefPtr mFontMetrics; const nsStyleText* mTextStyle; const nsTextFragment* mFrag; const nsIFrame* mLineContainer; nsTextFrame* mFrame; gfxSkipCharsIterator mStart; // Offset in original and transformed string const gfxSkipCharsIterator mTempIterator; // Either null, or pointing to the frame's TabWidthProperty. mutable nsTextFrame::TabWidthStore* mTabWidths; // How far we've done tab-width calculation; this is ONLY valid when // mTabWidths is nullptr (otherwise rely on mTabWidths->mLimit instead). // It's a DOM offset relative to the current frame's offset. mutable uint32_t mTabWidthsAnalyzedLimit; int32_t mLength; // DOM string length, may be INT32_MAX const gfxFloat mWordSpacing; // space for each whitespace char const gfxFloat mLetterSpacing; // space for each letter mutable gfxFloat mMinTabAdvance; // min advance for char mutable gfxFloat mHyphenWidth; mutable gfxFloat mOffsetFromBlockOriginForTabs; // The values in mJustificationSpacings corresponds to unskipped // characters start from mJustificationArrayStart. uint32_t mJustificationArrayStart; nsTArray mJustificationSpacings; const bool mReflowing; const nsTextFrame::TextRunType mWhichTextRun; }; explicit nsTextFrame(ComputedStyle* aStyle, nsPresContext* aPresContext, ClassID aID = kClassID) : nsIFrame(aStyle, aPresContext, aID) {} NS_DECL_FRAMEARENA_HELPERS(nsTextFrame) friend class nsContinuingTextFrame; // nsQueryFrame NS_DECL_QUERYFRAME NS_DECLARE_FRAME_PROPERTY_DELETABLE(ContinuationsProperty, nsTArray) // nsIFrame void BuildDisplayList(nsDisplayListBuilder* aBuilder, const nsDisplayListSet& aLists) final; void Init(nsIContent* aContent, nsContainerFrame* aParent, nsIFrame* aPrevInFlow) override; void Destroy(DestroyContext&) override; Cursor GetCursor(const nsPoint&) final; nsresult CharacterDataChanged(const CharacterDataChangeInfo&) final; nsTextFrame* FirstContinuation() const override { return const_cast(this); } nsTextFrame* GetPrevContinuation() const override { return nullptr; } nsTextFrame* GetNextContinuation() const final { return mNextContinuation; } void SetNextContinuation(nsIFrame* aNextContinuation) final { NS_ASSERTION(!aNextContinuation || Type() == aNextContinuation->Type(), "setting a next continuation with incorrect type!"); NS_ASSERTION( !nsSplittableFrame::IsInNextContinuationChain(aNextContinuation, this), "creating a loop in continuation chain!"); mNextContinuation = static_cast(aNextContinuation); if (aNextContinuation) aNextContinuation->RemoveStateBits(NS_FRAME_IS_FLUID_CONTINUATION); // Setting a non-fluid continuation might affect our flow length (they're // quite rare so we assume it always does) so we delete our cached value: if (GetContent()->HasFlag(NS_HAS_FLOWLENGTH_PROPERTY)) { GetContent()->RemoveProperty(nsGkAtoms::flowlength); GetContent()->UnsetFlags(NS_HAS_FLOWLENGTH_PROPERTY); } } nsTextFrame* GetNextInFlow() const final { return mNextContinuation && mNextContinuation->HasAnyStateBits( NS_FRAME_IS_FLUID_CONTINUATION) ? mNextContinuation : nullptr; } void SetNextInFlow(nsIFrame* aNextInFlow) final { NS_ASSERTION(!aNextInFlow || Type() == aNextInFlow->Type(), "setting a next in flow with incorrect type!"); NS_ASSERTION( !nsSplittableFrame::IsInNextContinuationChain(aNextInFlow, this), "creating a loop in continuation chain!"); mNextContinuation = static_cast(aNextInFlow); if (mNextContinuation && !mNextContinuation->HasAnyStateBits(NS_FRAME_IS_FLUID_CONTINUATION)) { // Changing from non-fluid to fluid continuation might affect our flow // length, so we delete our cached value: if (GetContent()->HasFlag(NS_HAS_FLOWLENGTH_PROPERTY)) { GetContent()->RemoveProperty(nsGkAtoms::flowlength); GetContent()->UnsetFlags(NS_HAS_FLOWLENGTH_PROPERTY); } } if (aNextInFlow) { aNextInFlow->AddStateBits(NS_FRAME_IS_FLUID_CONTINUATION); } } nsTextFrame* LastInFlow() const final; nsTextFrame* LastContinuation() const final; bool ShouldSuppressLineBreak() const; void InvalidateFrame(uint32_t aDisplayItemKey = 0, bool aRebuildDisplayItems = true) final; void InvalidateFrameWithRect(const nsRect& aRect, uint32_t aDisplayItemKey = 0, bool aRebuildDisplayItems = true) final; #ifdef DEBUG_FRAME_DUMP void List(FILE* out = stderr, const char* aPrefix = "", ListFlags aFlags = ListFlags()) const final; nsresult GetFrameName(nsAString& aResult) const final; void ToCString(nsCString& aBuf) const; void ListTextRuns(FILE* out, nsTHashSet& aSeen) const final; #endif // Returns this text frame's content's text fragment. // // Assertions in Init() ensure we only ever get a Text node as content. const nsTextFragment* TextFragment() const { return &mContent->AsText()->TextFragment(); } /** * Check that the text in this frame is entirely whitespace. Importantly, * this function considers non-breaking spaces (0xa0) to be whitespace, * whereas nsTextFrame::IsEmpty does not. It also considers both one and * two-byte chars. */ bool IsEntirelyWhitespace() const; ContentOffsets CalcContentOffsetsFromFramePoint(const nsPoint& aPoint) final; ContentOffsets GetCharacterOffsetAtFramePoint(const nsPoint& aPoint); /** * This is called only on the primary text frame. It indicates that * the selection state of the given character range has changed. * Frames corresponding to the character range are unconditionally invalidated * (Selection::Repaint depends on this). * @param aStart start of character range. * @param aEnd end (exclusive) of character range. * @param aSelected true iff the character range is now selected. * @param aType the type of the changed selection. */ void SelectionStateChanged(uint32_t aStart, uint32_t aEnd, bool aSelected, SelectionType aSelectionType); FrameSearchResult PeekOffsetNoAmount(bool aForward, int32_t* aOffset) final; FrameSearchResult PeekOffsetCharacter( bool aForward, int32_t* aOffset, PeekOffsetCharacterOptions aOptions = PeekOffsetCharacterOptions()) final; FrameSearchResult PeekOffsetWord(bool aForward, bool aWordSelectEatSpace, bool aIsKeyboardSelect, int32_t* aOffset, PeekWordState* aState, bool aTrimSpaces) final; // Helper method that editor code uses to test for visibility. [[nodiscard]] bool HasVisibleText(); // Flags for aSetLengthFlags enum { ALLOW_FRAME_CREATION_AND_DESTRUCTION = 0x01 }; // Update offsets to account for new length. This may clear mTextRun. void SetLength(int32_t aLength, nsLineLayout* aLineLayout, uint32_t aSetLengthFlags = 0); std::pair GetOffsets() const final; void AdjustOffsetsForBidi(int32_t start, int32_t end) final; nsresult GetPointFromOffset(int32_t inOffset, nsPoint* outPoint) final; nsresult GetCharacterRectsInRange(int32_t aInOffset, int32_t aLength, nsTArray& aRects) final; nsresult GetChildFrameContainingOffset(int32_t inContentOffset, bool inHint, int32_t* outFrameContentOffset, nsIFrame** outChildFrame) final; bool IsEmpty() final; bool IsSelfEmpty() final { return IsEmpty(); } Maybe GetNaturalBaselineBOffset( mozilla::WritingMode aWM, BaselineSharingGroup aBaselineGroup, BaselineExportContext) const override; bool HasSignificantTerminalNewline() const final; /** * Returns true if this text frame is logically adjacent to the end of the * line. */ bool IsAtEndOfLine() const; /** * Call this only after reflow the frame. Returns true if non-collapsed * characters are present. */ bool HasNoncollapsedCharacters() const { return HasAnyStateBits(TEXT_HAS_NONCOLLAPSED_CHARACTERS); } #ifdef ACCESSIBILITY mozilla::a11y::AccType AccessibleType() final; #endif float GetFontSizeInflation() const; bool IsCurrentFontInflation(float aInflation) const; bool HasFontSizeInflation() const { return HasAnyStateBits(TEXT_HAS_FONT_INFLATION); } void SetFontSizeInflation(float aInflation); void MarkIntrinsicISizesDirty() final; nscoord GetMinISize(gfxContext* aRenderingContext) final; nscoord GetPrefISize(gfxContext* aRenderingContext) final; void AddInlineMinISize(gfxContext* aRenderingContext, InlineMinISizeData* aData) override; void AddInlinePrefISize(gfxContext* aRenderingContext, InlinePrefISizeData* aData) override; SizeComputationResult ComputeSize( gfxContext* aRenderingContext, mozilla::WritingMode aWM, const mozilla::LogicalSize& aCBSize, nscoord aAvailableISize, const mozilla::LogicalSize& aMargin, const mozilla::LogicalSize& aBorderPadding, const mozilla::StyleSizeOverrides& aSizeOverrides, mozilla::ComputeSizeFlags aFlags) final; nsRect ComputeTightBounds(DrawTarget* aDrawTarget) const final; nsresult GetPrefWidthTightBounds(gfxContext* aContext, nscoord* aX, nscoord* aXMost) final; void Reflow(nsPresContext* aPresContext, ReflowOutput& aMetrics, const ReflowInput& aReflowInput, nsReflowStatus& aStatus) final; bool CanContinueTextRun() const final; // Method that is called for a text frame that is logically // adjacent to the end of the line (i.e. followed only by empty text frames, // placeholders or inlines containing such). struct TrimOutput { // true if we trimmed some space or changed metrics in some other way. // In this case, we should call RecomputeOverflow on this frame. bool mChanged; // an amount to *subtract* from the frame's width (zero if !mChanged) nscoord mDeltaWidth; }; TrimOutput TrimTrailingWhiteSpace(DrawTarget* aDrawTarget); RenderedText GetRenderedText( uint32_t aStartOffset = 0, uint32_t aEndOffset = UINT32_MAX, TextOffsetType aOffsetType = TextOffsetType::OffsetsInContentText, TrailingWhitespace aTrimTrailingWhitespace = TrailingWhitespace::Trim) final; mozilla::OverflowAreas RecomputeOverflow(nsIFrame* aBlockFrame, bool aIncludeShadows = true); enum TextRunType : uint8_t { // Anything in reflow (but not intrinsic width calculation) or // painting should use the inflated text run (i.e., with font size // inflation applied). eInflated, // Intrinsic width calculation should use the non-inflated text run. // When there is font size inflation, it will be different. eNotInflated }; void AddInlineMinISizeForFlow(gfxContext* aRenderingContext, nsIFrame::InlineMinISizeData* aData, TextRunType aTextRunType); void AddInlinePrefISizeForFlow(gfxContext* aRenderingContext, InlinePrefISizeData* aData, TextRunType aTextRunType); /** * Calculate the horizontal bounds of the grapheme clusters that fit entirely * inside the given left[top]/right[bottom] edges (which are positive lengths * from the respective frame edge). If an input value is zero it is ignored * and the result for that edge is zero. All out parameter values are * undefined when the method returns false. * @return true if at least one whole grapheme cluster fit between the edges */ bool MeasureCharClippedText(nscoord aVisIStartEdge, nscoord aVisIEndEdge, nscoord* aSnappedStartEdge, nscoord* aSnappedEndEdge); /** * Same as above; this method also the returns the corresponding text run * offset and number of characters that fit. All out parameter values are * undefined when the method returns false. * @return true if at least one whole grapheme cluster fit between the edges */ bool MeasureCharClippedText(PropertyProvider& aProvider, nscoord aVisIStartEdge, nscoord aVisIEndEdge, uint32_t* aStartOffset, uint32_t* aMaxLength, nscoord* aSnappedStartEdge, nscoord* aSnappedEndEdge); /** * Return true if this box has some text to display. * It returns false if at least one of these conditions are met: * a. the frame hasn't been reflowed yet * b. GetContentLength() == 0 * c. it contains only non-significant white-space */ bool HasNonSuppressedText() const; /** * Object with various callbacks for PaintText() to invoke for different parts * of the frame's text rendering, when we're generating paths rather than * painting. * * Callbacks are invoked in the following order: * * NotifySelectionBackgroundNeedsFill? * PaintDecorationLine* * NotifyBeforeText * NotifyGlyphPathEmitted* * NotifyAfterText * PaintDecorationLine* * PaintSelectionDecorationLine* * * The color of each part of the frame's text rendering is passed as an * argument to the NotifyBefore* callback for that part. The nscolor can take * on one of the three selection special colors defined in LookAndFeel.h -- * NS_TRANSPARENT, NS_SAME_AS_FOREGROUND_COLOR and * NS_40PERCENT_FOREGROUND_COLOR. */ struct DrawPathCallbacks : gfxTextRunDrawCallbacks { /** * @param aShouldPaintSVGGlyphs Whether SVG glyphs should be painted. */ explicit DrawPathCallbacks(bool aShouldPaintSVGGlyphs = false) : gfxTextRunDrawCallbacks(aShouldPaintSVGGlyphs) {} /** * Called to have the selection highlight drawn before the text is drawn * over the top. */ virtual void NotifySelectionBackgroundNeedsFill(const Rect& aBackgroundRect, nscolor aColor, DrawTarget& aDrawTarget) {} /** * Called before (for under/over-line) or after (for line-through) the text * is drawn to have a text decoration line drawn. */ virtual void PaintDecorationLine(Rect aPath, bool aPaintingShadows, nscolor aColor) {} /** * Called after selected text is drawn to have a decoration line drawn over * the text. (All types of text decoration are drawn after the text when * text is selected.) */ virtual void PaintSelectionDecorationLine(Rect aPath, bool aPaintingShadows, nscolor aColor) {} /** * Called just before any paths have been emitted to the gfxContext * for the glyphs of the frame's text. */ virtual void NotifyBeforeText(bool aPaintingShadows, nscolor aColor) {} /** * Called just after all the paths have been emitted to the gfxContext * for the glyphs of the frame's text. */ virtual void NotifyAfterText() {} /** * Called just before a path corresponding to a selection decoration line * has been emitted to the gfxContext. */ virtual void NotifyBeforeSelectionDecorationLine(nscolor aColor) {} /** * Called just after a path corresponding to a selection decoration line * has been emitted to the gfxContext. */ virtual void NotifySelectionDecorationLinePathEmitted() {} }; struct MOZ_STACK_CLASS PaintTextParams { gfxContext* context; Point framePt; LayoutDeviceRect dirtyRect; mozilla::SVGContextPaint* contextPaint = nullptr; DrawPathCallbacks* callbacks = nullptr; enum { PaintText, // Normal text painting. GenerateTextMask // To generate a mask from a text frame. Should // only paint text itself with opaque color. // Text shadow, text selection color and text // decoration are all discarded in this state. }; uint8_t state = PaintText; explicit PaintTextParams(gfxContext* aContext) : context(aContext) {} bool IsPaintText() const { return state == PaintText; } bool IsGenerateTextMask() const { return state == GenerateTextMask; } }; struct PaintTextSelectionParams; struct DrawTextRunParams; struct DrawTextParams; struct ClipEdges; struct PaintShadowParams; struct PaintDecorationLineParams; struct PriorityOrderedSelectionsForRange { /// List of Selection Details active for the given range. /// Ordered by priority, i.e. the last element has the highest priority. nsTArray mSelectionRanges; Range mRange; }; // Primary frame paint method called from nsDisplayText. Can also be used // to generate paths rather than paint the frame's text by passing a callback // object. The private DrawText() is what applies the text to a graphics // context. void PaintText(const PaintTextParams& aParams, const nscoord aVisIStartEdge, const nscoord aVisIEndEdge, const nsPoint& aToReferenceFrame, const bool aIsSelected, float aOpacity = 1.0f); // helper: paint text frame when we're impacted by at least one selection. // Return false if the text was not painted and we should continue with // the fast path. bool PaintTextWithSelection(const PaintTextSelectionParams& aParams, const ClipEdges& aClipEdges); // helper: paint text with foreground and background colors determined // by selection(s). Also computes a mask of all selection types applying to // our text, returned in aAllSelectionTypeMask. // Return false if the text was not painted and we should continue with // the fast path. bool PaintTextWithSelectionColors( const PaintTextSelectionParams& aParams, const mozilla::UniquePtr& aDetails, SelectionTypeMask* aAllSelectionTypeMask, const ClipEdges& aClipEdges); // helper: paint text decorations for text selected by aSelectionType void PaintTextSelectionDecorations( const PaintTextSelectionParams& aParams, const mozilla::UniquePtr& aDetails, SelectionType aSelectionType); SelectionTypeMask ResolveSelections( const PaintTextSelectionParams& aParams, const SelectionDetails* aDetails, nsTArray& aResult, SelectionType aSelectionType, bool* aAnyBackgrounds = nullptr) const; void DrawEmphasisMarks(gfxContext* aContext, mozilla::WritingMode aWM, const mozilla::gfx::Point& aTextBaselinePt, const mozilla::gfx::Point& aFramePt, Range aRange, const nscolor* aDecorationOverrideColor, PropertyProvider* aProvider); nscolor GetCaretColorAt(int32_t aOffset) final; // @param aSelectionFlags may be multiple of nsISelectionDisplay::DISPLAY_*. // @return nsISelectionController.idl's `getDisplaySelection`. int16_t GetSelectionStatus(int16_t* aSelectionFlags); int32_t GetContentOffset() const { return mContentOffset; } int32_t GetContentLength() const { NS_ASSERTION(GetContentEnd() - mContentOffset >= 0, "negative length"); return GetContentEnd() - mContentOffset; } int32_t GetContentEnd() const; // This returns the length the frame thinks it *should* have after it was // last reflowed (0 if it hasn't been reflowed yet). This should be used only // when setting up the text offsets for a new continuation frame. int32_t GetContentLengthHint() const { return mContentLengthHint; } // Compute the length of the content mapped by this frame // and all its in-flow siblings. Basically this means starting at // mContentOffset and going to the end of the text node or the next bidi // continuation boundary. int32_t GetInFlowContentLength(); /** * Acquires the text run for this content, if necessary. * @param aWhichTextRun indicates whether to get an inflated or non-inflated * text run * @param aRefDrawTarget the DrawTarget to use as a reference for creating the * textrun, if available (if not, we'll create one which will just be slower) * @param aLineContainer the block ancestor for this frame, or nullptr if * unknown * @param aFlowEndInTextRun if non-null, this returns the textrun offset of * end of the text associated with this frame and its in-flow siblings * @return a gfxSkipCharsIterator set up to map DOM offsets for this frame * to offsets into the textrun; its initial offset is set to this frame's * content offset */ gfxSkipCharsIterator EnsureTextRun(TextRunType aWhichTextRun, DrawTarget* aRefDrawTarget = nullptr, nsIFrame* aLineContainer = nullptr, const nsLineList_iterator* aLine = nullptr, uint32_t* aFlowEndInTextRun = nullptr); gfxTextRun* GetTextRun(TextRunType aWhichTextRun) const { if (aWhichTextRun == eInflated || !HasFontSizeInflation()) return mTextRun; return GetUninflatedTextRun(); } gfxTextRun* GetUninflatedTextRun() const; void SetTextRun(gfxTextRun* aTextRun, TextRunType aWhichTextRun, float aInflation); bool IsInTextRunUserData() const { return HasAnyStateBits(TEXT_IN_TEXTRUN_USER_DATA | TEXT_IN_UNINFLATED_TEXTRUN_USER_DATA); } /** * Notify the frame that it should drop its pointer to a text run. * Returns whether the text run was removed (i.e., whether it was * associated with this frame, either as its inflated or non-inflated * text run. */ bool RemoveTextRun(gfxTextRun* aTextRun); /** * Clears out |mTextRun| (or the uninflated text run, when aInflated * is nsTextFrame::eNotInflated and there is inflation) from all frames that * hold a reference to it, starting at |aStartContinuation|, or if it's * nullptr, starting at |this|. Deletes the text run if all references * were cleared and it's not cached. */ void ClearTextRun(nsTextFrame* aStartContinuation, TextRunType aWhichTextRun); void ClearTextRuns() { ClearTextRun(nullptr, nsTextFrame::eInflated); if (HasFontSizeInflation()) { ClearTextRun(nullptr, nsTextFrame::eNotInflated); } } /** * Wipe out references to textrun(s) without deleting the textruns. */ void DisconnectTextRuns(); // Get the DOM content range mapped by this frame after excluding // whitespace subject to start-of-line and end-of-line trimming. // The textrun must have been created before calling this. struct TrimmedOffsets { int32_t mStart; int32_t mLength; int32_t GetEnd() const { return mStart + mLength; } }; enum class TrimmedOffsetFlags : uint8_t { Default = 0, NotPostReflow = 1 << 0, NoTrimAfter = 1 << 1, NoTrimBefore = 1 << 2 }; TrimmedOffsets GetTrimmedOffsets( const nsTextFragment* aFrag, TrimmedOffsetFlags aFlags = TrimmedOffsetFlags::Default) const; // Similar to Reflow(), but for use from nsLineLayout void ReflowText(nsLineLayout& aLineLayout, nscoord aAvailableWidth, DrawTarget* aDrawTarget, ReflowOutput& aMetrics, nsReflowStatus& aStatus); nscoord ComputeLineHeight() const; bool IsFloatingFirstLetterChild() const; bool IsInitialLetterChild() const; bool ComputeCustomOverflow(mozilla::OverflowAreas& aOverflowAreas) final; bool ComputeCustomOverflowInternal(mozilla::OverflowAreas& aOverflowAreas, bool aIncludeShadows); void AssignJustificationGaps(const mozilla::JustificationAssignment& aAssign); mozilla::JustificationAssignment GetJustificationAssignment() const; uint32_t CountGraphemeClusters() const; bool HasAnyNoncollapsedCharacters() final; /** * Call this after you have manually changed the text node contents without * notifying that change. This behaves as if all the text contents changed. * (You should only use this for native anonymous content.) */ void NotifyNativeAnonymousTextnodeChange(uint32_t aOldLength); nsFontMetrics* InflatedFontMetrics() const; nsRect WebRenderBounds(); // Find the continuation (which may be this frame itself) containing the // given offset. Note that this may return null, if the offset is beyond the // text covered by the continuation chain. // (To be used only on the first textframe in the chain.) nsTextFrame* FindContinuationForOffset(int32_t aOffset); void SetHangableISize(nscoord aISize); nscoord GetHangableISize() const; void ClearHangableISize(); void SetTrimmableWS(gfxTextRun::TrimmableWS aTrimmableWS); gfxTextRun::TrimmableWS GetTrimmableWS() const; void ClearTrimmableWS(); protected: virtual ~nsTextFrame(); friend class mozilla::nsDisplayTextGeometry; friend class mozilla::nsDisplayText; mutable RefPtr mFontMetrics; RefPtr mTextRun; nsTextFrame* mNextContinuation = nullptr; // The key invariant here is that mContentOffset never decreases along // a next-continuation chain. And of course mContentOffset is always <= the // the text node's content length, and the mContentOffset for the first frame // is always 0. Furthermore the text mapped by a frame is determined by // GetContentOffset() and GetContentLength()/GetContentEnd(), which get // the length from the difference between this frame's offset and the next // frame's offset, or the text length if there is no next frame. This means // the frames always map the text node without overlapping or leaving any // gaps. int32_t mContentOffset = 0; // This does *not* indicate the length of text currently mapped by the frame; // instead it's a hint saying that this frame *wants* to map this much text // so if we create a new continuation, this is where that continuation should // start. int32_t mContentLengthHint = 0; nscoord mAscent = 0; // Cached selection state. enum class SelectionState : uint8_t { Unknown, Selected, NotSelected, }; mutable SelectionState mIsSelected = SelectionState::Unknown; // Flags used to track whether certain properties are present. // (Public to keep MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS happy.) public: enum class PropertyFlags : uint8_t { // Whether a cached continuations array is present. Continuations = 1 << 0, // Whether a HangableWhitespace property is present. HangableWS = 1 << 1, // Whether a TrimmableWhitespace property is present. TrimmableWS = 2 << 1, }; protected: PropertyFlags mPropertyFlags = PropertyFlags(0); /** * Return true if the frame is part of a Selection. * Helper method to implement the public IsSelected() API. */ bool IsFrameSelected() const final; void InvalidateSelectionState() { mIsSelected = SelectionState::Unknown; } mozilla::UniquePtr GetSelectionDetails(); void UnionAdditionalOverflow(nsPresContext* aPresContext, nsIFrame* aBlock, PropertyProvider& aProvider, nsRect* aInkOverflowRect, bool aIncludeTextDecorations, bool aIncludeShadows); // Update information of emphasis marks, and return the visial // overflow rect of the emphasis marks. nsRect UpdateTextEmphasis(mozilla::WritingMode aWM, PropertyProvider& aProvider); void PaintOneShadow(const PaintShadowParams& aParams, const mozilla::StyleSimpleShadow& aShadowDetails, gfxRect& aBoundingBox, uint32_t aBlurFlags); void PaintShadows(mozilla::Span, const PaintShadowParams& aParams); struct LineDecoration { nsIFrame* mFrame; // This is represents the offset from our baseline to mFrame's baseline; // positive offsets are *above* the baseline and negative offsets below nscoord mBaselineOffset; // This represents the offset from the initial position of the underline const mozilla::LengthPercentageOrAuto mTextUnderlineOffset; // for CSS property text-decoration-thickness, the width refers to the // thickness of the decoration line const mozilla::StyleTextDecorationLength mTextDecorationThickness; nscolor mColor; mozilla::StyleTextDecorationStyle mStyle; // The text-underline-position property; affects the underline offset only // if mTextUnderlineOffset is auto. const mozilla::StyleTextUnderlinePosition mTextUnderlinePosition; LineDecoration(nsIFrame* const aFrame, const nscoord aOff, mozilla::StyleTextUnderlinePosition aUnderlinePosition, const mozilla::LengthPercentageOrAuto& aUnderlineOffset, const mozilla::StyleTextDecorationLength& aDecThickness, const nscolor aColor, const mozilla::StyleTextDecorationStyle aStyle) : mFrame(aFrame), mBaselineOffset(aOff), mTextUnderlineOffset(aUnderlineOffset), mTextDecorationThickness(aDecThickness), mColor(aColor), mStyle(aStyle), mTextUnderlinePosition(aUnderlinePosition) {} LineDecoration(const LineDecoration& aOther) = default; bool operator==(const LineDecoration& aOther) const { return mFrame == aOther.mFrame && mStyle == aOther.mStyle && mColor == aOther.mColor && mBaselineOffset == aOther.mBaselineOffset && mTextUnderlinePosition == aOther.mTextUnderlinePosition && mTextUnderlineOffset == aOther.mTextUnderlineOffset && mTextDecorationThickness == aOther.mTextDecorationThickness; } bool operator!=(const LineDecoration& aOther) const { return !(*this == aOther); } }; struct TextDecorations { AutoTArray mOverlines, mUnderlines, mStrikes; TextDecorations() = default; bool HasDecorationLines() const { return HasUnderline() || HasOverline() || HasStrikeout(); } bool HasUnderline() const { return !mUnderlines.IsEmpty(); } bool HasOverline() const { return !mOverlines.IsEmpty(); } bool HasStrikeout() const { return !mStrikes.IsEmpty(); } bool operator==(const TextDecorations& aOther) const { return mOverlines == aOther.mOverlines && mUnderlines == aOther.mUnderlines && mStrikes == aOther.mStrikes; } bool operator!=(const TextDecorations& aOther) const { return !(*this == aOther); } }; enum TextDecorationColorResolution { eResolvedColors, eUnresolvedColors }; void GetTextDecorations(nsPresContext* aPresContext, TextDecorationColorResolution aColorResolution, TextDecorations& aDecorations); void DrawTextRun(Range aRange, const mozilla::gfx::Point& aTextBaselinePt, const DrawTextRunParams& aParams); void DrawTextRunAndDecorations(Range aRange, const mozilla::gfx::Point& aTextBaselinePt, const DrawTextParams& aParams, const TextDecorations& aDecorations); void DrawText(Range aRange, const mozilla::gfx::Point& aTextBaselinePt, const DrawTextParams& aParams); // Set non empty rect to aRect, it should be overflow rect or frame rect. // If the result rect is larger than the given rect, this returns true. bool CombineSelectionUnderlineRect(nsPresContext* aPresContext, nsRect& aRect); // This sets *aShadows to the appropriate shadows, if any, for the given // type of selection. // If text-shadow was not specified, *aShadows is left untouched. // Note that the returned shadow(s) will only be valid as long as the // textPaintStyle remains in scope. void GetSelectionTextShadow( SelectionType aSelectionType, nsTextPaintStyle& aTextPaintStyle, mozilla::Span* aShadows); /** * Utility methods to paint selection. */ void DrawSelectionDecorations( gfxContext* aContext, const LayoutDeviceRect& aDirtyRect, mozilla::SelectionType aSelectionType, nsTextPaintStyle& aTextPaintStyle, const TextRangeStyle& aRangeStyle, const Point& aPt, gfxFloat aICoordInFrame, gfxFloat aWidth, gfxFloat aAscent, const gfxFont::Metrics& aFontMetrics, DrawPathCallbacks* aCallbacks, bool aVertical, mozilla::StyleTextDecorationLine aDecoration); void PaintDecorationLine(const PaintDecorationLineParams& aParams); /** * ComputeDescentLimitForSelectionUnderline() computes the most far position * where we can put selection underline. * * @return The maximum underline offset from the baseline (positive value * means that the underline can put below the baseline). */ gfxFloat ComputeDescentLimitForSelectionUnderline( nsPresContext* aPresContext, const gfxFont::Metrics& aFontMetrics); /** * This function encapsulates all knowledge of how selections affect * foreground and background colors. * @param aForeground the foreground color to use * @param aBackground the background color to use, or RGBA(0,0,0,0) if no * background should be painted * @return true if the selection affects colors, false otherwise */ static bool GetSelectionTextColors(SelectionType aSelectionType, nsAtom* aHighlightName, nsTextPaintStyle& aTextPaintStyle, const TextRangeStyle& aRangeStyle, nscolor* aForeground, nscolor* aBackground); /** * ComputeSelectionUnderlineHeight() computes selection underline height of * the specified selection type from the font metrics. */ static gfxFloat ComputeSelectionUnderlineHeight( nsPresContext* aPresContext, const gfxFont::Metrics& aFontMetrics, SelectionType aSelectionType); /** * @brief Helper struct which contains selection data such as its details, * range and priority. */ struct SelectionRange { const SelectionDetails* mDetails{nullptr}; gfxTextRun::Range mRange; /// used to determine the order of overlapping selections of the same type. uint32_t mPriority{0}; }; /** * @brief Helper: Extracts a list of `SelectionRange` structs from given * `SelectionDetails` and computes a priority for overlapping selection * ranges. */ static SelectionTypeMask CreateSelectionRangeList( const SelectionDetails* aDetails, SelectionType aSelectionType, const PaintTextSelectionParams& aParams, nsTArray& aSelectionRanges, bool* aAnyBackgrounds); /** * @brief Creates an array of `CombinedSelectionRange`s from given list * of `SelectionRange`s. * Each instance of `CombinedSelectionRange` represents a piece of text with * constant Selections. * * Example: * * Consider this text fragment, [] and () marking selection ranges: * ab[cd(e]f)g * This results in the following array of combined ranges: * - [0]: range: (2, 4), selections: "[]" * - [1]: range: (4, 5), selections: "[]", "()" * - [2]: range: (5, 6), selections: "()" * Depending on the priorities of the ranges, [1] may have a different order * of its ranges. The given example indicates that "()" has a higher priority * than "[]". * * @param aSelectionRanges Array of `SelectionRange` objects. Must be * sorted by the start offset. * @param aCombinedSelectionRanges Out parameter. Returns the constructed * array of combined selection ranges. */ static void CombineSelectionRanges( const nsTArray& aSelectionRanges, nsTArray& aCombinedSelectionRanges); ContentOffsets GetCharacterOffsetAtFramePointInternal( const nsPoint& aPoint, bool aForInsertionPoint); static float GetTextCombineScaleFactor(nsTextFrame* aFrame); void ClearFrameOffsetCache(); void ClearMetrics(ReflowOutput& aMetrics); // Return pointer to an array of all frames in the continuation chain, or // null if we're too short of memory. nsTArray* GetContinuations(); // Clear any cached continuations array; this should be called whenever the // chain is modified. inline void ClearCachedContinuations(); /** * UpdateIteratorFromOffset() updates the iterator from a given offset. * Also, aInOffset may be updated to cluster start if aInOffset isn't * the offset of cluster start. */ void UpdateIteratorFromOffset(const PropertyProvider& aProperties, int32_t& aInOffset, gfxSkipCharsIterator& aIter); nsPoint GetPointFromIterator(const gfxSkipCharsIterator& aIter, PropertyProvider& aProperties); }; MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(nsTextFrame::TrimmedOffsetFlags) MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(nsTextFrame::PropertyFlags) inline void nsTextFrame::ClearCachedContinuations() { MOZ_ASSERT(NS_IsMainThread()); if (mPropertyFlags & PropertyFlags::Continuations) { RemoveProperty(ContinuationsProperty()); mPropertyFlags &= ~PropertyFlags::Continuations; } } #endif