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_
;