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