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

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 code reviews. Created 4 years, 1 month 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 {number}
20 * @private 20 * @private
21 */ 21 */
22 this.displaySize_ = 0; 22 this.rowCount_ = 0;
23 /** 23 /**
24 * @type {number} 24 * @type {number}
25 * @private 25 * @private
26 */ 26 */
27 this.contentLength_ = 0; 27 this.columnCount_ = 0;
28 /** 28 /**
29 * Points before which it is desirable to break content if it doesn't fit 29 * Start and end are both inclusive.
30 * on the display.
31 * @type {!Array<number>}
32 * @private
33 */
34 this.breakPoints_ = [];
35 /**
36 * @type {!cvox.PanStrategy.Range} 30 * @type {!cvox.PanStrategy.Range}
37 * @private 31 * @private
38 */ 32 */
39 this.viewPort_ = {start: 0, end: 0}; 33 this.viewPort_ = {start: 0, end: 0};
34
35 /**
36 * Start and end are both inclusive.
37 * @type {ArrayBuffer}
38 * @private
39 */
40 this.wrappedBuffer_ = new ArrayBuffer(0);
41
42 /**
43 * Start and end are both inclusive.
44 * @type {ArrayBuffer}
45 * @private
46 */
47 this.fixedBuffer_ = new ArrayBuffer(0);
48
49 /**
50 * Start and end are both inclusive.
51 * @type {Array}
David Tseng 2016/11/15 17:55:00 Array<number>
ultimatedbz 2016/11/19 03:11:59 Done.
52 * @private
53 */
54 this.wrappedBrailleToText_ = [];
55
56 /**
57 * Start and end are both inclusive.
David Tseng 2016/11/15 17:55:00 Fix these comments.
ultimatedbz 2016/11/19 03:11:59 Done.
58 * @type {Array}
59 * @private
60 */
61 this.fixedBrailleToText_ = [];
62
63 /**
64 * Start and end are both inclusive.
65 * @type {boolean}
66 * @private
67 */
68 this.panStrategyWrapped_ = false;
69
70 /**
71 * Start and end are both inclusive.
72 * @type {number}
73 * @private
74 */
75 this.fixedLineCount_ = 0;
David Tseng 2016/11/16 16:56:01 You don't need this (and the below) members. Just
ultimatedbz 2016/11/19 03:11:58 Done.
76
77 /**
78 * Start and end are both inclusive.
79 * @type {number}
80 * @private
81 */
82 this.wrappedLineCount_ = 0;
40 }; 83 };
41 84
42 /** 85 /**
43 * A range used to represent the viewport with inclusive start and xclusive 86 * A range used to represent the viewport with inclusive start and xclusive
44 * end position. 87 * end position.
45 * @typedef {{start: number, end: number}} 88 * @typedef {{start: number, end: number}}
46 */ 89 */
47 cvox.PanStrategy.Range; 90 cvox.PanStrategy.Range;
48 91
49 cvox.PanStrategy.prototype = { 92 cvox.PanStrategy.prototype = {
50 /** 93 /**
51 * Gets the current viewport which is never larger than the current 94 * Gets the current viewport which is never larger than the current
52 * display size and whose end points are always within the limits of 95 * display size and whose end points are always within the limits of
53 * the current content. 96 * the current content.
54 * @type {!cvox.PanStrategy.Range} 97 * @type {!cvox.PanStrategy.Range}
55 */ 98 */
56 get viewPort() { 99 get viewPort() {
57 return this.viewPort_; 100 return this.viewPort_;
58 }, 101 },
59 102
60 /** 103 /**
104 * @return {Array<number>} The offset of braille and text indices of the
David Tseng 2016/11/15 17:55:00 Make this an Object e.g. {brailleOffset: number, t
ultimatedbz 2016/11/19 03:11:58 Done.
105 * current slice.
106 */
107 get offsetsForSlices() {
108 return [this.viewPort_.start * this.columnCount_,
109 this.brailleToText[this.viewPort_.start * this.columnCount_]];
110 },
111
112 /**
113 * @return {Array<number>} The map of Braille cells to the first index of the
114 * corresponding text character.
David Tseng 2016/11/15 17:55:00 @return {Array<number}
ultimatedbz 2016/11/19 03:11:59 I'm not sure what you're suggesting? Thanks!
115 */
116 get brailleToText() {
117 if (this.panStrategyWrapped_)
118 return this.wrappedBrailleToText_;
119 else
120 return this.fixedBrailleToText_;
121 },
122
123 /**
124 * @return {ArrayBuffer} Buffer of the slice of braille cells within the
125 * bounds of the viewport.
126 */
127 getCurrentBrailleSlice: function() {
128 var buf = this.panStrategyWrapped_ ?
129 this.wrappedBuffer_ : this.fixedBuffer_;
David Tseng 2016/11/15 17:54:59 Part of why it was nice to have different classes
ultimatedbz 2016/11/19 03:11:59 As per our offline conversation, we'll be keeping
130 buf = buf.slice(this.viewPort.start * this.columnCount_,
David Tseng 2016/11/15 17:54:59 return buff.slice...
ultimatedbz 2016/11/19 03:11:59 Done.
131 (this.viewPort.end + 1) * this.columnCount_);
David Tseng 2016/11/15 17:55:00 Why not just make the viewport index into the buff
ultimatedbz 2016/11/19 03:11:59 How about firstRow and lastRow?
132 return buf;
133 },
134
135 /**
136 * @return {string} String of the slice of text letters corresponding with
137 * the current braille slice.
David Tseng 2016/11/15 17:54:59 @return {Array<number>}
David Tseng 2016/11/16 16:56:01 Disregard.
138 */
139 getCurrentTextSlice: function() {
140 var brailleToText = this.brailleToText;
141 // Index of last braille character in slice.
142 var index = (this.viewPort.end + 1) * this.columnCount_ - 1;
David Tseng 2016/11/15 17:54:59 This just looks wrong (mostly the viewport.end + 1
ultimatedbz 2016/11/15 19:31:57 Right, so I know it looks a bit janky, but this.vi
David Tseng 2016/11/15 21:36:38 pan_strategy_test.unitjs awaits; I am generally ok
143 // Index of first text character that the last braille character points to.
144 var end = brailleToText[index];
145 // Increment index until brailleToText[index] points to a different char.
146 // This is the cutoff point, as substring cuts up to, but not including,
147 // brailleToText[index].
148 while (index < brailleToText.length && brailleToText[index] == end) {
149 index++;
150 }
David Tseng 2016/11/15 17:54:59 nit: no curlies for single lined if bodies.
David Tseng 2016/11/15 21:36:38 Ignore this :).
151 return this.textBuffer.substring(
David Tseng 2016/11/15 17:54:59 This deserves some unit tests to exercises the var
152 brailleToText[this.viewPort.start * this.columnCount_],
153 brailleToText[index]);
154 },
155
156 /**
157 * Sets the current pan strategy and resets the viewport.
158 */
159 setPanStrategy: function(wordWrap) {
160 this.panStrategyWrapped_ = wordWrap;
161 this.viewPort_.start = 0;
162 this.viewPort_.end = this.rowCount_ - 1;
163 },
164
165 /**
61 * Sets the display size. This call may update the viewport. 166 * 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 167 * @param {number} rowCount the new row size, or {@code 0} if no display is
63 * present. 168 * present.
169 * @param {number} columnCount the new column size, or {@code 0}
170 * if no display is present.
64 */ 171 */
65 setDisplaySize: function(size) { 172 setDisplaySize: function(rowCount, columnCount) {
66 this.displaySize_ = size; 173 this.rowCount_ = rowCount;
67 this.panToPosition_(this.viewPort_.start); 174 this.columnCount_ = columnCount;
David Tseng 2016/11/15 17:54:59 You'll want ot keep this around I suspect.
ultimatedbz 2016/11/19 03:11:59 Acknowledged.
68 }, 175 },
69 176
70 /** 177 /**
71 * Sets the current content that panning should happen within. This call may 178 * Sets the internal data structures that hold the fixed and wrapped buffers
72 * change the viewport. 179 * and maps.
73 * @param {!ArrayBuffer} translatedContent The new content. 180 * @param {string} textBuffer Text of the shown braille.
74 * @param {number} targetPosition Target position. The viewport is changed 181 * @param {!ArrayBuffer} translatedContent The new braille content.
75 * to overlap this position. 182 * @param {Array<number>} fixedBrailleToText Map of Braille cells to the first
183 * index of corresponding text letter.
76 */ 184 */
77 setContent: function(translatedContent, targetPosition) { 185 setContent: function(textBuffer, translatedContent, fixedBrailleToText) {
78 this.breakPoints_ = this.calculateBreakPoints_(translatedContent); 186 this.viewPort_.start = 0;
79 this.contentLength_ = translatedContent.byteLength; 187 this.viewPort_.end = this.rowCount_ - 1;
80 this.panToPosition_(targetPosition); 188 this.fixedBrailleToText_ = fixedBrailleToText;
189 this.wrappedBrailleToText_ = [];
190 this.textBuffer = textBuffer;
David Tseng 2016/11/16 16:56:01 This isn't annotated and set in the constructor.
ultimatedbz 2016/11/19 03:11:59 Done.
191 this.fixedBuffer_ = translatedContent;
David Tseng 2016/11/16 16:56:01 Note that I think there are some tricky ownership
ultimatedbz 2016/11/19 03:11:58 I've now given the panStrategy full ownership of t
192
193 // Convert the cells to Unicode braille pattern characters.
194 var view = new Uint8Array(translatedContent);
195 var wrappedBrailleArray = [];
196
197 var lastBreak = 0;
198 var cellsPadded = 0;
199 var index;
200 for (index = 0; index < translatedContent.byteLength + cellsPadded;
201 index++) {
202 // Is index at the beginning of a new line?
203 if (index != 0 && index % this.columnCount_ == 0) {
204 if (view[index - cellsPadded] == 0) {
205 // On Delete all 0's at beginning of new line.
David Tseng 2016/11/15 17:55:00 Rephrase
ultimatedbz 2016/11/19 03:11:58 Done.
206 while (index - cellsPadded < view.length &&
207 view[index - cellsPadded] == 0) {
208 cellsPadded--;
209 }
David Tseng 2016/11/15 17:55:00 nit: remove curlies
ultimatedbz 2016/11/15 19:31:57 Is it okay to remove the curly braces, even though
David Tseng 2016/11/15 21:36:38 Good question. It looks like this file keeps curly
210 index--;
211 } else if (view[index - cellsPadded - 1] != 0 &&
212 lastBreak % this.columnCount_ != 0) {
213 // If first cell is not empty, we need to move the whole word down to
214 // this line and padd to previous line with 0's, from |lastBreak| to
215 // index. The braille to text map is also updated.
216 // If lastBreak is at the beginning of a line, that means the current
217 // word is bigger than |this.columnCount_| so we shouldn't wrap.
218 for (var j = lastBreak + 1; j < index; j++) {
219 wrappedBrailleArray[j] = 0;
David Tseng 2016/11/15 17:55:00 A few things: - this absolutely needs tests - for
220 this.wrappedBrailleToText_[j] = this.wrappedBrailleToText_[j - 1];
221 cellsPadded++;
222 }
223 lastBreak = index;
224 index--;
225 } else {
226 // |lastBreak| is at the beginning of a line, so current word is
227 // bigger than |this.columnCount_| so we shouldn't wrap.
228 wrappedBrailleArray.push(view[index - cellsPadded]);
229 this.wrappedBrailleToText_.push(
230 fixedBrailleToText[index - cellsPadded]);
231 }
232 } else {
233 if (view[index - cellsPadded] == 0) {
234 lastBreak = index;
235 }
236 wrappedBrailleArray.push(view[index - cellsPadded]);
237 this.wrappedBrailleToText_.push(
238 fixedBrailleToText[index - cellsPadded]);
239 }
240 }
241
242 // Convert the wrapped Braille Uint8 Array back to ArrayBuffer.
243 var wrappedBrailleUint8Array = new Uint8Array(wrappedBrailleArray);
244 this.wrappedBuffer_ = new ArrayBuffer(wrappedBrailleUint8Array.length);
245 new Uint8Array(this.wrappedBuffer_).set(wrappedBrailleUint8Array);
246
247 // Calculate how many lines are in the fixed and wrapped buffers.
248 this.fixedLineCount_ =
249 Math.ceil(this.fixedBuffer_.byteLength / this.columnCount_);
250 this.wrappedLineCount_ =
251 Math.ceil(this.wrappedBuffer_.byteLength / this.columnCount_);
David Tseng 2016/11/15 17:54:59 Optional: You might consider even storing a 2d arr
ultimatedbz 2016/11/15 19:31:57 Acknowledged.
81 }, 252 },
82 253
83 /** 254 /**
84 * If possible, changes the viewport to a part of the line that follows 255 * If possible, changes the viewport to a part of the line that follows
85 * the current viewport. 256 * the current viewport.
86 * @return {boolean} {@code true} if the viewport was changed. 257 * @return {boolean} {@code true} if the viewport was changed.
87 */ 258 */
88 next: function() { 259 next: function() {
260 var contentLength = this.panStrategyWrapped_ ?
261 this.wrappedLineCount_ : this.fixedLineCount_;
David Tseng 2016/11/15 17:54:59 I'm even more convinced now that you should separa
ultimatedbz 2016/11/15 19:31:57 I agree.
89 var newStart = this.viewPort_.end; 262 var newStart = this.viewPort_.end;
90 var newEnd; 263 var newEnd;
91 if (newStart + this.displaySize_ < this.contentLength_) { 264 if (newStart + this.rowCount_ - 1 < contentLength) {
92 newEnd = this.extendRight_(newStart); 265 newEnd = newStart + this.rowCount_ - 1;
93 } else { 266 } else {
94 newEnd = this.contentLength_; 267 newEnd = contentLength;
95 } 268 }
96 if (newEnd > newStart) { 269 if (newEnd > newStart) {
97 this.viewPort_ = {start: newStart, end: newEnd}; 270 this.viewPort_ = {start: newStart, end: newEnd};
98 return true; 271 return true;
99 } 272 }
100 return false; 273 return false;
101 }, 274 },
102 275
103 /** 276 /**
104 * If possible, changes the viewport to a part of the line that precedes 277 * If possible, changes the viewport to a part of the line that precedes
105 * the current viewport. 278 * the current viewport.
106 * @return {boolean} {@code true} if the viewport was changed. 279 * @return {boolean} {@code true} if the viewport was changed.
107 */ 280 */
108 previous: function() { 281 previous: function() {
282 var contentLength = this.panStrategyWrapped_ ?
283 this.wrappedLineCount_ : this.fixedLineCount_;
109 if (this.viewPort_.start > 0) { 284 if (this.viewPort_.start > 0) {
110 var newStart, newEnd; 285 var newStart, newEnd;
111 if (this.viewPort_.start <= this.displaySize_) { 286 if (this.viewPort_.start < this.rowCount_) {
112 newStart = 0; 287 newStart = 0;
113 newEnd = this.extendRight_(newStart); 288 newEnd = Math.min(this.rowCount_, contentLength);
114 } else { 289 } else {
115 newEnd = this.viewPort_.start; 290 newEnd = this.viewPort_.start;
116 var limit = newEnd - this.displaySize_; 291 newStart = newEnd - this.rowCount_ + 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 } 292 }
128 if (newStart < newEnd) { 293 if (newStart < newEnd) {
129 this.viewPort_ = {start: newStart, end: newEnd}; 294 this.viewPort_ = {start: newStart, end: newEnd};
130 return true; 295 return true;
131 } 296 }
132 } 297 }
133 return false; 298 return false;
134 }, 299 },
135 300
136 /** 301 /**
137 * Finds the end position for a new viewport start position, considering 302 * TODO need to implement for routing
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 303 * Moves the viewport so that it overlaps a target position without taking
170 * the current viewport position into consideration. 304 * the current viewport position into consideration.
171 * @param {number} position Target position. 305 * @param {number} position Target position.
172 */ 306 */
173 panToPosition_: function(position) { 307 panToPosition_: function(position) {
308 /*
David Tseng 2016/11/15 17:55:00 Don't do this, even in a work in progress cl.
ultimatedbz 2016/11/15 19:31:57 What should I do instead? The code would just brea
David Tseng 2016/11/15 21:36:38 Well, could you see how this method works and see
ultimatedbz 2016/11/15 22:25:42 Sorry, I should have clarified my question. My que
David Tseng 2016/11/16 16:56:01 As mentioned before, you're breaking things either
ultimatedbz 2016/11/19 03:11:58 Yep, this makes a lot more sense, and I'm glad we
174 if (this.displaySize_ > 0) { 309 if (this.displaySize_ > 0) {
175 this.viewPort_ = {start: 0, end: 0}; 310 this.viewPort_ = {start: 0, end: 0};
176 while (this.next() && this.viewPort_.end <= position) { 311 while (this.next() && this.viewPort_.end <= position) {
177 // Nothing to do. 312 // Nothing to do.
178 } 313 }
179 } else { 314 } else {
180 this.viewPort_ = {start: position, end: position}; 315 this.viewPort_ = {start: position, end: position};
181 } 316 }
317 */
182 }, 318 },
183 }; 319 };
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