diff options
Diffstat (limited to 'layout/generic/nsTextFrame.h')
-rw-r--r-- | layout/generic/nsTextFrame.h | 1040 |
1 files changed, 1040 insertions, 0 deletions
diff --git a/layout/generic/nsTextFrame.h b/layout/generic/nsTextFrame.h new file mode 100644 index 0000000000..fede0d3e4d --- /dev/null +++ b/layout/generic/nsTextFrame.h @@ -0,0 +1,1040 @@ +/* -*- 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<DrawTarget> 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<mozilla::JustificationAssignment>* 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<gfxTextRun> mTextRun; + mutable gfxFontGroup* mFontGroup; + mutable RefPtr<nsFontMetrics> 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 <tab> char + mutable gfxFloat mHyphenWidth; + mutable gfxFloat mOffsetFromBlockOriginForTabs; + + // The values in mJustificationSpacings corresponds to unskipped + // characters start from mJustificationArrayStart. + uint32_t mJustificationArrayStart; + nsTArray<Spacing> mJustificationSpacings; + + const bool mReflowing; + const nsTextFrame::TextRunType mWhichTextRun; + }; + + explicit nsTextFrame(ComputedStyle* aStyle, nsPresContext* aPresContext, + ClassID aID = kClassID) + : nsIFrame(aStyle, aPresContext, aID), + mNextContinuation(nullptr), + mContentOffset(0), + mContentLengthHint(0), + mAscent(0), + mIsSelected(SelectionState::Unknown) {} + + NS_DECL_FRAMEARENA_HELPERS(nsTextFrame) + + friend class nsContinuingTextFrame; + + // nsQueryFrame + NS_DECL_QUERYFRAME + + NS_DECLARE_FRAME_PROPERTY_DELETABLE(ContinuationsProperty, + nsTArray<nsTextFrame*>) + + // nsIFrame + void BuildDisplayList(nsDisplayListBuilder* aBuilder, + const nsDisplayListSet& aLists) final; + + void Init(nsIContent* aContent, nsContainerFrame* aParent, + nsIFrame* aPrevInFlow) override; + + void Destroy(DestroyContext&) override; + + mozilla::Maybe<Cursor> GetCursor(const nsPoint&) final; + + nsresult CharacterDataChanged(const CharacterDataChangeInfo&) final; + + nsTextFrame* FirstContinuation() const override { + return const_cast<nsTextFrame*>(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<nsTextFrame*>(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<nsTextFrame*>(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 IsFrameOfType(uint32_t aFlags) const final { + // Set the frame state bit for text frames to mark them as replaced. + // XXX kipp: temporary + return nsIFrame::IsFrameOfType( + aFlags & ~(nsIFrame::eReplaced | nsIFrame::eLineParticipant)); + } + + 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<const void*>& 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<int32_t, int32_t> 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<nsRect>& aRects) final; + + nsresult GetChildFrameContainingOffset(int32_t inContentOffset, bool inHint, + int32_t* outFrameContentOffset, + nsIFrame** outChildFrame) final; + + bool IsEmpty() final; + bool IsSelfEmpty() final { return IsEmpty(); } + Maybe<nscoord> 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, 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, nscolor aColor) {} + + /** + * Called just before any paths have been emitted to the gfxContext + * for the glyphs of the frame's text. + */ + virtual void NotifyBeforeText(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 SelectionRange { + const SelectionDetails* mDetails; + gfxTextRun::Range mRange; + uint32_t mPriority; + }; + + // 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<SelectionDetails>& aDetails, + SelectionTypeMask* aAllSelectionTypeMask, const ClipEdges& aClipEdges); + // helper: paint text decorations for text selected by aSelectionType + void PaintTextSelectionDecorations( + const PaintTextSelectionParams& aParams, + const mozilla::UniquePtr<SelectionDetails>& aDetails, + SelectionType aSelectionType); + + SelectionTypeMask ResolveSelections(const PaintTextSelectionParams& aParams, + const SelectionDetails* aDetails, + nsTArray<SelectionRange>& 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<nsFontMetrics> mFontMetrics; + RefPtr<gfxTextRun> mTextRun; + nsTextFrame* mNextContinuation; + // 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; + // 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; + nscoord mAscent; + + // Cached selection state. + enum class SelectionState : uint8_t { + Unknown, + Selected, + NotSelected, + }; + mutable SelectionState mIsSelected; + + // 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<SelectionDetails> 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 mozilla::StyleSimpleShadow>, + 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<LineDecoration, 1> 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<const mozilla::StyleSimpleShadow>* 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, + const 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); + + 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<nsTextFrame*>* 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 |