TextLeafRange.h (15192B)
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* vim: set ts=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 mozilla_a11y_TextLeafRange_h__ 8 #define mozilla_a11y_TextLeafRange_h__ 9 10 #include <stdint.h> 11 12 #include "AccAttributes.h" 13 #include "nsDirection.h" 14 #include "nsIAccessibleText.h" 15 #include "mozilla/FunctionRef.h" 16 17 namespace mozilla { 18 namespace dom { 19 class AbstractRange; 20 class Document; 21 } // namespace dom 22 23 namespace a11y { 24 class Accessible; 25 class LocalAccessible; 26 27 /** 28 * Represents a point within accessible text. 29 * This is stored as a leaf Accessible and an offset into that Accessible. 30 * For an empty Accessible, the offset will always be 0. 31 * This will eventually replace TextPoint. Unlike TextPoint, this does not 32 * use HyperTextAccessible offsets. 33 */ 34 class TextLeafPoint final { 35 public: 36 TextLeafPoint(Accessible* aAcc, int32_t aOffset); 37 38 /** 39 * Constructs an invalid TextPoint (mAcc is null). 40 * A TextLeafPoint in this state will evaluate to false. 41 * mAcc can be set later. Alternatively, this can be used to indicate an error 42 * (e.g. if a requested point couldn't be found). 43 */ 44 TextLeafPoint() : mAcc(nullptr), mOffset(0) {} 45 46 /** 47 * Construct a TextLeafPoint representing the caret. 48 */ 49 static TextLeafPoint GetCaret(Accessible* aAcc); 50 51 Accessible* mAcc; 52 int32_t mOffset; 53 54 /** 55 * True if this point is the insertion point at the end of a line. This is the 56 * point where the caret is positioned when pressing the end key, for example. 57 * On the very last line, mOffset will be equal to the length of the text. 58 * However, where text wraps across lines, this line end insertion point 59 * doesn't have its own offset, so mOffset will be the offset for the first 60 * character on the next line. This is where this flag becomes important. 61 * Otherwise, for example, commanding a screen reader to read the current line 62 * would read the next line instead of the current line in this case. 63 */ 64 bool mIsEndOfLineInsertionPoint = false; 65 66 bool operator==(const TextLeafPoint& aPoint) const { 67 return mAcc == aPoint.mAcc && mOffset == aPoint.mOffset; 68 } 69 70 bool operator!=(const TextLeafPoint& aPoint) const { 71 return !(*this == aPoint); 72 } 73 74 bool operator<(const TextLeafPoint& aPoint) const; 75 76 bool operator<=(const TextLeafPoint& aPoint) const; 77 78 /** 79 * A valid TextLeafPoint evaluates to true. An invalid TextLeafPoint 80 * evaluates to false. 81 */ 82 explicit operator bool() const { return !!mAcc; } 83 84 enum class BoundaryFlags : uint32_t { 85 eDefaultBoundaryFlags = 0, 86 // Return point unchanged if it is at the given boundary type. 87 eIncludeOrigin = 1 << 0, 88 // If current point is in editable, return point within same editable. 89 eStopInEditable = 1 << 1, 90 // Skip over list items in searches and don't consider them line or 91 // paragraph starts. 92 eIgnoreListItemMarker = 1 << 2, 93 }; 94 95 /** 96 * Find a boundary (word start, line start, etc.) in a specific direction. 97 * If no boundary is found, the start/end of the document is returned 98 * (depending on the direction). 99 */ 100 TextLeafPoint FindBoundary( 101 AccessibleTextBoundary aBoundaryType, nsDirection aDirection, 102 BoundaryFlags aFlags = BoundaryFlags::eDefaultBoundaryFlags) const; 103 104 /** 105 * These two functions find a line start boundary within the same 106 * LocalAccessible as this. That is, they do not cross Accessibles. If no 107 * boundary is found, an invalid TextLeafPoint is returned. 108 * These are used by FindBoundary. Most callers will want FindBoundary 109 * instead. 110 */ 111 TextLeafPoint FindPrevLineStartSameLocalAcc(bool aIncludeOrigin) const; 112 TextLeafPoint FindNextLineStartSameLocalAcc(bool aIncludeOrigin) const; 113 114 /** 115 * These two functions find a word start boundary within the same 116 * Accessible as this. That is, they do not cross Accessibles. If no 117 * boundary is found, an invalid TextLeafPoint is returned. 118 * These are used by FindBoundary. Most callers will want FindBoundary 119 * instead. 120 */ 121 TextLeafPoint FindPrevWordStartSameAcc(bool aIncludeOrigin) const; 122 TextLeafPoint FindNextWordStartSameAcc(bool aIncludeOrigin) const; 123 124 /** 125 * Get the text attributes at this point. 126 * If aIncludeDefaults is true, default attributes on the HyperTextAccessible 127 * will be included. 128 */ 129 already_AddRefed<AccAttributes> GetTextAttributes( 130 bool aIncludeDefaults = true) const; 131 132 /** 133 * Get Get the text attributes at this point in a LocalAccessible. 134 * This is used by GetTextAttributes. Most callers will want GetTextAttributes 135 * instead. 136 */ 137 already_AddRefed<AccAttributes> GetTextAttributesLocalAcc( 138 bool aIncludeDefaults = true) const; 139 140 /** 141 * Get all the attributes that apply to offset ranges in a given text leaf 142 * LocalAccessible. This should only be used when pushing the cache. Most 143 * callers will want FindTextAttrsStart instead. 144 */ 145 static nsTArray<TextOffsetAttribute> GetTextOffsetAttributes( 146 LocalAccessible* aAcc); 147 148 /** 149 * Queue a cache update for text offset attributes in a given DOM range. 150 */ 151 static void UpdateCachedTextOffsetAttributes( 152 dom::Document* aDocument, const dom::AbstractRange& aRange); 153 154 /** 155 * Find the start of a run of text attributes in a specific direction. 156 * A text attributes run is a span of text where the attributes are the same. 157 * If no boundary is found, the function will walk out of the container and 158 * into the next/previous leaf (if it exists) until it finds a start point. 159 * If aIncludeOrigin is true and this is at a boundary, this will be 160 * returned unchanged. 161 */ 162 TextLeafPoint FindTextAttrsStart(nsDirection aDirection, 163 bool aIncludeOrigin = false) const; 164 165 /** 166 * Returns a rect (in dev pixels) describing position and size of 167 * the character at mOffset in mAcc. This rect is screen-relative. 168 */ 169 LayoutDeviceIntRect CharBounds() const; 170 171 /** 172 * Returns true if the given point (in screen coords) is contained 173 * in the char bounds of the current TextLeafPoint. Returns false otherwise. 174 * If the current point is an empty container, we use the acc's bounds instead 175 * of char bounds. 176 */ 177 bool ContainsPoint(int32_t aX, int32_t aY); 178 179 bool IsLineFeedChar() const { return GetChar() == '\n'; } 180 181 bool IsSpace() const; 182 183 bool IsParagraphStart(bool aIgnoreListItemMarker = false) const { 184 return mOffset == 0 && 185 FindParagraphSameAcc(eDirPrevious, true, aIgnoreListItemMarker); 186 } 187 188 /** 189 * Translate given TextLeafPoint into a DOM point. 190 */ 191 MOZ_CAN_RUN_SCRIPT std::pair<nsIContent*, uint32_t> ToDOMPoint( 192 bool aIncludeGenerated = true) const; 193 194 private: 195 /** 196 * If this is the insertion point at the end of a line, return an adjusted 197 * point such that word and line boundaries can be calculated correctly. 198 */ 199 TextLeafPoint AdjustEndOfLine() const; 200 201 bool IsEmptyLastLine() const; 202 203 bool IsDocEdge(nsDirection aDirection) const; 204 205 bool IsLeafAfterListItemMarker() const; 206 207 char16_t GetChar() const; 208 209 TextLeafPoint FindLineStartSameRemoteAcc(nsDirection aDirection, 210 bool aIncludeOrigin) const; 211 212 /** 213 * Helper which just calls the appropriate function based on whether mAcc 214 *is local or remote. 215 */ 216 TextLeafPoint FindLineStartSameAcc(nsDirection aDirection, 217 bool aIncludeOrigin, 218 bool aIgnoreListItemMarker = false) const; 219 220 TextLeafPoint FindLineEnd(nsDirection aDirection, BoundaryFlags aFlags) const; 221 TextLeafPoint FindWordEnd(nsDirection aDirection, BoundaryFlags aFlags) const; 222 223 TextLeafPoint FindParagraphSameAcc(nsDirection aDirection, 224 bool aIncludeOrigin, 225 bool aIgnoreListItemMarker = false) const; 226 227 TextLeafPoint FindClusterSameAcc(nsDirection aDirection, 228 bool aIncludeOrigin) const; 229 230 void AddTextOffsetAttributes(AccAttributes* aAttrs) const; 231 232 /** 233 * Find a text offset attribute boundary in the same Accessible. This function 234 * searches for either start or end points, since either means a change in 235 * text attributes. This only considers attributes such as spelling errors 236 * which are mapped to DOM selections. Most callers will want 237 * FindTextAttrsStart instead. 238 */ 239 TextLeafPoint FindTextOffsetAttributeSameAcc(nsDirection aDirection, 240 bool aIncludeOrigin) const; 241 242 // Return the point immediately succeeding or preceding this leaf depending 243 // on given direction. 244 TextLeafPoint NeighborLeafPoint(nsDirection aDirection, bool aIsEditable, 245 bool aIgnoreListItemMarker) const; 246 247 /** 248 * This function assumes mAcc is a LocalAccessible. 249 * It iterates the continuations of mAcc's primary frame until it locates 250 * the continuation containing mOffset (a rendered offset). It then uses 251 * GetScreenRectInAppUnits to compute screen coords for the frame, resizing 252 * such that the resulting rect contains only one character. 253 */ 254 LayoutDeviceIntRect ComputeBoundsFromFrame() const; 255 256 LayoutDeviceIntRect InsertionPointBounds() const; 257 258 friend class TextLeafRange; 259 }; 260 261 MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(TextLeafPoint::BoundaryFlags) 262 263 /** 264 * Represents a range of accessible text. 265 * This will eventually replace TextRange. 266 */ 267 class TextLeafRange final { 268 public: 269 TextLeafRange(const TextLeafPoint& aStart, const TextLeafPoint& aEnd) 270 : mStart(aStart), mEnd(aEnd) {} 271 explicit TextLeafRange(const TextLeafPoint& aStart) 272 : mStart(aStart), mEnd(aStart) {} 273 explicit TextLeafRange() {} 274 275 // Create a TextLeafRange spanning the entire leaf. 276 static TextLeafRange FromAccessible(Accessible* aAcc) { 277 return {{aAcc, 0}, {aAcc, nsIAccessibleText::TEXT_OFFSET_END_OF_TEXT}}; 278 } 279 280 /** 281 * A valid TextLeafRange evaluates to true. An invalid TextLeafRange 282 * evaluates to false. 283 */ 284 explicit operator bool() const { return !!mStart && !!mEnd; } 285 286 bool operator!=(const TextLeafRange& aOther) const { 287 return mEnd != aOther.mEnd || mStart != aOther.mStart; 288 } 289 290 bool operator==(const TextLeafRange& aOther) const { 291 return mEnd == aOther.mEnd && mStart == aOther.mStart; 292 } 293 294 TextLeafPoint Start() const { return mStart; } 295 void SetStart(const TextLeafPoint& aStart) { mStart = aStart; } 296 TextLeafPoint End() const { return mEnd; } 297 void SetEnd(const TextLeafPoint& aEnd) { mEnd = aEnd; } 298 299 bool Crop(Accessible* aContainer); 300 301 /** 302 * Returns a union rect (in dev pixels) of all character bounds in this range. 303 * This rect is screen-relative and exclusive of mEnd. 304 */ 305 LayoutDeviceIntRect Bounds() const; 306 307 /** 308 * Returns an array of bounding rectangles, one for each visible text line in 309 * this range. These rectangles are screen-relative and exclusive of mEnd. 310 */ 311 nsTArray<LayoutDeviceIntRect> LineRects() const; 312 313 /** 314 * Returns a TextLeafPoint corresponding to the point in the TextLeafRange 315 * containing the given screen point. The function returns a TextLeafPoint 316 * constructed from mStart if it does not find a containing character. 317 */ 318 TextLeafPoint TextLeafPointAtScreenPoint(int32_t aX, int32_t aY) const; 319 320 /** 321 * Get the ranges of text that are selected within this Accessible. The caret 322 * is not included as a collapsed range. 323 */ 324 static void GetSelection(Accessible* aAcc, nsTArray<TextLeafRange>& aRanges); 325 326 static const int32_t kCreateNewSelectionRange = -1; 327 static const int32_t kRemoveAllExistingSelectedRanges = -2; 328 329 /** 330 * Set range as DOM selection. 331 * aSelectionNum is the selection index to use. If aSelectionNum is 332 * out of bounds for current selection ranges, or is kCreateNewSelectionRange, 333 * a new selection range is created. If aSelectionNum is 334 * kRemoveAllExistingSelectedRanges, this will be set as the only range in the 335 * selection; i.e. all existing ranges (if any) will be removed from the 336 * selection first. 337 * If aSetFocus is true, the element containing the start point will be 338 * focused if appropriate. If aSetFocus is false, the focused element will 339 * be left as is. 340 */ 341 MOZ_CAN_RUN_SCRIPT bool SetSelection(int32_t aSelectionNum, 342 bool aSetFocus = true) const; 343 344 MOZ_CAN_RUN_SCRIPT void ScrollIntoView(uint32_t aScrollType) const; 345 346 /** 347 * Returns sub-ranges for all the lines in this range visible within the given 348 * container Accessible. 349 */ 350 nsTArray<TextLeafRange> VisibleLines(Accessible* aContainer) const; 351 352 private: 353 TextLeafPoint mStart; 354 TextLeafPoint mEnd; 355 356 /** 357 * Walk all of the lines within the TextLeafRange. This function invokes the 358 * given callback with the sub-range for each line and the line's bounding 359 * rectangle. The bounds are inclusive of all characters in each line, except 360 * that the first and last lines might be partial if the range begins or ends 361 * in the middle of a line. They are exclusive of mEnd, since range ends are 362 * always exclusive, so including mEnd would include the bounds for 1 363 * character past the end of the range. Each rectangle is screen-relative. If 364 * this range is collapsed, the callback is called with the insertion point 365 * bounds. The function returns true if it walks any lines, and false if it 366 * could not walk any lines, which could happen if the start and end points 367 * are improperly positioned. 368 */ 369 using LineRectCallback = 370 FunctionRef<void(TextLeafRange, LayoutDeviceIntRect)>; 371 bool WalkLineRects(LineRectCallback aCallback) const; 372 373 public: 374 /** 375 * A TextLeafRange iterator will iterate through single leaf segments of the 376 * given range. 377 */ 378 379 class Iterator { 380 public: 381 Iterator(Iterator&& aOther) 382 : mRange(aOther.mRange), 383 mSegmentStart(aOther.mSegmentStart), 384 mSegmentEnd(aOther.mSegmentEnd) {} 385 386 static Iterator BeginIterator(const TextLeafRange& aRange); 387 388 static Iterator EndIterator(const TextLeafRange& aRange); 389 390 Iterator& operator++(); 391 392 bool operator!=(const Iterator& aOther) const { 393 return mRange != aOther.mRange || mSegmentStart != aOther.mSegmentStart || 394 mSegmentEnd != aOther.mSegmentEnd; 395 } 396 397 TextLeafRange operator*() { 398 return TextLeafRange(mSegmentStart, mSegmentEnd); 399 } 400 401 private: 402 explicit Iterator(const TextLeafRange& aRange) : mRange(aRange) {} 403 404 Iterator() = delete; 405 Iterator(const Iterator&) = delete; 406 Iterator& operator=(const Iterator&) = delete; 407 Iterator& operator=(const Iterator&&) = delete; 408 409 const TextLeafRange& mRange; 410 TextLeafPoint mSegmentStart; 411 TextLeafPoint mSegmentEnd; 412 }; 413 414 Iterator begin() const { return Iterator::BeginIterator(*this); } 415 Iterator end() const { return Iterator::EndIterator(*this); } 416 }; 417 418 } // namespace a11y 419 } // namespace mozilla 420 421 #endif