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 Translates text to braille, optionally with some parts
10 goog.provide('cvox.ExpandingBrailleTranslator');
12 goog.require('cvox.LibLouis');
13 goog.require('cvox.Spannable');
14 goog.require('cvox.ValueSelectionSpan');
15 goog.require('cvox.ValueSpan');
19 * A wrapper around one or two braille translators that uses contracted
20 * braille or not based on the selection start- and end-points (if any) in the
21 * translated text. If only one translator is provided, then that translator
22 * is used for all text regardless of selection. If two translators
23 * are provided, then the uncontracted translator is used for some text
24 * around the selection end-points and the contracted translator is used
25 * for all other text. When determining what text to use uncontracted
26 * translation for around a position, a region surrounding that position
27 * containing either only whitespace characters or only non-whitespace
29 * @param {!cvox.LibLouis.Translator} defaultTranslator The translator for all
30 * text when the uncontracted translator is not used.
31 * @param {cvox.LibLouis.Translator=} opt_uncontractedTranslator
32 * Translator to use for uncontracted braille translation.
35 cvox.ExpandingBrailleTranslator =
36 function(defaultTranslator, opt_uncontractedTranslator) {
38 * @type {!cvox.LibLouis.Translator}
41 this.defaultTranslator_ = defaultTranslator;
43 * @type {cvox.LibLouis.Translator}
46 this.uncontractedTranslator_ = opt_uncontractedTranslator || null;
51 * What expansion to apply to the part of the translated string marked by the
52 * {@code cvox.ValueSpan} spannable.
55 cvox.ExpandingBrailleTranslator.ExpansionType = {
57 * Use the default translator all of the value, regardless of any selection.
58 * This is typically used when the user is in the middle of typing and the
59 * typing started outside of a word.
63 * Expand text around the selection end-points if any. If the selection is
64 * a cursor, expand the text that occupies the positions right before and
65 * after the cursor. This is typically used when the user hasn't started
66 * typing contracted braille or when editing inside a word.
70 * Expand all text covered by the value span. this is typically used when
71 * the user is editing a text field where it doesn't make sense to use
72 * contracted braille (such as a url or email address).
79 * Translates text to braille using the translator(s) provided to the
80 * constructor. See {@code cvox.LibLouis.Translator} for further details.
81 * @param {!cvox.Spannable} text Text to translate.
82 * @param {cvox.ExpandingBrailleTranslator.ExpansionType} expansionType
83 * Indicates how the text marked by a value span, if any, is expanded.
84 * @param {function(!ArrayBuffer, !Array<number>, !Array<number>)}
85 * callback Called when the translation is done. It takes resulting
86 * braille cells and positional mappings as parameters.
88 cvox.ExpandingBrailleTranslator.prototype.translate =
89 function(text, expansionType, callback) {
90 var expandRanges = this.findExpandRanges_(text, expansionType);
91 if (expandRanges.length == 0) {
92 this.defaultTranslator_.translate(
94 cvox.ExpandingBrailleTranslator.nullParamsToEmptyAdapter_(
95 text.getLength(), callback));
100 function addChunk(translator, start, end) {
101 chunks.push({translator: translator, start: start, end: end});
104 for (var i = 0; i < expandRanges.length; ++i) {
105 var range = expandRanges[i];
106 if (lastEnd < range.start) {
107 addChunk(this.defaultTranslator_, lastEnd, range.start);
109 addChunk(this.uncontractedTranslator_, range.start, range.end);
112 if (lastEnd < text.getLength()) {
113 addChunk(this.defaultTranslator_, lastEnd, text.getLength());
116 var numPendingCallbacks = chunks.length;
118 function chunkTranslated(chunk, cells, textToBraille, brailleToText) {
120 chunk.textToBraille = textToBraille;
121 chunk.brailleToText = brailleToText;
122 if (--numPendingCallbacks <= 0) {
128 var totalCells = chunks.reduce(
129 function(accum, chunk) { return accum + chunk.cells.byteLength}, 0);
130 var cells = new Uint8Array(totalCells);
132 var textToBraille = [];
133 var brailleToText = [];
134 function appendAdjusted(array, toAppend, adjustment) {
135 array.push.apply(array, toAppend.map(
136 function(elem) { return adjustment + elem; }
139 for (var i = 0, chunk; chunk = chunks[i]; ++i) {
140 cells.set(new Uint8Array(chunk.cells), cellPos);
141 appendAdjusted(textToBraille, chunk.textToBraille, cellPos);
142 appendAdjusted(brailleToText, chunk.brailleToText, chunk.start);
143 cellPos += chunk.cells.byteLength;
145 callback(cells.buffer, textToBraille, brailleToText);
148 for (var i = 0, chunk; chunk = chunks[i]; ++i) {
149 chunk.translator.translate(
150 text.toString().substring(chunk.start, chunk.end),
151 cvox.ExpandingBrailleTranslator.nullParamsToEmptyAdapter_(
152 chunk.end - chunk.start, goog.partial(chunkTranslated, chunk)));
158 * Expands a position to a range that covers the consecutive range of
159 * either whitespace or non whitespace characters around it.
160 * @param {string} str Text to look in.
161 * @param {number} pos Position to start looking at.
162 * @param {number} start Minimum value for the start position of the returned
164 * @param {number} end Maximum value for the end position of the returned
166 * @return {!cvox.ExpandingBrailleTranslator.Range_} The claculated range.
169 cvox.ExpandingBrailleTranslator.rangeForPosition_ = function(
170 str, pos, start, end) {
171 if (start < 0 || end > str.length) {
173 'End-points out of range looking for braille expansion range');
175 if (pos < start || pos >= end) {
177 'Position out of range looking for braille expansion range');
179 // Find the last chunk of either whitespace or non-whitespace before and
181 var start = str.substring(start, pos + 1).search(/(\s+|\S+)$/) + start;
182 // Find the characters to include after pos, starting at pos so that
183 // they are the same kind (either whitespace or not) as the
184 // characters starting at start.
185 var end = pos + /^(\s+|\S+)/.exec(str.substring(pos, end))[0].length;
186 return {start: start, end: end};
191 * Finds the ranges in which contracted braille should not be used.
192 * @param {!cvox.Spannable} text Text to find expansion ranges in.
193 * @param {cvox.ExpandingBrailleTranslator.ExpansionType} expansionType
194 * Indicates how the text marked up as the value is expanded.
195 * @return {!Array<cvox.ExpandingBrailleTranslator.Range_>} The calculated
199 cvox.ExpandingBrailleTranslator.prototype.findExpandRanges_ = function(
200 text, expansionType) {
202 if (this.uncontractedTranslator_ &&
203 expansionType != cvox.ExpandingBrailleTranslator.ExpansionType.NONE) {
204 var value = text.getSpanInstanceOf(cvox.ValueSpan);
206 // The below type casts are valid because the ranges must be valid when
207 // the span is known to exist.
208 var valueStart = /** @type {number} */ (text.getSpanStart(value));
209 var valueEnd = /** @type {number} */ (text.getSpanEnd(value));
210 switch (expansionType) {
211 case cvox.ExpandingBrailleTranslator.ExpansionType.SELECTION:
212 this.addRangesForSelection_(text, valueStart, valueEnd, result);
214 case cvox.ExpandingBrailleTranslator.ExpansionType.ALL:
215 result.push({start: valueStart, end: valueEnd});
226 * Finds ranges to expand around selection end points inside the value of
227 * a string. If any ranges are found, adds them to {@code outRanges}.
228 * @param {cvox.Spannable} text Text to find ranges in.
229 * @param {number} valueStart Start of the value in {@code text}.
230 * @param {number} valueEnd End of the value in {@code text}.
231 * @param {Array<cvox.ExpandingBrailleTranslator.Range_>} outRanges
232 * Destination for the expansion ranges. Untouched if no ranges
233 * are found. Note that ranges may be coalesced.
236 cvox.ExpandingBrailleTranslator.prototype.addRangesForSelection_ = function(
237 text, valueStart, valueEnd, outRanges) {
238 var selection = text.getSpanInstanceOf(cvox.ValueSelectionSpan);
242 var selectionStart = text.getSpanStart(selection);
243 var selectionEnd = text.getSpanEnd(selection);
244 if (selectionStart < valueStart || selectionEnd > valueEnd) {
247 var expandPositions = [];
248 if (selectionStart == valueEnd) {
249 if (selectionStart > valueStart) {
250 expandPositions.push(selectionStart - 1);
253 if (selectionStart == selectionEnd && selectionStart > valueStart) {
254 expandPositions.push(selectionStart - 1);
256 expandPositions.push(selectionStart);
257 // Include the selection end if the length of the selection is
258 // greater than one (otherwise this position would be redundant).
259 if (selectionEnd > selectionStart + 1) {
260 // Look at the last actual character of the selection, not the
261 // character at the (exclusive) end position.
262 expandPositions.push(selectionEnd - 1);
266 var lastRange = outRanges[outRanges.length - 1] || null;
267 for (var i = 0; i < expandPositions.length; ++i) {
268 var range = cvox.ExpandingBrailleTranslator.rangeForPosition_(
269 text.toString(), expandPositions[i], valueStart, valueEnd);
270 if (lastRange && lastRange.end >= range.start) {
271 lastRange.end = range.end;
273 outRanges.push(range);
281 * Adapts {@code callback} to accept null arguments and treat them as if the
282 * translation result is empty.
283 * @param {number} inputLength Length of the input to the translation.
284 * Used for populating {@code textToBraille} if null.
285 * @param {function(!ArrayBuffer, !Array<number>, !Array<number>)} callback
286 * The callback to adapt.
287 * @return {function(ArrayBuffer, Array<number>, Array<number>)}
288 * An adapted version of the callback.
291 cvox.ExpandingBrailleTranslator.nullParamsToEmptyAdapter_ =
292 function(inputLength, callback) {
293 return function(cells, textToBraille, brailleToText) {
294 if (!textToBraille) {
295 textToBraille = new Array(inputLength);
296 for (var i = 0; i < inputLength; ++i) {
297 textToBraille[i] = 0;
300 callback(cells || new ArrayBuffer(0),
302 brailleToText || []);
308 * A character range with inclusive start and exclusive end positions.
309 * @typedef {{start: number, end: number}}
312 cvox.ExpandingBrailleTranslator.Range_;