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_) */