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