Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(11)

Side by Side Diff: chrome/browser/resources/chromeos/chromevox/braille/pan_strategy.js

Issue 2496823002: Implement word wrapping and panning in multiline Braille. (Closed)
Patch Set: Addressed David's comments Created 4 years ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
1 // Copyright 2015 The Chromium Authors. All rights reserved. 1 // Copyright 2015 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 /** @fileoverview Logic for panning a braille display within a line of braille 5 /** @fileoverview Logic for panning a braille display within a line of braille
6 * content that might not fit on a single display. 6 * content that might not fit on a single display.
7 */ 7 */
8 8
9 goog.provide('cvox.PanStrategy'); 9 goog.provide('cvox.PanStrategy');
10 10
11 /** 11 /**
12 * @constructor 12 * @constructor
13 * 13 *
14 * A stateful class that keeps track of the current 'viewport' of a braille 14 * A stateful class that keeps track of the current 'viewport' of a braille
15 * display in a line of content. 15 * display in a line of content.
16 */ 16 */
17 cvox.PanStrategy = function() { 17 cvox.PanStrategy = function() {
18 /** 18 /**
19 * @type {number} 19 * @type {{rows: number, columns: number}}
20 * @private 20 * @private
21 */ 21 */
22 this.displaySize_ = 0; 22 this.displaySize_ = {rows: 1, columns: 40};
23
23 /** 24 /**
24 * @type {number} 25 * Start and end are both inclusive.
25 * @private
26 */
27 this.contentLength_ = 0;
28 /**
29 * Points before which it is desirable to break content if it doesn't fit
30 * on the display.
31 * @type {!Array<number>}
32 * @private
33 */
34 this.breakPoints_ = [];
35 /**
36 * @type {!cvox.PanStrategy.Range} 26 * @type {!cvox.PanStrategy.Range}
37 * @private 27 * @private
38 */ 28 */
39 this.viewPort_ = {start: 0, end: 0}; 29 this.viewPort_ = {firstRow: 0, lastRow: 0};
30
31 /**
32 * The ArrayBuffer holding the braille cells after it's been processed to
33 * wrap words that are cut off by the column boundaries.
34 * @type {!ArrayBuffer}
35 * @private
36 */
37 this.wrappedBuffer_ = new ArrayBuffer(0);
38
39 /**
40 * The original text that corresponds with the braille buffers. There is only
41 * one textBuffer that correlates with both fixed and wrapped buffers.
42 * @type {string}
43 * @private
44 */
45 this.textBuffer_ = '';
46
47 /**
48 * The ArrayBuffer holding the original braille cells, without being
49 * processed to wrap words.
50 * @type {!ArrayBuffer}
51 * @private
52 */
53 this.fixedBuffer_ = new ArrayBuffer(0);
54
55 /**
56 * The updated mapping from braille cells to text characters for the wrapped
57 * buffer.
58 * @type {Array<number>}
59 * @private
60 */
61 this.wrappedBrailleToText_ = [];
62
63 /**
64 * The original mapping from braille cells to text characters.
65 * @type {Array<number>}
66 * @private
67 */
68 this.fixedBrailleToText_ = [];
69
70 /**
71 * Indicates whether the pan strategy is wrapped or fixed. It is wrapped when
72 * true.
73 * @type {boolean}
74 * @private
75 */
76 this.panStrategyWrapped_ = false;
77
40 }; 78 };
41 79
42 /** 80 /**
43 * A range used to represent the viewport with inclusive start and xclusive 81 * A range used to represent the viewport with inclusive start and xclusive
44 * end position. 82 * end position.
45 * @typedef {{start: number, end: number}} 83 * @typedef {{firstRow: number, lastRow: number}}
46 */ 84 */
47 cvox.PanStrategy.Range; 85 cvox.PanStrategy.Range;
48 86
49 cvox.PanStrategy.prototype = { 87 cvox.PanStrategy.prototype = {
50 /** 88 /**
51 * Gets the current viewport which is never larger than the current 89 * Gets the current viewport which is never larger than the current
52 * display size and whose end points are always within the limits of 90 * display size and whose end points are always within the limits of
53 * the current content. 91 * the current content.
54 * @type {!cvox.PanStrategy.Range} 92 * @type {!cvox.PanStrategy.Range}
55 */ 93 */
56 get viewPort() { 94 get viewPort() {
57 return this.viewPort_; 95 return this.viewPort_;
58 }, 96 },
59 97
60 /** 98 /**
99 * Gets the current displaySize.
100 * @type {{rows: number, columns: number}}
101 */
102 get displaySize() {
103 return this.displaySize_;
104 },
105
106 /**
107 * @return {{brailleOffset: number, textOffset: number}} The offset of
108 * braille and text indices of the current slice.
109 */
110 get offsetsForSlices() {
111 return {brailleOffset: this.viewPort_.firstRow * this.displaySize_.columns,
112 textOffset: this.brailleToText[this.viewPort_.firstRow *
113 this.displaySize_.columns]};
114 },
115
116 /**
117 * @return {number} The number of lines in the fixedBuffer.
118 */
119 get fixedLineCount() {
120 return Math.ceil(this.fixedBuffer_.byteLength / this.displaySize_.columns);
121 },
122
123 /**
124 * @return {number} The number of lines in the wrappedBuffer.
125 */
126 get wrappedLineCount() {
127 return Math.ceil(this.wrappedBuffer_.byteLength /
128 this.displaySize_.columns);
129 },
130
131 /**
132 * @return {Array<number>} The map of Braille cells to the first index of the
133 * corresponding text character.
134 */
135 get brailleToText() {
136 if (this.panStrategyWrapped_)
137 return this.wrappedBrailleToText_;
138 else
139 return this.fixedBrailleToText_;
140 },
141
142 /**
143 * @return {ArrayBuffer} Buffer of the slice of braille cells within the
144 * bounds of the viewport.
145 */
146 getCurrentBrailleViewportContents: function() {
147 var buf = this.panStrategyWrapped_ ?
148 this.wrappedBuffer_ : this.fixedBuffer_;
149 return buf.slice(this.viewPort_.firstRow * this.displaySize_.columns,
150 (this.viewPort_.lastRow + 1) * this.displaySize_.columns);
151 },
152
153 /**
154 * @return {string} String of the slice of text letters corresponding with
155 * the current braille slice.
156 */
157 getCurrentTextViewportContents: function() {
158 var brailleToText = this.brailleToText;
159 // Index of last braille character in slice.
160 var index = (this.viewPort_.lastRow + 1) * this.displaySize_.columns - 1;
161 // Index of first text character that the last braille character points to.
162 var end = brailleToText[index];
163 // Increment index until brailleToText[index] points to a different char.
164 // This is the cutoff point, as substring cuts up to, but not including,
165 // brailleToText[index].
166 while (index < brailleToText.length && brailleToText[index] == end) {
167 index++;
168 }
169 return this.textBuffer_.substring(
170 brailleToText[this.viewPort_.firstRow * this.displaySize_.columns],
171 brailleToText[index]);
172 },
173
174 /**
175 * Sets the current pan strategy and resets the viewport.
176 */
177 setPanStrategy: function(wordWrap) {
178 this.panStrategyWrapped_ = wordWrap;
179 this.panToPosition_(0);
180 },
181
182 /**
61 * Sets the display size. This call may update the viewport. 183 * Sets the display size. This call may update the viewport.
62 * @param {number} size the new display size, or {@code 0} if no display is 184 * @param {number} rowCount the new row size, or {@code 0} if no display is
63 * present. 185 * present.
186 * @param {number} columnCount the new column size, or {@code 0}
187 * if no display is present.
64 */ 188 */
65 setDisplaySize: function(size) { 189 setDisplaySize: function(rowCount, columnCount) {
66 this.displaySize_ = size; 190 this.displaySize_ = {rows: rowCount, columns: columnCount};
67 this.panToPosition_(this.viewPort_.start); 191 this.setContent(this.textBuffer_, this.fixedBuffer_,
192 this.fixedBrailleToText_, 0);
68 }, 193 },
69 194
70 /** 195 /**
71 * Sets the current content that panning should happen within. This call may 196 * Sets the internal data structures that hold the fixed and wrapped buffers
72 * change the viewport. 197 * and maps.
73 * @param {!ArrayBuffer} translatedContent The new content. 198 * @param {string} textBuffer Text of the shown braille.
199 * @param {!ArrayBuffer} translatedContent The new braille content.
200 * @param {Array<number>} fixedBrailleToText Map of Braille cells to the first
201 * index of corresponding text letter.
74 * @param {number} targetPosition Target position. The viewport is changed 202 * @param {number} targetPosition Target position. The viewport is changed
75 * to overlap this position. 203 * to overlap this position.
76 */ 204 */
77 setContent: function(translatedContent, targetPosition) { 205 setContent: function(textBuffer, translatedContent, fixedBrailleToText,
78 this.breakPoints_ = this.calculateBreakPoints_(translatedContent); 206 targetPosition) {
79 this.contentLength_ = translatedContent.byteLength; 207 this.viewPort_.firstRow = 0;
208 this.viewPort_.lastRow = this.displaySize_.rows - 1;
209 this.fixedBrailleToText_ = fixedBrailleToText;
210 this.wrappedBrailleToText_ = [];
211 this.textBuffer_ = textBuffer;
212 this.fixedBuffer_ = translatedContent;
213
214 // Convert the cells to Unicode braille pattern characters.
215 var view = new Uint8Array(translatedContent);
216 var wrappedBrailleArray = [];
217
218 var lastBreak = 0;
219 var cellsPadded = 0;
220 var index;
221 for (index = 0; index < translatedContent.byteLength + cellsPadded;
222 index++) {
223 // Is index at the beginning of a new line?
224 if (index != 0 && index % this.displaySize_.columns == 0) {
225 if (view[index - cellsPadded] == 0) {
226 // Delete all empty cells at the beginning of this line.
227 while (index - cellsPadded < view.length &&
228 view[index - cellsPadded] == 0) {
229 cellsPadded--;
230 }
231 index--;
232 lastBreak = index;
233 } else if (view[index - cellsPadded - 1] != 0 &&
234 lastBreak % this.displaySize_.columns != 0) {
235 // If first cell is not empty, we need to move the whole word down to
236 // this line and padd to previous line with 0's, from |lastBreak| to
237 // index. The braille to text map is also updated.
238 // If lastBreak is at the beginning of a line, that means the current
239 // word is bigger than |this.displaySize_.columns| so we shouldn't
240 // wrap.
241 for (var j = lastBreak + 1; j < index; j++) {
242 wrappedBrailleArray[j] = 0;
243 this.wrappedBrailleToText_[j] = this.wrappedBrailleToText_[j - 1];
244 cellsPadded++;
245 }
246 lastBreak = index;
247 index--;
248 } else {
249 // |lastBreak| is at the beginning of a line, so current word is
250 // bigger than |this.displaySize_.columns| so we shouldn't wrap.
251 wrappedBrailleArray.push(view[index - cellsPadded]);
252 this.wrappedBrailleToText_.push(
253 fixedBrailleToText[index - cellsPadded]);
254 }
255 } else {
256 if (view[index - cellsPadded] == 0) {
257 lastBreak = index;
258 }
259 wrappedBrailleArray.push(view[index - cellsPadded]);
260 this.wrappedBrailleToText_.push(
261 fixedBrailleToText[index - cellsPadded]);
262 }
263 }
264
265 // Convert the wrapped Braille Uint8 Array back to ArrayBuffer.
266 var wrappedBrailleUint8Array = new Uint8Array(wrappedBrailleArray);
267 this.wrappedBuffer_ = new ArrayBuffer(wrappedBrailleUint8Array.length);
268 new Uint8Array(this.wrappedBuffer_).set(wrappedBrailleUint8Array);
80 this.panToPosition_(targetPosition); 269 this.panToPosition_(targetPosition);
81 }, 270 },
82 271
83 /** 272 /**
84 * If possible, changes the viewport to a part of the line that follows 273 * If possible, changes the viewport to a part of the line that follows
85 * the current viewport. 274 * the current viewport.
86 * @return {boolean} {@code true} if the viewport was changed. 275 * @return {boolean} {@code true} if the viewport was changed.
87 */ 276 */
88 next: function() { 277 next: function() {
89 var newStart = this.viewPort_.end; 278 var contentLength = this.panStrategyWrapped_ ?
279 this.wrappedLineCount : this.fixedLineCount;
280 var newStart = this.viewPort_.lastRow + 1;
90 var newEnd; 281 var newEnd;
91 if (newStart + this.displaySize_ < this.contentLength_) { 282 if (newStart + this.displaySize_.rows - 1 < contentLength) {
92 newEnd = this.extendRight_(newStart); 283 newEnd = newStart + this.displaySize_.rows - 1;
93 } else { 284 } else {
94 newEnd = this.contentLength_; 285 newEnd = contentLength - 1;
95 } 286 }
96 if (newEnd > newStart) { 287 if (newEnd >= newStart) {
97 this.viewPort_ = {start: newStart, end: newEnd}; 288 this.viewPort_ = {firstRow: newStart, lastRow: newEnd};
98 return true; 289 return true;
99 } 290 }
100 return false; 291 return false;
101 }, 292 },
102 293
103 /** 294 /**
104 * If possible, changes the viewport to a part of the line that precedes 295 * If possible, changes the viewport to a part of the line that precedes
105 * the current viewport. 296 * the current viewport.
106 * @return {boolean} {@code true} if the viewport was changed. 297 * @return {boolean} {@code true} if the viewport was changed.
107 */ 298 */
108 previous: function() { 299 previous: function() {
109 if (this.viewPort_.start > 0) { 300 var contentLength = this.panStrategyWrapped_ ?
301 this.wrappedLineCount : this.fixedLineCount;
302 if (this.viewPort_.firstRow > 0) {
110 var newStart, newEnd; 303 var newStart, newEnd;
111 if (this.viewPort_.start <= this.displaySize_) { 304 if (this.viewPort_.firstRow < this.displaySize_.rows) {
112 newStart = 0; 305 newStart = 0;
113 newEnd = this.extendRight_(newStart); 306 newEnd = Math.min(this.displaySize_.rows, contentLength);
114 } else { 307 } else {
115 newEnd = this.viewPort_.start; 308 newEnd = this.viewPort_.firstRow - 1;
116 var limit = newEnd - this.displaySize_; 309 newStart = newEnd - this.displaySize_.rows + 1;
117 newStart = limit;
118 var pos = 0;
119 while (pos < this.breakPoints_.length &&
120 this.breakPoints_[pos] < limit) {
121 pos++;
122 }
123 if (pos < this.breakPoints_.length &&
124 this.breakPoints_[pos] < newEnd) {
125 newStart = this.breakPoints_[pos];
126 }
127 } 310 }
128 if (newStart < newEnd) { 311 if (newStart <= newEnd) {
129 this.viewPort_ = {start: newStart, end: newEnd}; 312 this.viewPort_ = {firstRow: newStart, lastRow: newEnd};
130 return true; 313 return true;
131 } 314 }
132 } 315 }
133 return false; 316 return false;
134 }, 317 },
135 318
136 /** 319 /**
137 * Finds the end position for a new viewport start position, considering
138 * current breakpoints as well as display size and content length.
139 * @param {number} from Start of the region to extend.
140 * @return {number}
141 * @private
142 */
143 extendRight_: function(from) {
144 var limit = Math.min(from + this.displaySize_, this.contentLength_);
145 var pos = 0;
146 var result = limit;
147 while (pos < this.breakPoints_.length && this.breakPoints_[pos] <= from) {
148 pos++;
149 }
150 while (pos < this.breakPoints_.length && this.breakPoints_[pos] <= limit) {
151 result = this.breakPoints_[pos];
152 pos++;
153 }
154 return result;
155 },
156
157 /**
158 * Overridden by subclasses to provide breakpoints given translated
159 * braille cell content.
160 * @param {!ArrayBuffer} content New display content.
161 * @return {!Array<number>} The points before which it is desirable to break
162 * content if needed or the empty array if no points are more desirable
163 * than any position.
164 * @private
165 */
166 calculateBreakPoints_: function(content) {return [];},
167
168 /**
169 * Moves the viewport so that it overlaps a target position without taking 320 * Moves the viewport so that it overlaps a target position without taking
170 * the current viewport position into consideration. 321 * the current viewport position into consideration.
171 * @param {number} position Target position. 322 * @param {number} position Target position.
172 */ 323 */
173 panToPosition_: function(position) { 324 panToPosition_: function(position) {
174 if (this.displaySize_ > 0) { 325 if (this.displaySize_.rows * this.displaySize_.columns > 0) {
175 this.viewPort_ = {start: 0, end: 0}; 326 this.viewPort_ = {firstRow: -1, lastRow: -1};
176 while (this.next() && this.viewPort_.end <= position) { 327 while (this.next() && (this.viewPort_.lastRow + 1) *
328 this.displaySize_.columns <= position) {
177 // Nothing to do. 329 // Nothing to do.
178 } 330 }
179 } else { 331 } else {
180 this.viewPort_ = {start: position, end: position}; 332 this.viewPort_ = {firstRow: position, lastRow: position};
181 } 333 }
182 }, 334 },
183 }; 335 };
184
185 /**
186 * A pan strategy that fits as much content on the display as possible, that
187 * is, it doesn't do any wrapping.
188 * @constructor
189 * @extends {cvox.PanStrategy}
190 */
191 cvox.FixedPanStrategy = cvox.PanStrategy;
192 /**
193 * A pan strategy that tries to wrap 'words' when breaking content.
194 * A 'word' in this context is just a chunk of non-blank braille cells
195 * delimited by blank cells.
196 * @constructor
197 * @extends {cvox.PanStrategy}
198 */
199 cvox.WrappingPanStrategy = function() {
200 cvox.PanStrategy.call(this);
201 };
202
203 cvox.WrappingPanStrategy.prototype = {
204 __proto__: cvox.PanStrategy.prototype,
205
206 /** @override */
207 calculateBreakPoints_: function(content) {
208 var view = new Uint8Array(content);
209 var newContentLength = view.length;
210 var result = [];
211 var lastCellWasBlank = false;
212 for (var pos = 0; pos < view.length; ++pos) {
213 if (lastCellWasBlank && view[pos] != 0) {
214 result.push(pos);
215 }
216 lastCellWasBlank = (view[pos] == 0);
217 }
218 return result;
219 },
220 };
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698