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 this.wrappedBuffer_ = new ArrayBuffer(0); | |
| 36 | |
| 37 this.fixedBuffer_ = new ArrayBuffer(0); | |
| 38 | |
| 39 this.wrappedBrailleToText_ = []; | |
| 40 | |
| 41 this.fixedBrailleToText_ = []; | |
| 42 | |
| 43 this.panStrategyWrapped_ = false; | |
| 44 | |
| 45 this.fixedLineCount_ = 0; | |
| 46 | |
| 47 this.wrappedLineCount_ = 0; | |
|
David Tseng
2016/11/14 21:05:08
All of the members here need JS doc. Use the other
ultimatedbz
2016/11/15 00:01:17
Thanks! I actually wasn't sure how to type check.
| |
| 40 }; | 48 }; |
| 41 | 49 |
| 42 /** | 50 /** |
| 43 * A range used to represent the viewport with inclusive start and xclusive | 51 * A range used to represent the viewport with inclusive start and xclusive |
| 44 * end position. | 52 * end position. |
| 45 * @typedef {{start: number, end: number}} | 53 * @typedef {{start: number, end: number}} |
| 46 */ | 54 */ |
| 47 cvox.PanStrategy.Range; | 55 cvox.PanStrategy.Range; |
| 48 | 56 |
| 49 cvox.PanStrategy.prototype = { | 57 cvox.PanStrategy.prototype = { |
| 50 /** | 58 /** |
| 51 * Gets the current viewport which is never larger than the current | 59 * Gets the current viewport which is never larger than the current |
| 52 * display size and whose end points are always within the limits of | 60 * display size and whose end points are always within the limits of |
| 53 * the current content. | 61 * the current content. |
| 54 * @type {!cvox.PanStrategy.Range} | 62 * @type {!cvox.PanStrategy.Range} |
| 55 */ | 63 */ |
| 56 get viewPort() { | 64 get viewPort() { |
| 57 return this.viewPort_; | 65 return this.viewPort_; |
| 58 }, | 66 }, |
| 59 | 67 |
| 60 /** | 68 /** |
| 69 * @return {Array<number>} The offset of braille and text indices of the | |
| 70 * current slice. | |
| 71 */ | |
| 72 get offsetsForSlices() { | |
| 73 return [this.viewPort_.start * this.columnCount_, | |
| 74 this.brailleToText[this.viewPort_.start * this.columnCount_]]; | |
| 75 }, | |
| 76 | |
| 77 /** | |
| 78 * @return {Array<number>} The map of Braille cells to the first index of the | |
| 79 * corresponding text character. | |
| 80 */ | |
| 81 get brailleToText() { | |
| 82 if (this.panStrategyWrapped_) | |
| 83 return this.wrappedBrailleToText_; | |
| 84 else | |
| 85 return this.fixedBrailleToText_; | |
| 86 }, | |
| 87 | |
| 88 /** | |
| 89 * @return {ArrayBuffer} Buffer of the slice of braille cells within the | |
| 90 * bounds of the viewport. | |
| 91 */ | |
| 92 getCurrentBrailleSlice: function() { | |
| 93 var buf = this.panStrategyWrapped_ ? | |
| 94 this.wrappedBuffer_ : this.fixedBuffer_; | |
| 95 buf = buf.slice(this.viewPort.start * this.columnCount_, | |
| 96 (this.viewPort.end + 1) * this.columnCount_); | |
| 97 return buf; | |
| 98 }, | |
| 99 | |
| 100 /** | |
| 101 * @return {string} String of the slice of text letters corresponding with | |
| 102 * the current braille slice. | |
| 103 */ | |
| 104 getCurrentTextSlice: function() { | |
| 105 var brailleToText = this.brailleToText; | |
| 106 // Index of last braille character in slice. | |
| 107 var index = (this.viewPort.end + 1) * this.columnCount_ - 1; | |
| 108 // Last text character that is in slice. | |
| 109 var end = brailleToText[index]; | |
| 110 // Increment index until brailleToText[index] points to a different char. | |
| 111 while (index < brailleToText.length && brailleToText[index] == end) { | |
| 112 index++; | |
| 113 } | |
| 114 return this.textBuffer.substring( | |
| 115 brailleToText[this.viewPort.start * this.columnCount_], | |
| 116 brailleToText[index]); | |
| 117 }, | |
| 118 | |
| 119 /** | |
| 120 * Sets the current pan strategy and resets the viewport. | |
| 121 */ | |
| 122 setPanStrategy: function(wordWrap) { | |
| 123 this.panStrategyWrapped_ = wordWrap; | |
| 124 this.viewPort_.start = 0; | |
| 125 this.viewPort_.end = this.rowCount_ - 1; | |
| 126 }, | |
| 127 | |
| 128 /** | |
| 61 * Sets the display size. This call may update the viewport. | 129 * 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 | 130 * @param {number} rowCount the new row size, or {@code 0} if no display is |
| 63 * present. | 131 * present. |
| 132 * @param {number} columnCount the new column size, or {@code 0} | |
| 133 * if no display is present. | |
| 64 */ | 134 */ |
| 65 setDisplaySize: function(size) { | 135 setDisplaySize: function(rowCount, columnCount) { |
| 66 this.displaySize_ = size; | 136 this.rowCount_ = rowCount; |
| 67 this.panToPosition_(this.viewPort_.start); | 137 this.columnCount_ = columnCount; |
| 138 // this.setContent(this.textBuffer, this.fixedBuffer_, | |
| 139 // this.fixedBrailleToText_); | |
| 140 // TODO do I need to set the content again? | |
| 68 }, | 141 }, |
| 69 | 142 |
| 70 /** | 143 /** |
| 71 * Sets the current content that panning should happen within. This call may | 144 * Sets the internal data structures that hold the fixed and wrapped buffers |
| 72 * change the viewport. | 145 * and maps. |
| 73 * @param {!ArrayBuffer} translatedContent The new content. | 146 * @param {string} textBuffer Text of the shown braille. |
| 74 * @param {number} targetPosition Target position. The viewport is changed | 147 * @param {!ArrayBuffer} translatedContent The new braille content. |
| 75 * to overlap this position. | 148 * @param {Array<number>} fixedBrailleToText Map of Braille cells to the first |
| 149 * index of corresponding text letter. | |
| 76 */ | 150 */ |
| 77 setContent: function(translatedContent, targetPosition) { | 151 setContent: function(textBuffer, translatedContent, fixedBrailleToText) { |
| 78 this.breakPoints_ = this.calculateBreakPoints_(translatedContent); | 152 this.viewPort_.start = 0; |
| 79 this.contentLength_ = translatedContent.byteLength; | 153 this.viewPort_.end = this.rowCount_ - 1; |
| 80 this.panToPosition_(targetPosition); | 154 this.fixedBrailleToText_ = fixedBrailleToText; |
| 155 this.wrappedBrailleToText_ = []; | |
| 156 this.textBuffer = textBuffer; | |
| 157 this.fixedBuffer_ = translatedContent; | |
| 158 | |
| 159 // Convert the cells to Unicode braille pattern characters. | |
| 160 var view = new Uint8Array(translatedContent); | |
| 161 var wrappedBrailleArray = new Uint8Array(translatedContent.byteLength * 4); | |
|
dmazzoni
2016/11/14 21:37:37
Since Uint8Array can't be resized, it might make m
ultimatedbz
2016/11/15 18:04:02
I was thinking if I had to expand the Uint8array,
| |
| 162 //TODO increase size if needed. | |
| 163 | |
| 164 var lastBreak = 0; | |
| 165 var cellsPadded = 0; | |
| 166 var index; | |
| 167 for (index = 0; index < translatedContent.byteLength + cellsPadded; | |
| 168 index++) { | |
| 169 // Is index at the beginning of a new line? | |
| 170 if (index != 0 && index % this.columnCount_ == 0) { | |
| 171 if (view[index - cellsPadded] == 0) { | |
| 172 // On Delete all 0's at beginning of new line. | |
| 173 while (index - cellsPadded < view.length && | |
| 174 view[index - cellsPadded] == 0) { | |
| 175 cellsPadded--; | |
| 176 } | |
| 177 index--; | |
| 178 } else if (view[index - cellsPadded - 1] != 0 && | |
| 179 lastBreak % this.columnCount_ != 0) { | |
| 180 // If first cell is not empty, we need to move the whole word down to | |
| 181 // this line and padd to previous line with 0's, from |lastBreak| to | |
| 182 // index. The braille to text map is also updated. | |
| 183 // If lastBreak is at the beginning of a line, that means the current | |
| 184 // word is bigger than |this.columnCount_| so we shouldn't wrap. | |
| 185 for (var j = lastBreak + 1; j < index; j++) { | |
| 186 wrappedBrailleArray[j] = 0; | |
| 187 this.wrappedBrailleToText_[j] = this.wrappedBrailleToText_[j - 1]; | |
| 188 cellsPadded++; | |
| 189 } | |
| 190 lastBreak = index; | |
| 191 index--; | |
| 192 } else { | |
| 193 wrappedBrailleArray[index] = view[index - cellsPadded]; | |
| 194 this.wrappedBrailleToText_.push( | |
| 195 fixedBrailleToText[index - cellsPadded]); | |
| 196 } | |
| 197 } else { | |
| 198 if (view[index - cellsPadded] == 0) { | |
| 199 lastBreak = index; | |
| 200 } | |
| 201 wrappedBrailleArray[index] = view[index - cellsPadded]; | |
| 202 this.wrappedBrailleToText_.push( | |
| 203 fixedBrailleToText[index - cellsPadded]); | |
| 204 } | |
| 205 } | |
| 206 | |
| 207 // Convert the wrapped Braille Uint8 Array back to ArrayBuffer. | |
| 208 this.wrappedBuffer_ = new ArrayBuffer(wrappedBrailleArray.length); | |
| 209 new Uint8Array(this.wrappedBuffer_).set(wrappedBrailleArray); | |
| 210 this.wrappedBuffer_ = this.wrappedBuffer_.slice(0, index); | |
| 211 | |
| 212 // Calculate how many lines are in the fixed and wrapped buffers. | |
| 213 this.fixedLineCount_ = this.fixedBuffer_.byteLength / this.columnCount_; | |
|
dmazzoni
2016/11/14 21:37:37
How about Math.ceil(byteLength / columnCount)?
ultimatedbz
2016/11/15 18:04:02
Oh! I forgot about Math.ceil(), thanks!
| |
| 214 if (this.fixedBuffer_.byteLength % this.columnCount_ > 0) | |
| 215 this.fixedLineCount_++; | |
| 216 this.wrappedLineCount_ = | |
|
dmazzoni
2016/11/14 21:37:37
same
ultimatedbz
2016/11/15 18:04:02
Done.
| |
| 217 this.wrappedBuffer_.byteLength / this.columnCount_; | |
| 218 if (this.wrappedBuffer_.byteLength % this.columnCount_ > 0) | |
| 219 this.wrappedLineCount_++; | |
| 81 }, | 220 }, |
| 82 | 221 |
| 83 /** | 222 /** |
| 84 * If possible, changes the viewport to a part of the line that follows | 223 * If possible, changes the viewport to a part of the line that follows |
| 85 * the current viewport. | 224 * the current viewport. |
| 86 * @return {boolean} {@code true} if the viewport was changed. | 225 * @return {boolean} {@code true} if the viewport was changed. |
| 87 */ | 226 */ |
| 88 next: function() { | 227 next: function() { |
| 228 var contentLength = this.panStrategyWrapped_ ? | |
| 229 this.wrappedLineCount_ : this.fixedLineCount_; | |
| 89 var newStart = this.viewPort_.end; | 230 var newStart = this.viewPort_.end; |
| 90 var newEnd; | 231 var newEnd; |
| 91 if (newStart + this.displaySize_ < this.contentLength_) { | 232 if (newStart + this.rowCount_ - 1 < contentLength) { |
| 92 newEnd = this.extendRight_(newStart); | 233 newEnd = newStart + this.rowCount_ - 1; |
| 93 } else { | 234 } else { |
| 94 newEnd = this.contentLength_; | 235 newEnd = contentLength; |
| 95 } | 236 } |
| 96 if (newEnd > newStart) { | 237 if (newEnd > newStart) { |
| 97 this.viewPort_ = {start: newStart, end: newEnd}; | 238 this.viewPort_ = {start: newStart, end: newEnd}; |
| 98 return true; | 239 return true; |
| 99 } | 240 } |
| 100 return false; | 241 return false; |
| 101 }, | 242 }, |
| 102 | 243 |
| 103 /** | 244 /** |
| 104 * If possible, changes the viewport to a part of the line that precedes | 245 * If possible, changes the viewport to a part of the line that precedes |
| 105 * the current viewport. | 246 * the current viewport. |
| 106 * @return {boolean} {@code true} if the viewport was changed. | 247 * @return {boolean} {@code true} if the viewport was changed. |
| 107 */ | 248 */ |
| 108 previous: function() { | 249 previous: function() { |
| 250 var contentLength = this.panStrategyWrapped_ ? | |
| 251 this.wrappedLineCount_ : this.fixedLineCount_; | |
| 109 if (this.viewPort_.start > 0) { | 252 if (this.viewPort_.start > 0) { |
| 110 var newStart, newEnd; | 253 var newStart, newEnd; |
| 111 if (this.viewPort_.start <= this.displaySize_) { | 254 if (this.viewPort_.start < this.rowCount_) { |
| 112 newStart = 0; | 255 newStart = 0; |
| 113 newEnd = this.extendRight_(newStart); | 256 newEnd = Math.min(this.rowCount_, contentLength); |
| 114 } else { | 257 } else { |
| 115 newEnd = this.viewPort_.start; | 258 newEnd = this.viewPort_.start; |
| 116 var limit = newEnd - this.displaySize_; | 259 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 } | 260 } |
| 128 if (newStart < newEnd) { | 261 if (newStart < newEnd) { |
| 129 this.viewPort_ = {start: newStart, end: newEnd}; | 262 this.viewPort_ = {start: newStart, end: newEnd}; |
| 130 return true; | 263 return true; |
| 131 } | 264 } |
| 132 } | 265 } |
| 133 return false; | 266 return false; |
| 134 }, | 267 }, |
| 135 | 268 |
| 136 /** | 269 /** |
| 137 * Finds the end position for a new viewport start position, considering | 270 * TODO |
| 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 | 271 * Moves the viewport so that it overlaps a target position without taking |
| 170 * the current viewport position into consideration. | 272 * the current viewport position into consideration. |
| 171 * @param {number} position Target position. | 273 * @param {number} position Target position. |
| 172 */ | 274 */ |
| 173 panToPosition_: function(position) { | 275 panToPosition_: function(position) { |
| 276 /* | |
| 174 if (this.displaySize_ > 0) { | 277 if (this.displaySize_ > 0) { |
| 175 this.viewPort_ = {start: 0, end: 0}; | 278 this.viewPort_ = {start: 0, end: 0}; |
| 176 while (this.next() && this.viewPort_.end <= position) { | 279 while (this.next() && this.viewPort_.end <= position) { |
| 177 // Nothing to do. | 280 // Nothing to do. |
| 178 } | 281 } |
| 179 } else { | 282 } else { |
| 180 this.viewPort_ = {start: position, end: position}; | 283 this.viewPort_ = {start: position, end: position}; |
| 181 } | 284 } |
| 285 */ | |
| 182 }, | 286 }, |
| 183 }; | 287 }; |
| 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; | |
|
David Tseng
2016/11/14 21:05:08
Please don't remove these classes.
A much better
ultimatedbz
2016/11/15 00:01:17
I was thinking that if we got multiline to work, i
| |
| 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 |