expanding_braille_translator.js revision cedac228d2dd51db4b79ea1e72c7f249408ee061
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.
4
5/**
6 * @fileoverview Translates text to braille, optionally with some parts
7 * uncontracted.
8 */
9
10goog.provide('cvox.ExpandingBrailleTranslator');
11
12goog.require('cvox.BrailleUtil');
13goog.require('cvox.LibLouis');
14goog.require('cvox.Spannable');
15
16
17/**
18 * A wrapper around one or two braille translators that uses contracted
19 * braille or not based on the selection start- and end-points (if any) in the
20 * translated text.  If only one translator is provided, then that translator
21 * is used for all text regardless of selection.  If two translators
22 * are provided, then the uncontracted translator is used for some text
23 * around the selection end-points and the contracted translator is used
24 * for all other text.  When determining what text to use uncontracted
25 * translation for around a position, a region surrounding that position
26 * containing either only whitespace characters or only non-whitespace
27 * characters is used.
28 * @param {!cvox.LibLouis.Translator} defaultTranslator The translator for all
29 *     text when the uncontracted translator is not used.
30 * @param {cvox.LibLouis.Translator=} opt_uncontractedTranslator
31 *     Translator to use for uncontracted braille translation.
32 * @constructor
33 */
34cvox.ExpandingBrailleTranslator =
35    function(defaultTranslator, opt_uncontractedTranslator) {
36  /**
37   * @type {!cvox.LibLouis.Translator}
38   * @private
39   */
40  this.defaultTranslator_ = defaultTranslator;
41  /**
42   * @type {cvox.LibLouis.Translator}
43   * @private
44   */
45  this.uncontractedTranslator_ = opt_uncontractedTranslator || null;
46};
47
48
49/**
50 * Translates text to braille using the translator(s) provided to the
51 * constructor.  See {@code cvox.LibLouis.Translator} for further details.
52 * @param {!cvox.Spannable} text Text to translate.
53 * @param {function(!ArrayBuffer, !Array.<number>, !Array.<number>)}
54 *     callback Called when the translation is done.  It takes resulting
55 *         braille cells and positional mappings as parameters.
56 */
57cvox.ExpandingBrailleTranslator.prototype.translate =
58    function(text, callback) {
59  var expandRanges = this.findExpandRanges_(text);
60  if (expandRanges.length == 0) {
61    this.defaultTranslator_.translate(
62        text.toString(),
63        cvox.ExpandingBrailleTranslator.nullParamsToEmptyAdapter_(
64            text.getLength(), callback));
65    return;
66  }
67
68  var chunks = [];
69  function addChunk(translator, start, end) {
70    chunks.push({translator: translator, start: start, end: end});
71  }
72  var lastEnd = 0;
73  for (var i = 0; i < expandRanges.length; ++i) {
74    var range = expandRanges[i];
75    if (lastEnd < range.start) {
76      addChunk(this.defaultTranslator_, lastEnd, range.start);
77    }
78    addChunk(this.uncontractedTranslator_, range.start, range.end);
79    lastEnd = range.end;
80  }
81  if (lastEnd < text.getLength()) {
82    addChunk(this.defaultTranslator_, lastEnd, text.getLength());
83  }
84
85  var numPendingCallbacks = chunks.length;
86
87  function chunkTranslated(chunk, cells, textToBraille, brailleToText) {
88    chunk.cells = cells;
89    chunk.textToBraille = textToBraille;
90    chunk.brailleToText = brailleToText;
91    if (--numPendingCallbacks <= 0) {
92      finish();
93    }
94  }
95
96  function finish() {
97    var totalCells = chunks.reduce(
98        function(accum, chunk) { return accum + chunk.cells.byteLength}, 0);
99    var cells = new Uint8Array(totalCells);
100    var cellPos = 0;
101    var textToBraille = [];
102    var brailleToText = [];
103    function appendAdjusted(array, toAppend, adjustment) {
104      array.push.apply(array, toAppend.map(
105          function(elem) { return adjustment + elem; }
106          ));
107    }
108    for (var i = 0, chunk; chunk = chunks[i]; ++i) {
109      cells.set(new Uint8Array(chunk.cells), cellPos);
110      appendAdjusted(textToBraille, chunk.textToBraille, cellPos);
111      appendAdjusted(brailleToText, chunk.brailleToText, chunk.start);
112      cellPos += chunk.cells.byteLength;
113    }
114    callback(cells.buffer, textToBraille, brailleToText);
115  }
116
117  for (var i = 0, chunk; chunk = chunks[i]; ++i) {
118    chunk.translator.translate(
119        text.toString().substring(chunk.start, chunk.end),
120        cvox.ExpandingBrailleTranslator.nullParamsToEmptyAdapter_(
121            chunk.end - chunk.start, goog.partial(chunkTranslated, chunk)));
122  }
123};
124
125
126/**
127 * Expands a position to a range that covers the consecutive range of
128 * either whitespace or non whitespace characters around it.
129 * @param {string} str Text to look in.
130 * @param {number} pos Position to start looking at.
131 * @return {!cvox.ExpandingBrailleTranslator.Range_}
132 * @private
133 */
134cvox.ExpandingBrailleTranslator.rangeForPosition_ = function(str, pos) {
135  if (pos < 0 || pos >= str.length) {
136    throw Error('Position out of range looking for braille expansion range');
137  }
138  // Find the last chunk of either whitespace or non-whitespace before and
139  // including pos.
140  var start = str.substring(0, pos + 1).search(/(\s+|\S+)$/);
141  // Find the characters to include after pos, starting at pos so that
142  // they are the same kind (either whitespace or not) as the
143  // characters starting at start.
144  var end = pos + /^(\s+|\S+)/.exec(str.substring(pos))[0].length;
145  return {start: start, end: end};
146};
147
148
149/**
150 * Finds the ranges in which contracted braille should not be used.
151 * @param {!cvox.Spannable} text Text to find expansion ranges in.
152 * @return {!Array.<cvox.ExpandingBrailleTranslator.Range_>}
153 * @private
154 */
155cvox.ExpandingBrailleTranslator.prototype.findExpandRanges_ = function(text) {
156  var result = [];
157  if (this.uncontractedTranslator_) {
158    var selection = text.getSpanInstanceOf(cvox.BrailleUtil.ValueSelectionSpan);
159    if (selection) {
160      var selectionStart = text.getSpanStart(selection);
161      var selectionEnd = text.getSpanEnd(selection);
162      if (selectionStart < text.getLength()) {
163        var expandPositions = [selectionStart];
164        // Include the selection end if the length of the selection is greater
165        // than one (otherwise this position would be redundant).
166        if (selectionEnd > selectionStart + 1) {
167          // Look at the last actual character of the selection, not the
168          // character at the (exclusive) end position.
169          expandPositions.push(selectionEnd - 1);
170        }
171
172        var lastRange = null;
173        for (var i = 0; i < expandPositions.length; ++i) {
174          var range = cvox.ExpandingBrailleTranslator.rangeForPosition_(
175              text.toString(), expandPositions[i]);
176          if (lastRange && lastRange.end >= range.start) {
177            lastRange.end = range.end;
178          } else {
179            result.push(range);
180            lastRange = range;
181          }
182        }
183      }
184    }
185  }
186
187  return result;
188};
189
190
191/**
192 * Adapts {@code callback} to accept null arguments and treat them as if the
193 * translation result is empty.
194 * @param {number} inputLength Length of the input to the translation.
195 *     Used for populating {@code textToBraille} if null.
196 * @param {function(!ArrayBuffer, !Array.<number>, !Array.<number>)} callback
197 * @return {function(ArrayBuffer, Array.<number>, Array.<number>)}
198 * @private
199 */
200cvox.ExpandingBrailleTranslator.nullParamsToEmptyAdapter_ =
201    function(inputLength, callback) {
202  return function(cells, textToBraille, brailleToText) {
203    if (!textToBraille) {
204      textToBraille = new Array(inputLength);
205      for (var i = 0; i < inputLength; ++i) {
206        textToBraille[i] = 0;
207      }
208    }
209    callback(cells || new ArrayBuffer(0),
210             textToBraille,
211             brailleToText || []);
212  };
213};
214
215
216/**
217 * A character range with inclusive start and exclusive end positions.
218 * @typedef {{start: number, end: number}}
219 * @private
220 */
221cvox.ExpandingBrailleTranslator.Range_;
222