Chromium Code Reviews| OLD | NEW |
|---|---|
| 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 }; | |
| OLD | NEW |