/* -*- 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/EventForwards.h"
#include "mozilla/gfx/2D.h"
#include "mozilla/UniquePtr.h"
#include "nsFrame.h"
#include "nsFrameSelection.h"
#include "nsGenericDOMDataNode.h"
#include "nsSplittableFrame.h"
#include "nsLineBox.h"
#include "gfxSkipChars.h"
#include "gfxTextRun.h"
#include "nsDisplayList.h"
#include "JustificationUtils.h"
#include "RubyUtils.h"

// Undo the windows.h damage
#if defined(XP_WIN) && defined(DrawText)
#undef DrawText
#endif

class nsTextPaintStyle;
class PropertyProvider;
struct SelectionDetails;
class nsTextFragment;

class nsDisplayTextGeometry;
class nsDisplayText;

namespace mozilla {
class SVGContextPaint;
};

class nsTextFrame : public nsFrame
{
  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:
  explicit nsTextFrame(nsStyleContext* aContext, ClassID aID = kClassID)
    : nsFrame(aContext, aID)
    , mNextContinuation(nullptr)
    , mContentOffset(0)
    , mContentLengthHint(0)
    , mAscent(0)
  {}

  NS_DECL_FRAMEARENA_HELPERS(nsTextFrame)

  friend class nsContinuingTextFrame;
  friend class nsDisplayTextGeometry;
  friend class nsDisplayText;

  // nsQueryFrame
  NS_DECL_QUERYFRAME

  // nsIFrame
  void BuildDisplayList(nsDisplayListBuilder* aBuilder,
                        const nsDisplayListSet& aLists) override;

  void Init(nsIContent* aContent,
            nsContainerFrame* aParent,
            nsIFrame* aPrevInFlow) override;

  void DestroyFrom(nsIFrame* aDestructRoot, PostDestroyData& aPostDestroyData) override;

  nsresult GetCursor(const nsPoint& aPoint, nsIFrame::Cursor& aCursor) override;

  nsresult CharacterDataChanged(CharacterDataChangeInfo* aInfo) final;

  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()->DeleteProperty(nsGkAtoms::flowlength);
      GetContent()->UnsetFlags(NS_HAS_FLOWLENGTH_PROPERTY);
    }
  }
  nsIFrame* GetNextInFlowVirtual() const override { return GetNextInFlow(); }
  nsTextFrame* GetNextInFlow() const
  {
    return mNextContinuation &&
               (mNextContinuation->GetStateBits() &
                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()->DeleteProperty(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;

  nsSplittableType GetSplittableType() const final
  {
    return NS_FRAME_SPLITTABLE;
  }

  bool IsFrameOfType(uint32_t aFlags) const final
  {
    // Set the frame state bit for text frames to mark them as replaced.
    // XXX kipp: temporary
    return nsFrame::IsFrameOfType(
      aFlags & ~(nsIFrame::eReplaced | nsIFrame::eLineParticipant));
  }

  bool ShouldSuppressLineBreak() const
  {
    // If the parent frame of the text frame is ruby content box, it must
    // suppress line break inside. This check is necessary, because when
    // a whitespace is only contained by pseudo ruby frames, its style
    // context won't have SuppressLineBreak bit set.
    if (mozilla::RubyUtils::IsRubyContentBox(GetParent()->Type())) {
      return true;
    }
    return StyleContext()->ShouldSuppressLineBreak();
  }

  void InvalidateFrame(uint32_t aDisplayItemKey = 0) override;
  void InvalidateFrameWithRect(const nsRect& aRect,
                               uint32_t aDisplayItemKey = 0) override;

#ifdef DEBUG_FRAME_DUMP
  void List(FILE* out = stderr,
            const char* aPrefix = "",
            uint32_t aFlags = 0) const override;
  nsresult GetFrameName(nsAString& aResult) const override;
  void ToCString(nsCString& aBuf, int32_t* aTotalContentLength) const;
#endif

#ifdef DEBUG
  nsFrameState GetDebugStateBits() const override;
#endif

  ContentOffsets CalcContentOffsetsFromFramePoint(nsPoint aPoint) override;
  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.
   * Text in the range is unconditionally invalidated
   * (Selection::Repaint depends on this).
   * @param aSelected true if the selection has been added to the range,
   * false otherwise
   * @param aType the type of selection added or removed
   */
  void SetSelectedRange(uint32_t aStart,
                        uint32_t aEnd,
                        bool aSelected,
                        SelectionType aSelectionType);

  FrameSearchResult PeekOffsetNoAmount(bool aForward,
                                       int32_t* aOffset) override;
  FrameSearchResult
  PeekOffsetCharacter(bool aForward,
                      int32_t* aOffset,
                      PeekOffsetCharacterOptions aOptions =
                        PeekOffsetCharacterOptions()) override;
  FrameSearchResult PeekOffsetWord(bool aForward,
                                   bool aWordSelectEatSpace,
                                   bool aIsKeyboardSelect,
                                   int32_t* aOffset,
                                   PeekWordState* aState) override;

  nsresult CheckVisibility(nsPresContext* aContext,
                           int32_t aStartIndex,
                           int32_t aEndIndex,
                           bool aRecurse,
                           bool* aFinished,
                           bool* _retval) override;

  // 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);

  nsresult GetOffsets(int32_t& start, int32_t& end) const override;

  void AdjustOffsetsForBidi(int32_t start, int32_t end) override;

  nsresult GetPointFromOffset(int32_t inOffset, nsPoint* outPoint) override;
  nsresult GetCharacterRectsInRange(int32_t aInOffset,
                                    int32_t aLength,
                                    nsTArray<nsRect>& aRects) override;

  nsresult GetChildFrameContainingOffset(int32_t inContentOffset,
                                         bool inHint,
                                         int32_t* outFrameContentOffset,
                                         nsIFrame** outChildFrame) override;

  bool IsVisibleInSelection(nsISelection* aSelection) override;

  bool IsEmpty() override;
  bool IsSelfEmpty() override { return IsEmpty(); }
  nscoord GetLogicalBaseline(mozilla::WritingMode aWritingMode) const final;

  bool HasSignificantTerminalNewline() const override;

  /**
   * 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 (GetStateBits() & TEXT_HAS_NONCOLLAPSED_CHARACTERS) != 0;
  }

#ifdef ACCESSIBILITY
  mozilla::a11y::AccType AccessibleType() override;
#endif

  float GetFontSizeInflation() const;
  bool IsCurrentFontInflation(float aInflation) const;
  bool HasFontSizeInflation() const
  {
    return (GetStateBits() & TEXT_HAS_FONT_INFLATION) != 0;
  }
  void SetFontSizeInflation(float aInflation);

  void MarkIntrinsicISizesDirty() override;
  nscoord GetMinISize(gfxContext* aRenderingContext) override;
  nscoord GetPrefISize(gfxContext* aRenderingContext) override;
  void AddInlineMinISize(gfxContext* aRenderingContext,
                         InlineMinISizeData* aData) override;
  void AddInlinePrefISize(gfxContext* aRenderingContext,
                          InlinePrefISizeData* aData) override;
  mozilla::LogicalSize ComputeSize(gfxContext* aRenderingContext,
                                   mozilla::WritingMode aWritingMode,
                                   const mozilla::LogicalSize& aCBSize,
                                   nscoord aAvailableISize,
                                   const mozilla::LogicalSize& aMargin,
                                   const mozilla::LogicalSize& aBorder,
                                   const mozilla::LogicalSize& aPadding,
                                   ComputeSizeFlags aFlags) override;
  nsRect ComputeTightBounds(DrawTarget* aDrawTarget) const override;
  nsresult GetPrefWidthTightBounds(gfxContext* aContext,
                                   nscoord* aX,
                                   nscoord* aXMost) override;
  void Reflow(nsPresContext* aPresContext,
              ReflowOutput& aMetrics,
              const ReflowInput& aReflowInput,
              nsReflowStatus& aStatus) override;
  bool CanContinueTextRun() const override;
  // 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::OFFSETS_IN_CONTENT_TEXT,
    TrailingWhitespace aTrimTrailingWhitespace =
      TrailingWhitespace::TRIM_TRAILING_WHITESPACE) override;

  nsOverflowAreas RecomputeOverflow(nsIFrame* aBlockFrame);

  enum TextRunType
  {
    // 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);

  /**
   * 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 PaintTextParams
  {
    gfxContext* context;
    mozilla::gfx::Point framePt;
    LayoutDeviceRect dirtyRect;
    mozilla::SVGContextPaint* contextPaint = nullptr;
    DrawPathCallbacks* callbacks = nullptr;
    enum
    {
      PaintText,        // Normal text painting.
      PaintTextBGColor, // Only paint background color of the selected text
                        // range in this state.
      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; }
    bool IsPaintBGColor() const { return state == PaintTextBGColor; }
  };

  struct PaintTextSelectionParams : PaintTextParams
  {
    mozilla::gfx::Point textBaselinePt;
    PropertyProvider* provider = nullptr;
    Range contentRange;
    nsTextPaintStyle* textPaintStyle = nullptr;
    explicit PaintTextSelectionParams(const PaintTextParams& aParams)
      : PaintTextParams(aParams)
    {}
  };

  struct DrawTextRunParams
  {
    gfxContext* context;
    PropertyProvider* provider = nullptr;
    gfxFloat* advanceWidth = nullptr;
    mozilla::SVGContextPaint* contextPaint = nullptr;
    DrawPathCallbacks* callbacks = nullptr;
    nscolor textColor = NS_RGBA(0, 0, 0, 0);
    nscolor textStrokeColor = NS_RGBA(0, 0, 0, 0);
    float textStrokeWidth = 0.0f;
    bool drawSoftHyphen = false;
    explicit DrawTextRunParams(gfxContext* aContext)
      : context(aContext)
    {}
  };

  struct DrawTextParams : DrawTextRunParams
  {
    mozilla::gfx::Point framePt;
    LayoutDeviceRect dirtyRect;
    const nsTextPaintStyle* textStyle = nullptr;
    const nsCharClipDisplayItem::ClipEdges* clipEdges = nullptr;
    const nscolor* decorationOverrideColor = nullptr;
    explicit DrawTextParams(gfxContext* aContext)
      : DrawTextRunParams(aContext)
    {}
  };

  // 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 nsCharClipDisplayItem& aItem,
                 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 nsCharClipDisplayItem::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 nsCharClipDisplayItem::ClipEdges& aClipEdges);
  // helper: paint text decorations for text selected by aSelectionType
  void PaintTextSelectionDecorations(const PaintTextSelectionParams& aParams,
                                     const mozilla::UniquePtr<SelectionDetails>& aDetails,
                                     SelectionType aSelectionType);

  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) override;

  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)
  {
    if (aWhichTextRun == eInflated || !HasFontSizeInflation())
      return mTextRun;
    return GetUninflatedTextRun();
  }
  gfxTextRun* GetUninflatedTextRun();
  void SetTextRun(gfxTextRun* aTextRun,
                  TextRunType aWhichTextRun,
                  float aInflation);
  bool IsInTextRunUserData() const
  {
    return GetStateBits() &
           (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; }
  };
  TrimmedOffsets GetTrimmedOffsets(const nsTextFragment* aFrag,
                                   bool aTrimAfter,
                                   bool aPostReflow = true) const;

  // Similar to Reflow(), but for use from nsLineLayout
  void ReflowText(nsLineLayout& aLineLayout,
                  nscoord aAvailableWidth,
                  DrawTarget* aDrawTarget,
                  ReflowOutput& aMetrics,
                  nsReflowStatus& aStatus);

  bool IsFloatingFirstLetterChild() const;

  bool IsInitialLetterChild() const;

  bool ComputeCustomOverflow(nsOverflowAreas& aOverflowAreas) override;

  void AssignJustificationGaps(const mozilla::JustificationAssignment& aAssign);
  mozilla::JustificationAssignment GetJustificationAssignment() const;

  uint32_t CountGraphemeClusters() const;

protected:
  virtual ~nsTextFrame();

  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;

  /**
   * Return true if the frame is part of a Selection.
   * Helper method to implement the public IsSelected() API.
   */
  bool IsFrameSelected() const override;

  mozilla::UniquePtr<SelectionDetails> GetSelectionDetails();

  void UnionAdditionalOverflow(nsPresContext* aPresContext,
                               nsIFrame* aBlock,
                               PropertyProvider& aProvider,
                               nsRect* aVisualOverflowRect,
                               bool aIncludeTextDecorations);

  // Update information of emphasis marks, and return the visial
  // overflow rect of the emphasis marks.
  nsRect UpdateTextEmphasis(mozilla::WritingMode aWM,
                            PropertyProvider& aProvider);

  struct PaintShadowParams
  {
    gfxTextRun::Range range;
    LayoutDeviceRect dirtyRect;
    mozilla::gfx::Point framePt;
    mozilla::gfx::Point textBaselinePt;
    gfxContext* context;
    nscolor foregroundColor = NS_RGBA(0, 0, 0, 0);
    const nsCharClipDisplayItem::ClipEdges* clipEdges = nullptr;
    PropertyProvider* provider = nullptr;
    nscoord leftSideOffset = 0;
    explicit PaintShadowParams(const PaintTextParams& aParams)
      : dirtyRect(aParams.dirtyRect)
      , framePt(aParams.framePt)
      , context(aParams.context)
    {
    }
  };

  void PaintOneShadow(const PaintShadowParams& aParams,
                      nsCSSShadowItem* aShadowDetails,
                      gfxRect& aBoundingBox,
                      uint32_t aBlurFlags);

  void PaintShadows(nsCSSShadowArray* aShadow,
                    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;

    nscolor mColor;
    uint8_t mStyle;

    LineDecoration(nsIFrame* const aFrame,
                   const nscoord aOff,
                   const nscolor aColor,
                   const uint8_t aStyle)
      : mFrame(aFrame)
      , mBaselineOffset(aOff)
      , mColor(aColor)
      , mStyle(aStyle)
    {
    }

    LineDecoration(const LineDecoration& aOther)
      : mFrame(aOther.mFrame)
      , mBaselineOffset(aOther.mBaselineOffset)
      , mColor(aOther.mColor)
      , mStyle(aOther.mStyle)
    {
    }

    bool operator==(const LineDecoration& aOther) const
    {
      return mFrame == aOther.mFrame && mStyle == aOther.mStyle &&
             mColor == aOther.mColor &&
             mBaselineOffset == aOther.mBaselineOffset;
    }

    bool operator!=(const LineDecoration& aOther) const
    {
      return !(*this == aOther);
    }
  };
  struct TextDecorations
  {
    AutoTArray<LineDecoration, 1> mOverlines, mUnderlines, mStrikes;

    TextDecorations() {}

    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);

  /**
   * 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,
                                uint8_t aDecoration);

  struct PaintDecorationLineParams;
  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,
                                     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(
    nsPoint aPoint,
    bool aForInsertionPoint);

  void ClearFrameOffsetCache();

  bool HasAnyNoncollapsedCharacters() override;

  void ClearMetrics(ReflowOutput& aMetrics);

  /**
   * 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);
};

#endif
