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.ExtraCellsSpan');
13 goog.require('cvox.LibLouis');
14 goog.require('cvox.Spannable');
15 goog.require('cvox.ValueSelectionSpan');
16 goog.require('cvox.ValueSpan');
20 * A wrapper around one or two braille translators that uses contracted
21 * braille or not based on the selection start- and end-points (if any) in the
22 * translated text. If only one translator is provided, then that translator
23 * is used for all text regardless of selection. If two translators
24 * are provided, then the uncontracted translator is used for some text
25 * around the selection end-points and the contracted translator is used
26 * for all other text. When determining what text to use uncontracted
27 * translation for around a position, a region surrounding that position
28 * containing either only whitespace characters or only non-whitespace
30 * @param {!cvox.LibLouis.Translator} defaultTranslator The translator for all
31 * text when the uncontracted translator is not used.
32 * @param {cvox.LibLouis.Translator=} opt_uncontractedTranslator
33 * Translator to use for uncontracted braille translation.
36 cvox.ExpandingBrailleTranslator =
37 function(defaultTranslator, opt_uncontractedTranslator) {
39 * @type {!cvox.LibLouis.Translator}
42 this.defaultTranslator_ = defaultTranslator;
44 * @type {cvox.LibLouis.Translator}
47 this.uncontractedTranslator_ = opt_uncontractedTranslator || null;
52 * What expansion to apply to the part of the translated string marked by the
53 * {@code cvox.ValueSpan} spannable.
56 cvox.ExpandingBrailleTranslator.ExpansionType = {
58 * Use the default translator all of the value, regardless of any selection.
59 * This is typically used when the user is in the middle of typing and the
60 * typing started outside of a word.
64 * Expand text around the selection end-points if any. If the selection is
65 * a cursor, expand the text that occupies the positions right before and
66 * after the cursor. This is typically used when the user hasn't started
67 * typing contracted braille or when editing inside a word.
71 * Expand all text covered by the value span. this is typically used when
72 * the user is editing a text field where it doesn't make sense to use
73 * contracted braille (such as a url or email address).
80 * Translates text to braille using the translator(s) provided to the
81 * constructor. See {@code cvox.LibLouis.Translator} for further details.
82 * @param {!cvox.Spannable} text Text to translate.
83 * @param {cvox.ExpandingBrailleTranslator.ExpansionType} expansionType
84 * Indicates how the text marked by a value span, if any, is expanded.
85 * @param {function(!ArrayBuffer, !Array<number>, !Array<number>)}
86 * callback Called when the translation is done. It takes resulting
87 * braille cells and positional mappings as parameters.
89 cvox.ExpandingBrailleTranslator.prototype.translate =
90 function(text, expansionType, callback) {
91 var expandRanges = this.findExpandRanges_(text, expansionType);
92 var extraCellsSpans = text.getSpansInstanceOf(cvox.ExtraCellsSpan)
93 .filter(function(span) { return span.cells.byteLength > 0;});
94 var extraCellsPositions = extraCellsSpans.map(function(span) {
95 return text.getSpanStart(span);
97 if (expandRanges.length == 0 && extraCellsSpans.length == 0) {
98 this.defaultTranslator_.translate(
100 cvox.ExpandingBrailleTranslator.nullParamsToEmptyAdapter_(
101 text.getLength(), callback));
106 function maybeAddChunkToTranslate(translator, start, end) {
108 chunks.push({translator: translator, start: start, end: end});
110 function addExtraCellsChunk(pos, cells) {
111 var chunk = {translator: null,
116 brailleToText: new Array(cells.byteLength)};
117 for (var i = 0; i < cells.byteLength; ++i)
118 chunk.brailleToText[i] = 0;
121 function addChunk(translator, start, end) {
122 while (extraCellsSpans.length > 0 && extraCellsPositions[0] <= end) {
123 maybeAddChunkToTranslate(translator, start, extraCellsPositions[0]);
124 start = extraCellsPositions.shift();
125 addExtraCellsChunk(start, extraCellsSpans.shift().cells);
127 maybeAddChunkToTranslate(translator, start, end);
130 for (var i = 0; i < expandRanges.length; ++i) {
131 var range = expandRanges[i];
132 if (lastEnd < range.start) {
133 addChunk(this.defaultTranslator_, lastEnd, range.start);
135 addChunk(this.uncontractedTranslator_, range.start, range.end);
138 addChunk(this.defaultTranslator_, lastEnd, text.getLength());
140 var chunksToTranslate = chunks.filter(function(chunk) {
141 return chunk.translator;
143 var numPendingCallbacks = chunksToTranslate.length;
145 function chunkTranslated(chunk, cells, textToBraille, brailleToText) {
147 chunk.textToBraille = textToBraille;
148 chunk.brailleToText = brailleToText;
149 if (--numPendingCallbacks <= 0)
154 var totalCells = chunks.reduce(
155 function(accum, chunk) { return accum + chunk.cells.byteLength}, 0);
156 var cells = new Uint8Array(totalCells);
158 var textToBraille = [];
159 var brailleToText = [];
160 function appendAdjusted(array, toAppend, adjustment) {
161 array.push.apply(array, toAppend.map(
162 function(elem) { return adjustment + elem; }
165 for (var i = 0, chunk; chunk = chunks[i]; ++i) {
166 cells.set(new Uint8Array(chunk.cells), cellPos);
167 appendAdjusted(textToBraille, chunk.textToBraille, cellPos);
168 appendAdjusted(brailleToText, chunk.brailleToText, chunk.start);
169 cellPos += chunk.cells.byteLength;
171 callback(cells.buffer, textToBraille, brailleToText);
174 if (chunksToTranslate.length > 0) {
175 chunksToTranslate.forEach(function(chunk) {
176 chunk.translator.translate(
177 text.toString().substring(chunk.start, chunk.end),
178 cvox.ExpandingBrailleTranslator.nullParamsToEmptyAdapter_(
179 chunk.end - chunk.start, goog.partial(chunkTranslated, chunk)));
188 * Expands a position to a range that covers the consecutive range of
189 * either whitespace or non whitespace characters around it.
190 * @param {string} str Text to look in.
191 * @param {number} pos Position to start looking at.
192 * @param {number} start Minimum value for the start position of the returned
194 * @param {number} end Maximum value for the end position of the returned
196 * @return {!cvox.ExpandingBrailleTranslator.Range_} The claculated range.
199 cvox.ExpandingBrailleTranslator.rangeForPosition_ = function(
200 str, pos, start, end) {
201 if (start < 0 || end > str.length) {
203 'End-points out of range looking for braille expansion range');
205 if (pos < start || pos >= end) {
207 'Position out of range looking for braille expansion range');
209 // Find the last chunk of either whitespace or non-whitespace before and
211 start = str.substring(start, pos + 1).search(/(\s+|\S+)$/) + start;
212 // Find the characters to include after pos, starting at pos so that
213 // they are the same kind (either whitespace or not) as the
214 // characters starting at start.
215 end = pos + /^(\s+|\S+)/.exec(str.substring(pos, end))[0].length;
216 return {start: start, end: end};
221 * Finds the ranges in which contracted braille should not be used.
222 * @param {!cvox.Spannable} text Text to find expansion ranges in.
223 * @param {cvox.ExpandingBrailleTranslator.ExpansionType} expansionType
224 * Indicates how the text marked up as the value is expanded.
225 * @return {!Array<cvox.ExpandingBrailleTranslator.Range_>} The calculated
229 cvox.ExpandingBrailleTranslator.prototype.findExpandRanges_ = function(
230 text, expansionType) {
232 if (this.uncontractedTranslator_ &&
233 expansionType != cvox.ExpandingBrailleTranslator.ExpansionType.NONE) {
234 var value = text.getSpanInstanceOf(cvox.ValueSpan);
236 // The below type casts are valid because the ranges must be valid when
237 // the span is known to exist.
238 var valueStart = /** @type {number} */ (text.getSpanStart(value));
239 var valueEnd = /** @type {number} */ (text.getSpanEnd(value));
240 switch (expansionType) {
241 case cvox.ExpandingBrailleTranslator.ExpansionType.SELECTION:
242 this.addRangesForSelection_(text, valueStart, valueEnd, result);
244 case cvox.ExpandingBrailleTranslator.ExpansionType.ALL:
245 result.push({start: valueStart, end: valueEnd});
256 * Finds ranges to expand around selection end points inside the value of
257 * a string. If any ranges are found, adds them to {@code outRanges}.
258 * @param {cvox.Spannable} text Text to find ranges in.
259 * @param {number} valueStart Start of the value in {@code text}.
260 * @param {number} valueEnd End of the value in {@code text}.
261 * @param {Array<cvox.ExpandingBrailleTranslator.Range_>} outRanges
262 * Destination for the expansion ranges. Untouched if no ranges
263 * are found. Note that ranges may be coalesced.
266 cvox.ExpandingBrailleTranslator.prototype.addRangesForSelection_ = function(
267 text, valueStart, valueEnd, outRanges) {
268 var selection = text.getSpanInstanceOf(cvox.ValueSelectionSpan);
272 var selectionStart = text.getSpanStart(selection);
273 var selectionEnd = text.getSpanEnd(selection);
274 if (selectionStart < valueStart || selectionEnd > valueEnd) {
277 var expandPositions = [];
278 if (selectionStart == valueEnd) {
279 if (selectionStart > valueStart) {
280 expandPositions.push(selectionStart - 1);
283 if (selectionStart == selectionEnd && selectionStart > valueStart) {
284 expandPositions.push(selectionStart - 1);
286 expandPositions.push(selectionStart);
287 // Include the selection end if the length of the selection is
288 // greater than one (otherwise this position would be redundant).
289 if (selectionEnd > selectionStart + 1) {
290 // Look at the last actual character of the selection, not the
291 // character at the (exclusive) end position.
292 expandPositions.push(selectionEnd - 1);
296 var lastRange = outRanges[outRanges.length - 1] || null;
297 for (var i = 0; i < expandPositions.length; ++i) {
298 var range = cvox.ExpandingBrailleTranslator.rangeForPosition_(
299 text.toString(), expandPositions[i], valueStart, valueEnd);
300 if (lastRange && lastRange.end >= range.start) {
301 lastRange.end = range.end;
303 outRanges.push(range);
311 * Adapts {@code callback} to accept null arguments and treat them as if the
312 * translation result is empty.
313 * @param {number} inputLength Length of the input to the translation.
314 * Used for populating {@code textToBraille} if null.
315 * @param {function(!ArrayBuffer, !Array<number>, !Array<number>)} callback
316 * The callback to adapt.
317 * @return {function(ArrayBuffer, Array<number>, Array<number>)}
318 * An adapted version of the callback.
321 cvox.ExpandingBrailleTranslator.nullParamsToEmptyAdapter_ =
322 function(inputLength, callback) {
323 return function(cells, textToBraille, brailleToText) {
324 if (!textToBraille) {
325 textToBraille = new Array(inputLength);
326 for (var i = 0; i < inputLength; ++i) {
327 textToBraille[i] = 0;
330 callback(cells || new ArrayBuffer(0),
332 brailleToText || []);
338 * A character range with inclusive start and exclusive end positions.
339 * @typedef {{start: number, end: number}}
342 cvox.ExpandingBrailleTranslator.Range_;