61669a0848816948291e5e2d4c3aa33aa2655fb7c075385c6f586ea53fd33248d8331281ff490804d1bbb8cf41c70d950ece5c0be15a7c0a0d8bc02170231b 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112
  1. /*---------------------------------------------------------------------------------------------
  2. * Copyright (c) Microsoft Corporation. All rights reserved.
  3. * Licensed under the MIT License. See License.txt in the project root for license information.
  4. *--------------------------------------------------------------------------------------------*/
  5. import { FloatHorizontalRange } from '../../view/renderingContext.js';
  6. export class RangeUtil {
  7. static _createRange() {
  8. if (!this._handyReadyRange) {
  9. this._handyReadyRange = document.createRange();
  10. }
  11. return this._handyReadyRange;
  12. }
  13. static _detachRange(range, endNode) {
  14. // Move range out of the span node, IE doesn't like having many ranges in
  15. // the same spot and will act badly for lines containing dashes ('-')
  16. range.selectNodeContents(endNode);
  17. }
  18. static _readClientRects(startElement, startOffset, endElement, endOffset, endNode) {
  19. const range = this._createRange();
  20. try {
  21. range.setStart(startElement, startOffset);
  22. range.setEnd(endElement, endOffset);
  23. return range.getClientRects();
  24. }
  25. catch (e) {
  26. // This is life ...
  27. return null;
  28. }
  29. finally {
  30. this._detachRange(range, endNode);
  31. }
  32. }
  33. static _mergeAdjacentRanges(ranges) {
  34. if (ranges.length === 1) {
  35. // There is nothing to merge
  36. return ranges;
  37. }
  38. ranges.sort(FloatHorizontalRange.compare);
  39. const result = [];
  40. let resultLen = 0;
  41. let prev = ranges[0];
  42. for (let i = 1, len = ranges.length; i < len; i++) {
  43. const range = ranges[i];
  44. if (prev.left + prev.width + 0.9 /* account for browser's rounding errors*/ >= range.left) {
  45. prev.width = Math.max(prev.width, range.left + range.width - prev.left);
  46. }
  47. else {
  48. result[resultLen++] = prev;
  49. prev = range;
  50. }
  51. }
  52. result[resultLen++] = prev;
  53. return result;
  54. }
  55. static _createHorizontalRangesFromClientRects(clientRects, clientRectDeltaLeft, clientRectScale) {
  56. if (!clientRects || clientRects.length === 0) {
  57. return null;
  58. }
  59. // We go through FloatHorizontalRange because it has been observed in bi-di text
  60. // that the clientRects are not coming in sorted from the browser
  61. const result = [];
  62. for (let i = 0, len = clientRects.length; i < len; i++) {
  63. const clientRect = clientRects[i];
  64. result[i] = new FloatHorizontalRange(Math.max(0, (clientRect.left - clientRectDeltaLeft) / clientRectScale), clientRect.width / clientRectScale);
  65. }
  66. return this._mergeAdjacentRanges(result);
  67. }
  68. static readHorizontalRanges(domNode, startChildIndex, startOffset, endChildIndex, endOffset, clientRectDeltaLeft, clientRectScale, endNode) {
  69. // Panic check
  70. const min = 0;
  71. const max = domNode.children.length - 1;
  72. if (min > max) {
  73. return null;
  74. }
  75. startChildIndex = Math.min(max, Math.max(min, startChildIndex));
  76. endChildIndex = Math.min(max, Math.max(min, endChildIndex));
  77. if (startChildIndex === endChildIndex && startOffset === endOffset && startOffset === 0 && !domNode.children[startChildIndex].firstChild) {
  78. // We must find the position at the beginning of a <span>
  79. // To cover cases of empty <span>s, avoid using a range and use the <span>'s bounding box
  80. const clientRects = domNode.children[startChildIndex].getClientRects();
  81. return this._createHorizontalRangesFromClientRects(clientRects, clientRectDeltaLeft, clientRectScale);
  82. }
  83. // If crossing over to a span only to select offset 0, then use the previous span's maximum offset
  84. // Chrome is buggy and doesn't handle 0 offsets well sometimes.
  85. if (startChildIndex !== endChildIndex) {
  86. if (endChildIndex > 0 && endOffset === 0) {
  87. endChildIndex--;
  88. endOffset = 1073741824 /* Constants.MAX_SAFE_SMALL_INTEGER */;
  89. }
  90. }
  91. let startElement = domNode.children[startChildIndex].firstChild;
  92. let endElement = domNode.children[endChildIndex].firstChild;
  93. if (!startElement || !endElement) {
  94. // When having an empty <span> (without any text content), try to move to the previous <span>
  95. if (!startElement && startOffset === 0 && startChildIndex > 0) {
  96. startElement = domNode.children[startChildIndex - 1].firstChild;
  97. startOffset = 1073741824 /* Constants.MAX_SAFE_SMALL_INTEGER */;
  98. }
  99. if (!endElement && endOffset === 0 && endChildIndex > 0) {
  100. endElement = domNode.children[endChildIndex - 1].firstChild;
  101. endOffset = 1073741824 /* Constants.MAX_SAFE_SMALL_INTEGER */;
  102. }
  103. }
  104. if (!startElement || !endElement) {
  105. return null;
  106. }
  107. startOffset = Math.min(startElement.textContent.length, Math.max(0, startOffset));
  108. endOffset = Math.min(endElement.textContent.length, Math.max(0, endOffset));
  109. const clientRects = this._readClientRects(startElement, startOffset, endElement, endOffset, endNode);
  110. return this._createHorizontalRangesFromClientRects(clientRects, clientRectDeltaLeft, clientRectScale);
  111. }
  112. }