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 Puts text on a braille display.
10 goog
.provide('cvox.BrailleDisplayManager');
12 goog
.require('cvox.BrailleCaptionsBackground');
13 goog
.require('cvox.BrailleDisplayState');
14 goog
.require('cvox.ExpandingBrailleTranslator');
15 goog
.require('cvox.LibLouis');
16 goog
.require('cvox.NavBraille');
17 goog
.require('cvox.PanStrategy');
21 * @param {!cvox.BrailleTranslatorManager} translatorManager Keeps track
22 * of the current translator to use.
25 cvox
.BrailleDisplayManager = function(translatorManager
) {
27 * @type {!cvox.BrailleTranslatorManager}
30 this.translatorManager_
= translatorManager
;
32 * @type {!cvox.NavBraille}
35 this.content_
= new cvox
.NavBraille({});
37 * @type {!cvox.ExpandingBrailleTranslator.ExpansionType} valueExpansion
41 cvox
.ExpandingBrailleTranslator
.ExpansionType
.SELECTION
;
43 * @type {!ArrayBuffer}
46 this.translatedContent_
= new ArrayBuffer(0);
48 * @type {!ArrayBuffer}
51 this.displayedContent_
= this.translatedContent_
;
53 * @type {cvox.PanStrategy}
56 this.panStrategy_
= new cvox
.WrappingPanStrategy();
58 * @type {function(!cvox.BrailleKeyEvent, cvox.NavBraille)}
61 this.commandListener_ = function() {};
63 * Current display state used for width calculations. This is different from
64 * realDisplayState_ if the braille captions feature is enabled and there is
65 * no hardware display connected. Otherwise, it is the same object
66 * as realDisplayState_.
67 * @type {!cvox.BrailleDisplayState}
70 this.displayState_
= {available
: false, textCellCount
: undefined};
72 * State reported from the chrome api, reflecting a real hardware
74 * @type {!cvox.BrailleDisplayState}
77 this.realDisplayState_
= this.displayState_
;
79 * @type {!Array<number>}
82 this.textToBraille_
= [];
84 * @type {!Array<number>}
87 this.brailleToText_
= [];
89 translatorManager
.addChangeListener(function() {
90 this.translateContent_(this.content_
, this.expansionType_
);
93 chrome
.storage
.onChanged
.addListener(function(changes
, area
) {
94 if (area
== 'local' && 'brailleWordWrap' in changes
) {
95 this.updatePanStrategy_(changes
.brailleWordWrap
.newValue
);
98 chrome
.storage
.local
.get({brailleWordWrap
: true}, function(items
) {
99 this.updatePanStrategy_(items
.brailleWordWrap
);
102 cvox
.BrailleCaptionsBackground
.init(goog
.bind(
103 this.onCaptionsStateChanged_
, this));
104 if (goog
.isDef(chrome
.brailleDisplayPrivate
)) {
105 var onDisplayStateChanged
= goog
.bind(this.refreshDisplayState_
, this);
106 chrome
.brailleDisplayPrivate
.getDisplayState(onDisplayStateChanged
);
107 chrome
.brailleDisplayPrivate
.onDisplayStateChanged
.addListener(
108 onDisplayStateChanged
);
109 chrome
.brailleDisplayPrivate
.onKeyEvent
.addListener(
110 goog
.bind(this.onKeyEvent_
, this));
112 // Get the initial captions state since we won't refresh the display
113 // state in an API callback in this case.
114 this.onCaptionsStateChanged_();
120 * Dots representing a cursor.
124 cvox
.BrailleDisplayManager
.CURSOR_DOTS_
= 1 << 6 | 1 << 7;
128 * @param {!cvox.NavBraille} content Content to send to the braille display.
129 * @param {!cvox.ExpandingBrailleTranslator.ExpansionType} expansionType
130 * If the text has a {@code ValueSpan}, this indicates how that part
131 * of the display content is expanded when translating to braille.
132 * (See {@code cvox.ExpandingBrailleTranslator}).
134 cvox
.BrailleDisplayManager
.prototype.setContent = function(
135 content
, expansionType
) {
136 this.translateContent_(content
, expansionType
);
141 * Sets the command listener. When a command is invoked, the listener will be
142 * called with the BrailleKeyEvent corresponding to the command and the content
143 * that was present on the display when the command was invoked. The content
144 * is guaranteed to be identical to an object previously used as the parameter
145 * to cvox.BrailleDisplayManager.setContent, or null if no content was set.
146 * @param {function(!cvox.BrailleKeyEvent, cvox.NavBraille)} func The listener.
148 cvox
.BrailleDisplayManager
.prototype.setCommandListener = function(func
) {
149 this.commandListener_
= func
;
154 * @param {!cvox.BrailleDisplayState} newState Display state reported
155 * by the extension API.
158 cvox
.BrailleDisplayManager
.prototype.refreshDisplayState_ = function(
160 var oldSize
= this.displayState_
.textCellCount
|| 0;
161 this.realDisplayState_
= newState
;
162 if (newState
.available
) {
163 this.displayState_
= newState
;
166 cvox
.BrailleCaptionsBackground
.getVirtualDisplayState();
168 var newSize
= this.displayState_
.textCellCount
|| 0;
169 if (oldSize
!= newSize
) {
170 this.panStrategy_
.setDisplaySize(newSize
);
177 * Called when the state of braille captions changes.
180 cvox
.BrailleDisplayManager
.prototype.onCaptionsStateChanged_ = function() {
181 // Force reevaluation of the display state based on our stored real
182 // hardware display state, meaning that if a real display is connected,
183 // that takes precedence over the state from the captions 'virtual' display.
184 this.refreshDisplayState_(this.realDisplayState_
);
189 cvox
.BrailleDisplayManager
.prototype.refresh_ = function() {
190 if (!this.displayState_
.available
) {
193 var viewPort
= this.panStrategy_
.viewPort
;
194 var buf
= this.displayedContent_
.slice(viewPort
.start
, viewPort
.end
);
195 if (this.realDisplayState_
.available
) {
196 chrome
.brailleDisplayPrivate
.writeDots(buf
);
198 if (cvox
.BrailleCaptionsBackground
.isEnabled()) {
199 var start
= this.brailleToTextPosition_(viewPort
.start
);
200 var end
= this.brailleToTextPosition_(viewPort
.end
);
201 cvox
.BrailleCaptionsBackground
.setContent(
202 this.content_
.text
.toString().substring(start
, end
), buf
);
208 * @param {!cvox.NavBraille} newContent New display content.
209 * @param {cvox.ExpandingBrailleTranslator.ExpansionType} newExpansionType
210 * How the value part of of the new content should be expanded
211 * with regards to contractions.
214 cvox
.BrailleDisplayManager
.prototype.translateContent_ = function(
215 newContent
, newExpansionType
) {
216 var writeTranslatedContent = function(cells
, textToBraille
, brailleToText
) {
217 this.content_
= newContent
;
218 this.expansionType_
= newExpansionType
;
219 this.textToBraille_
= textToBraille
;
220 this.brailleToText_
= brailleToText
;
221 var startIndex
= this.content_
.startIndex
;
222 var endIndex
= this.content_
.endIndex
;
224 if (startIndex
>= 0) {
225 var translatedStartIndex
;
226 var translatedEndIndex
;
227 if (startIndex
>= textToBraille
.length
) {
228 // Allow the cells to be extended with one extra cell for
229 // a carret after the last character.
230 var extCells
= new ArrayBuffer(cells
.byteLength
+ 1);
231 new Uint8Array(extCells
).set(new Uint8Array(cells
));
232 // Last byte is initialized to 0.
234 translatedStartIndex
= cells
.byteLength
- 1;
236 translatedStartIndex
= textToBraille
[startIndex
];
238 if (endIndex
>= textToBraille
.length
) {
239 // endIndex can't be past-the-end of the last cell unless
240 // startIndex is too, so we don't have to do another
242 translatedEndIndex
= cells
.byteLength
;
244 translatedEndIndex
= textToBraille
[endIndex
];
246 this.translatedContent_
= cells
;
247 // Copy the transalted content to a separate buffer and add the cursor
249 this.displayedContent_
= new ArrayBuffer(cells
.byteLength
);
250 new Uint8Array(this.displayedContent_
).set(new Uint8Array(cells
));
251 this.writeCursor_(this.displayedContent_
,
252 translatedStartIndex
, translatedEndIndex
);
253 targetPosition
= translatedStartIndex
;
255 this.translatedContent_
= this.displayedContent_
= cells
;
258 this.panStrategy_
.setContent(this.translatedContent_
, targetPosition
);
262 var translator
= this.translatorManager_
.getExpandingTranslator();
264 writeTranslatedContent(new ArrayBuffer(0), [], []);
266 translator
.translate(
269 writeTranslatedContent
);
275 * @param {cvox.BrailleKeyEvent} event The key event.
278 cvox
.BrailleDisplayManager
.prototype.onKeyEvent_ = function(event
) {
279 switch (event
.command
) {
280 case cvox
.BrailleKeyCommand
.PAN_LEFT
:
283 case cvox
.BrailleKeyCommand
.PAN_RIGHT
:
286 case cvox
.BrailleKeyCommand
.ROUTING
:
287 event
.displayPosition
= this.brailleToTextPosition_(
288 event
.displayPosition
+ this.panStrategy_
.viewPort
.start
);
291 this.commandListener_(event
, this.content_
);
298 * Shift the display by one full display size and refresh the content.
299 * Sends the appropriate command if the display is already at the leftmost
303 cvox
.BrailleDisplayManager
.prototype.panLeft_ = function() {
304 if (this.panStrategy_
.previous()) {
307 this.commandListener_({
308 command
: cvox
.BrailleKeyCommand
.PAN_LEFT
315 * Shifts the display position to the right by one full display size and
316 * refreshes the content. Sends the appropriate command if the display is
317 * already at its rightmost position.
320 cvox
.BrailleDisplayManager
.prototype.panRight_ = function() {
321 if (this.panStrategy_
.next()) {
324 this.commandListener_({
325 command
: cvox
.BrailleKeyCommand
.PAN_RIGHT
332 * Writes a cursor in the specified range into translated content.
333 * @param {ArrayBuffer} buffer Buffer to add cursor to.
334 * @param {number} startIndex The start index to place the cursor.
335 * @param {number} endIndex The end index to place the cursor (exclusive).
338 cvox
.BrailleDisplayManager
.prototype.writeCursor_ = function(
339 buffer
, startIndex
, endIndex
) {
340 if (startIndex
< 0 || startIndex
>= buffer
.byteLength
||
341 endIndex
< startIndex
|| endIndex
> buffer
.byteLength
) {
344 if (startIndex
== endIndex
) {
345 endIndex
= startIndex
+ 1;
347 var dataView
= new DataView(buffer
);
348 while (startIndex
< endIndex
) {
349 var value
= dataView
.getUint8(startIndex
);
350 value
|= cvox
.BrailleDisplayManager
.CURSOR_DOTS_
;
351 dataView
.setUint8(startIndex
, value
);
358 * Returns the text position corresponding to an absolute braille position,
359 * that is not accounting for the current pan position.
361 * @param {number} braillePosition Braille position relative to the startof
362 * the translated content.
363 * @return {number} The mapped position in code units.
365 cvox
.BrailleDisplayManager
.prototype.brailleToTextPosition_
=
366 function(braillePosition
) {
367 var mapping
= this.brailleToText_
;
368 if (braillePosition
< 0) {
369 // This shouldn't happen.
370 console
.error('WARNING: Braille position < 0: ' + braillePosition
);
372 } else if (braillePosition
>= mapping
.length
) {
373 // This happens when the user clicks on the right part of the display
374 // when it is not entirely filled with content. Allow addressing the
375 // position after the last character.
376 return this.content_
.text
.getLength();
378 return mapping
[braillePosition
];
384 * @param {boolean} wordWrap
387 cvox
.BrailleDisplayManager
.prototype.updatePanStrategy_ = function(wordWrap
) {
388 var newStrategy
= wordWrap
? new cvox
.WrappingPanStrategy() :
389 new cvox
.FixedPanStrategy();
390 newStrategy
.setDisplaySize(this.displayState_
.textCellCount
|| 0);
391 newStrategy
.setContent(this.translatedContent_
,
392 this.panStrategy_
.viewPort
.start
);
393 this.panStrategy_
= newStrategy
;