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;