• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// Copyright 2014 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5/**
6 * @fileoverview Defines the ContentEditableExtractor class.
7 */
8
9goog.provide('cvox.ContentEditableExtractor');
10
11goog.require('cvox.Cursor');
12goog.require('cvox.TraverseUtil');
13
14/**
15 * Extracts the text and line break information from a contenteditable region.
16 * @constructor
17 */
18cvox.ContentEditableExtractor = function() {
19  /**
20   * The extracted, flattened, text.
21   * @type {string}
22   * @private
23   */
24  this.text_ = '';
25
26  /**
27   * The start cursor/selection index.
28   * @type {number}
29   * @private
30   */
31  this.start_ = 0;
32
33  /**
34   * The end cursor/selection index.
35   * @type {number}
36   * @private
37   */
38  this.end_ = 0;
39
40  /**
41   * Map from line index to a data structure containing the start
42   * and end index within the line.
43   * @type {Object.<number, {startIndex: number, endIndex: number}>}
44   * @private
45   */
46  this.lines_ = {};
47
48  /**
49   * Map from 0-based character index to 0-based line index.
50   * @type {Array.<number>}
51   * @private
52   */
53  this.characterToLineMap_ = [];
54};
55
56/**
57 * Update the metadata.
58 * @param {Element} element The DOM element that's contentEditable.
59 */
60cvox.ContentEditableExtractor.prototype.update = function(element) {
61  /**
62   * Map from line index to a data structure containing the start
63   * and end index within the line.
64   * @type {Object.<number, {startIndex: number, endIndex: number}>}
65   */
66  var lines = {0: {startIndex: 0, endIndex: 0}};
67  var startCursor = new cvox.Cursor(element, 0, '');
68  var endCursor = startCursor.clone();
69  var range = document.createRange();
70  var rect;
71  var lineIndex = 0;
72  var lastBottom = null;
73  var text = '';
74  var textSize = 0;
75  var selectionStartIndex = -1;
76  var selectionEndIndex = -1;
77  var sel = window.getSelection();
78  var selectionStart = new cvox.Cursor(sel.baseNode, sel.baseOffset, '');
79  var selectionEnd = new cvox.Cursor(sel.extentNode, sel.extentOffset, '');
80  var setStart = false;
81  var setEnd = false;
82  while (true) {
83    var entered = [];
84    var left = [];
85    var c = cvox.TraverseUtil.forwardsChar(endCursor, entered, left);
86    var done = false;
87    if (!c) {
88      done = true;
89    }
90    for (var i = 0; i < left.length && !done; i++) {
91      if (left[i] == element) {
92        done = true;
93      }
94    }
95    if (done) {
96      break;
97    }
98
99    range.setStart(startCursor.node, startCursor.index);
100    range.setEnd(endCursor.node, endCursor.index);
101    rect = range.getBoundingClientRect();
102    if (!rect || rect.width == 0 || rect.height == 0) {
103      continue;
104    }
105
106    if (lastBottom !== null &&
107        rect.bottom != lastBottom &&
108        textSize > 0 &&
109        text.substr(-1).match(/\S/) &&
110        c.match(/\S/)) {
111      text += '\n';
112      textSize++;
113    }
114
115    if (startCursor.node != endCursor.node && endCursor.index > 0) {
116      range.setStart(endCursor.node, endCursor.index - 1);
117      rect = range.getBoundingClientRect();
118      if (!rect || rect.width == 0 || rect.height == 0) {
119        continue;
120      }
121    }
122
123    if (!setStart &&
124        selectionStartIndex == -1 &&
125        endCursor.node == selectionStart.node &&
126        endCursor.index >= selectionStart.index) {
127      if (endCursor.index > selectionStart.index) {
128        selectionStartIndex = textSize;
129      } else {
130        setStart = true;
131      }
132    }
133    if (!setEnd &&
134        selectionEndIndex == -1 &&
135        endCursor.node == selectionEnd.node &&
136        endCursor.index >= selectionEnd.index) {
137      if (endCursor.index > selectionEnd.index) {
138        selectionEndIndex = textSize;
139      } else {
140        setEnd = true;
141      }
142    }
143
144    if (lastBottom === null) {
145      // This is the first character we've successfully measured on this
146      // line. Save the vertical position but don't do anything else.
147      lastBottom = rect.bottom;
148    } else if (rect.bottom != lastBottom) {
149      lines[lineIndex].endIndex = textSize;
150      lineIndex++;
151      lines[lineIndex] = {startIndex: textSize, endIndex: textSize};
152      lastBottom = rect.bottom;
153    }
154    text += c;
155    textSize++;
156    startCursor = endCursor.clone();
157
158    if (setStart) {
159      selectionStartIndex = textSize;
160      setStart = false;
161    }
162    if (setEnd) {
163      selectionEndIndex = textSize;
164      setEnd = false;
165    }
166  }
167
168  // Finish up the last line.
169  lines[lineIndex].endIndex = textSize;
170
171  // Create a map from character index to line number.
172  var characterToLineMap = [];
173  for (var i = 0; i <= lineIndex; i++) {
174    for (var j = lines[i].startIndex; j <= lines[i].endIndex; j++) {
175      characterToLineMap[j] = i;
176    }
177  }
178
179  // Finish updating fields.
180  this.text_ = text;
181  this.characterToLineMap_ = characterToLineMap;
182  this.lines_ = lines;
183
184  this.start_ = selectionStartIndex >= 0 ? selectionStartIndex : text.length;
185  this.end_ = selectionEndIndex >= 0 ? selectionEndIndex : text.length;
186};
187
188/**
189 * Get the text.
190 * @return {string} The extracted, flattened, text.
191 */
192cvox.ContentEditableExtractor.prototype.getText = function() {
193  return this.text_;
194};
195
196/**
197 * @return {number} The start cursor/selection index.
198 */
199cvox.ContentEditableExtractor.prototype.getStartIndex = function() {
200  return this.start_;
201};
202
203/**
204 * @return {number} The end cursor/selection index.
205 */
206cvox.ContentEditableExtractor.prototype.getEndIndex = function() {
207  return this.end_;
208};
209
210/**
211 * Get the line number corresponding to a particular index.
212 * @param {number} index The 0-based character index.
213 * @return {number} The 0-based line number corresponding to that character.
214 */
215cvox.ContentEditableExtractor.prototype.getLineIndex = function(index) {
216  return this.characterToLineMap_[index];
217};
218
219/**
220 * Get the start character index of a line.
221 * @param {number} index The 0-based line index.
222 * @return {number} The 0-based index of the first character in this line.
223 */
224cvox.ContentEditableExtractor.prototype.getLineStart = function(index) {
225  return this.lines_[index].startIndex;
226};
227
228/**
229 * Get the end character index of a line.
230 * @param {number} index The 0-based line index.
231 * @return {number} The 0-based index of the end of this line.
232 */
233cvox.ContentEditableExtractor.prototype.getLineEnd = function(index) {
234  return this.lines_[index].endIndex;
235};
236