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.
6 * @fileoverview Defines the ContentEditableExtractor class.
9 goog.provide('cvox.ContentEditableExtractor');
11 goog.require('cvox.Cursor');
12 goog.require('cvox.TraverseUtil');
15 * Extracts the text and line break information from a contenteditable region.
18 cvox.ContentEditableExtractor = function() {
20 * The extracted, flattened, text.
27 * The start cursor/selection index.
34 * The end cursor/selection index.
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}>}
49 * Map from 0-based character index to 0-based line index.
50 * @type {Array<number>}
53 this.characterToLineMap_ = [];
57 * Update the metadata.
58 * @param {Element} element The DOM element that's contentEditable.
60 cvox.ContentEditableExtractor.prototype.update = function(element) {
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}>}
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();
72 var lastBottom = null;
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, '');
85 var c = cvox.TraverseUtil.forwardsChar(endCursor, entered, left);
90 for (var i = 0; i < left.length && !done; i++) {
91 if (left[i] == element) {
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) {
106 if (lastBottom !== null &&
107 rect.bottom != lastBottom &&
109 text.substr(-1).match(/\S/) &&
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) {
124 selectionStartIndex == -1 &&
125 endCursor.node == selectionStart.node &&
126 endCursor.index >= selectionStart.index) {
127 if (endCursor.index > selectionStart.index) {
128 selectionStartIndex = textSize;
134 selectionEndIndex == -1 &&
135 endCursor.node == selectionEnd.node &&
136 endCursor.index >= selectionEnd.index) {
137 if (endCursor.index > selectionEnd.index) {
138 selectionEndIndex = textSize;
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;
151 lines[lineIndex] = {startIndex: textSize, endIndex: textSize};
152 lastBottom = rect.bottom;
156 startCursor = endCursor.clone();
159 selectionStartIndex = textSize;
163 selectionEndIndex = textSize;
168 // Finish up the last line.
169 lines[lineIndex].endIndex = textSize;
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;
179 // Finish updating fields.
181 this.characterToLineMap_ = characterToLineMap;
184 this.start_ = selectionStartIndex >= 0 ? selectionStartIndex : text.length;
185 this.end_ = selectionEndIndex >= 0 ? selectionEndIndex : text.length;
190 * @return {string} The extracted, flattened, text.
192 cvox.ContentEditableExtractor.prototype.getText = function() {
197 * @return {number} The start cursor/selection index.
199 cvox.ContentEditableExtractor.prototype.getStartIndex = function() {
204 * @return {number} The end cursor/selection index.
206 cvox.ContentEditableExtractor.prototype.getEndIndex = function() {
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.
215 cvox.ContentEditableExtractor.prototype.getLineIndex = function(index) {
216 return this.characterToLineMap_[index];
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.
224 cvox.ContentEditableExtractor.prototype.getLineStart = function(index) {
225 return this.lines_[index].startIndex;
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.
233 cvox.ContentEditableExtractor.prototype.getLineEnd = function(index) {
234 return this.lines_[index].endIndex;