tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

TextOverflow.h (13329B)


      1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
      2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
      3 /* This Source Code Form is subject to the terms of the Mozilla Public
      4 * License, v. 2.0. If a copy of the MPL was not distributed with this
      5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      6 
      7 #ifndef TextOverflow_h_
      8 #define TextOverflow_h_
      9 
     10 #include <algorithm>
     11 
     12 #include "mozilla/Likely.h"
     13 #include "mozilla/WritingModes.h"
     14 #include "nsDisplayList.h"
     15 #include "nsTHashSet.h"
     16 
     17 class nsBlockFrame;
     18 class nsLineBox;
     19 
     20 namespace mozilla {
     21 class ScrollContainerFrame;
     22 
     23 namespace css {
     24 
     25 /**
     26 * A class for rendering CSS3 text-overflow.
     27 * Usage:
     28 *  1. allocate an object using WillProcessLines
     29 *  2. then call ProcessLine for each line you are building display lists for
     30 *
     31 * Note that this class is non-reassignable; we don't want to be making
     32 * arbitrary copies. (But we do have a move constructor, since that's required
     33 * in order to be stored in Maybe<>).
     34 */
     35 class TextOverflow final {
     36 private:
     37  /**
     38   * Private constructor, for internal use only. Client code should call
     39   * WillProcessLines(), which is basically the factory function for
     40   * TextOverflow instances.
     41   */
     42  TextOverflow(nsDisplayListBuilder* aBuilder, nsBlockFrame*);
     43 
     44 public:
     45  ~TextOverflow() = default;
     46 
     47  /**
     48   * Allocate an object for text-overflow processing. (Factory function.)
     49   * @return nullptr if no processing is necessary.  The caller owns the object.
     50   */
     51  static Maybe<TextOverflow> WillProcessLines(nsDisplayListBuilder* aBuilder,
     52                                              nsBlockFrame*);
     53 
     54  /**
     55   * This is a factory-constructed non-reassignable class, so we delete nearly
     56   * all constructors and reassignment operators.  We only provide a
     57   * move-constructor, because that's required for Maybe<TextOverflow> to work
     58   * (and that's what our factory method returns).
     59   */
     60  TextOverflow(TextOverflow&&) = default;
     61 
     62  TextOverflow() = delete;
     63  TextOverflow(const TextOverflow&) = delete;
     64  TextOverflow& operator=(const TextOverflow&) = delete;
     65  TextOverflow& operator=(TextOverflow&&) = delete;
     66 
     67  /**
     68   * Analyze the display lists for text overflow and what kind of item is at
     69   * the content edges.  Add display items for text-overflow markers as needed
     70   * and remove or clip items that would overlap a marker.
     71   */
     72  void ProcessLine(const nsDisplayListSet& aLists, nsLineBox* aLine,
     73                   uint32_t aLineNumber);
     74 
     75  /**
     76   * Get the resulting text-overflow markers (the list may be empty).
     77   * @return a DisplayList containing any text-overflow markers.
     78   */
     79  nsDisplayList& GetMarkers() { return mMarkerList; }
     80 
     81  // Returns whether aBlockFrame has text-overflow:clip on both sides.
     82  static bool HasClippedTextOverflow(nsIFrame* aBlockFrame);
     83 
     84  // Returns whether aBlockFrame has a block ellipsis on one of its lines.
     85  static bool HasBlockEllipsis(nsIFrame* aBlockFrame);
     86 
     87  // Returns whether the given block frame needs analysis for text overflow.
     88  // The BeforeReflow flag indicates whether we can be faster and more precise
     89  // for line-clamp ellipsis (only returning true iff the block actually uses
     90  // it).
     91  enum class BeforeReflow : bool { No, Yes };
     92  static bool CanHaveOverflowMarkers(nsBlockFrame*,
     93                                     BeforeReflow = BeforeReflow::No);
     94 
     95  typedef nsTHashSet<nsIFrame*> FrameHashtable;
     96 
     97 private:
     98  typedef mozilla::WritingMode WritingMode;
     99  typedef mozilla::LogicalRect LogicalRect;
    100 
    101  // Edges to align the IStart and IEnd markers to.
    102  struct AlignmentEdges {
    103    AlignmentEdges()
    104        : mIStart(0), mIEnd(0), mIEndOuter(0), mAssignedInner(false) {}
    105    void AccumulateInner(WritingMode aWM, const LogicalRect& aRect) {
    106      if (MOZ_LIKELY(mAssignedInner)) {
    107        mIStart = std::min(mIStart, aRect.IStart(aWM));
    108        mIEnd = std::max(mIEnd, aRect.IEnd(aWM));
    109      } else {
    110        mIStart = aRect.IStart(aWM);
    111        mIEnd = aRect.IEnd(aWM);
    112        mAssignedInner = true;
    113      }
    114    }
    115    void AccumulateOuter(WritingMode aWM, const LogicalRect& aRect) {
    116      mIEndOuter = std::max(mIEndOuter, aRect.IEnd(aWM));
    117    }
    118    nscoord ISize() { return mIEnd - mIStart; }
    119 
    120    // The outermost edges of all text and atomic inline-level frames that are
    121    // inside the area between the markers.
    122    nscoord mIStart;
    123    nscoord mIEnd;
    124 
    125    // The closest IEnd edge of all text and atomic inline-level frames that
    126    // fall completely before the IStart edge of the content area.  (Used to
    127    // align a block ellipsis when there are no visible frames to align to.)
    128    nscoord mIEndOuter;
    129 
    130    bool mAssignedInner;
    131  };
    132 
    133  struct InnerClipEdges {
    134    InnerClipEdges()
    135        : mIStart(0), mIEnd(0), mAssignedIStart(false), mAssignedIEnd(false) {}
    136    void AccumulateIStart(WritingMode aWM, const LogicalRect& aRect) {
    137      if (MOZ_LIKELY(mAssignedIStart)) {
    138        mIStart = std::max(mIStart, aRect.IStart(aWM));
    139      } else {
    140        mIStart = aRect.IStart(aWM);
    141        mAssignedIStart = true;
    142      }
    143    }
    144    void AccumulateIEnd(WritingMode aWM, const LogicalRect& aRect) {
    145      if (MOZ_LIKELY(mAssignedIEnd)) {
    146        mIEnd = std::min(mIEnd, aRect.IEnd(aWM));
    147      } else {
    148        mIEnd = aRect.IEnd(aWM);
    149        mAssignedIEnd = true;
    150      }
    151    }
    152    nscoord mIStart;
    153    nscoord mIEnd;
    154    bool mAssignedIStart;
    155    bool mAssignedIEnd;
    156  };
    157 
    158  LogicalRect GetLogicalScrollableOverflowRectRelativeToBlock(
    159      nsIFrame* aFrame) const {
    160    return LogicalRect(
    161        mBlockWM,
    162        aFrame->ScrollableOverflowRect() + aFrame->GetOffsetTo(mBlock),
    163        mBlockSize);
    164  }
    165 
    166  /**
    167   * Examines frames on the line to determine whether we should draw a left
    168   * and/or right marker, and if so, which frames should be completely hidden
    169   * and the bounds of what will be displayed between the markers.
    170   * @param aLine the line we're processing
    171   * @param aFramesToHide frames that should have their display items removed
    172   * @param aAlignmentEdges edges the markers will be aligned to, including
    173   *   the outermost edges of all text and atomic inline-level frames that
    174   *   are inside the content area, and the closest IEnd edge of such a frame
    175   *   outside the content area
    176   * @return the area inside which we should add any markers;
    177   *   this is the block's content area narrowed by any floats on this line.
    178   */
    179  LogicalRect ExamineLineFrames(nsLineBox* aLine, FrameHashtable* aFramesToHide,
    180                                AlignmentEdges* aAlignmentEdges);
    181 
    182  /**
    183   * LineHasOverflowingText calls this to analyze edges, both the block's
    184   * content edges and the hypothetical marker edges aligned at the block edges.
    185   * @param aFrame the descendant frame of mBlock that we're analyzing
    186   * @param aContentArea the block's content area
    187   * @param aInsideMarkersArea the rectangle between the markers
    188   * @param aFramesToHide frames that should have their display items removed
    189   * @param aAlignmentEdges edges the markers will be aligned to, including
    190   *   the outermost edges of all text and atomic inline-level frames that
    191   *   are inside the content area, and the closest IEnd edge of such a frame
    192   *   outside the content area
    193   * @param aFoundVisibleTextOrAtomic is set to true if a text or atomic
    194   *   inline-level frame is visible between the marker edges
    195   * @param aClippedMarkerEdges the innermost edges of all text and atomic
    196   *   inline-level frames that are clipped by the current marker width
    197   */
    198  void ExamineFrameSubtree(nsIFrame* aFrame, const LogicalRect& aContentArea,
    199                           const LogicalRect& aInsideMarkersArea,
    200                           FrameHashtable* aFramesToHide,
    201                           AlignmentEdges* aAlignmentEdges,
    202                           bool* aFoundVisibleTextOrAtomic,
    203                           InnerClipEdges* aClippedMarkerEdges);
    204 
    205  /**
    206   * ExamineFrameSubtree calls this to analyze a frame against the hypothetical
    207   * marker edges (aInsideMarkersArea) for text frames and atomic inline-level
    208   * elements.  A text frame adds its extent inside aInsideMarkersArea where
    209   * grapheme clusters are fully visible.  An atomic adds its border box if
    210   * it's fully inside aInsideMarkersArea, otherwise the frame is added to
    211   * aFramesToHide.
    212   * @param aFrame the descendant frame of mBlock that we're analyzing
    213   * @param aFrameType aFrame's frame type
    214   * @param aInsideMarkersArea the rectangle between the markers
    215   * @param aFramesToHide frames that should have their display items removed
    216   * @param aAlignmentEdges the outermost edges of all text and atomic
    217   *   inline-level frames that are inside the area between the markers
    218   *                       inside aInsideMarkersArea
    219   * @param aAlignmentEdges edges the markers will be aligned to, including
    220   *   the outermost edges of all text and atomic inline-level frames that
    221   *   are inside aInsideMarkersArea, and the closest IEnd edge of such a frame
    222   *   outside the content area
    223   * @param aFoundVisibleTextOrAtomic is set to true if a text or atomic
    224   *   inline-level frame is visible between the marker edges
    225   * @param aClippedMarkerEdges the innermost edges of all text and atomic
    226   *   inline-level frames that are clipped by the current marker width
    227   */
    228  void AnalyzeMarkerEdges(nsIFrame* aFrame, mozilla::LayoutFrameType aFrameType,
    229                          const LogicalRect& aInsideMarkersArea,
    230                          FrameHashtable* aFramesToHide,
    231                          AlignmentEdges* aAlignmentEdges,
    232                          bool* aFoundVisibleTextOrAtomic,
    233                          InnerClipEdges* aClippedMarkerEdges);
    234 
    235  /**
    236   * Clip or remove items given the final marker edges. ("clip" here just means
    237   * assigning mVisIStartEdge/mVisIEndEdge for any nsCharClipDisplayItem that
    238   * needs it; see nsDisplayList.h for a description of that item).
    239   * @param aFramesToHide remove display items for these frames
    240   * @param aInsideMarkersArea is the area inside the markers
    241   */
    242  void PruneDisplayListContents(nsDisplayList* aList,
    243                                const FrameHashtable& aFramesToHide,
    244                                const LogicalRect& aInsideMarkersArea);
    245 
    246  /**
    247   * ProcessLine calls this to create display items for the markers and insert
    248   * them into mMarkerList.
    249   * @param aLine the line we're processing
    250   * @param aCreateIStart if true, create a marker on the inline start side
    251   * @param aCreateIEnd if true, create a marker on the inline end side
    252   * @param aInsideMarkersArea is the area inside the markers
    253   * @param aContentArea is the area inside which we should add the markers;
    254   *   this is the block's content area narrowed by any floats on this line.
    255   */
    256  void CreateMarkers(const nsLineBox* aLine, bool aCreateIStart,
    257                     bool aCreateIEnd, const LogicalRect& aInsideMarkersArea,
    258                     const LogicalRect& aContentArea, uint32_t aLineNumber);
    259 
    260  LogicalRect mContentArea;
    261  nsDisplayListBuilder* mBuilder;
    262  nsIFrame* mBlock;
    263  ScrollContainerFrame* mScrollContainerFrame;
    264  nsDisplayList mMarkerList;
    265  nsSize mBlockSize;
    266  WritingMode mBlockWM;
    267  bool mCanHaveInlineAxisScrollbar;
    268  // When we're in a -webkit-line-clamp context, we should ignore inline-end
    269  // text-overflow markers. See nsBlockFrame::IsInLineClampContext.
    270  const bool mInLineClampContext;
    271  bool mAdjustForPixelSnapping;
    272 
    273  class Marker {
    274   public:
    275    void Init(const StyleTextOverflowSide& aStyle) {
    276      mInitialized = false;
    277      mISize = 0;
    278      mStyle = &aStyle;
    279      mIntrinsicISize = 0;
    280      mHasOverflow = false;
    281      mHasBlockEllipsis = false;
    282      mActive = false;
    283      mEdgeAligned = false;
    284    }
    285 
    286    /**
    287     * Setup the marker string and calculate its size, if not done already.
    288     */
    289    void SetupString(nsIFrame* aFrame);
    290 
    291    bool IsSuppressed(bool aInLineClampContext) const {
    292      if (aInLineClampContext) {
    293        return !mHasBlockEllipsis;
    294      }
    295      return mStyle->IsClip();
    296    }
    297    bool IsNeeded() const { return mHasOverflow || mHasBlockEllipsis; }
    298    void Reset() {
    299      mHasOverflow = false;
    300      mHasBlockEllipsis = false;
    301      mEdgeAligned = false;
    302    }
    303 
    304    // The current width of the marker, the range is [0 .. mIntrinsicISize].
    305    nscoord mISize;
    306    // The intrinsic width of the marker.
    307    nscoord mIntrinsicISize;
    308    // The text-overflow style for this side.  Ignored if we're rendering a
    309    // block ellipsis.
    310    const StyleTextOverflowSide* mStyle;
    311    // True if there is visible overflowing inline content on this side.
    312    bool mHasOverflow;
    313    // True if this side has a block ellipsis (from -webkit-line-clamp).
    314    bool mHasBlockEllipsis;
    315    // True if mISize and mIntrinsicISize have been setup from style.
    316    bool mInitialized;
    317    // True if the style is not text-overflow:clip on this side and the marker
    318    // won't cause the line to become empty.
    319    bool mActive;
    320    // True if this marker is aligned to the edge of the content box, so that
    321    // when scrolling the marker doesn't jump around.
    322    bool mEdgeAligned;
    323  };
    324 
    325  Marker mIStart;  // the inline start marker
    326  Marker mIEnd;    // the inline end marker
    327 };
    328 
    329 }  // namespace css
    330 }  // namespace mozilla
    331 
    332 #endif /* !defined(TextOverflow_h_) */