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