diff options
Diffstat (limited to 'chromium/chrome/browser/resources/chromeos/chromevox/host/interface/abstract_tts.js')
-rw-r--r-- | chromium/chrome/browser/resources/chromeos/chromevox/host/interface/abstract_tts.js | 616 |
1 files changed, 616 insertions, 0 deletions
diff --git a/chromium/chrome/browser/resources/chromeos/chromevox/host/interface/abstract_tts.js b/chromium/chrome/browser/resources/chromeos/chromevox/host/interface/abstract_tts.js new file mode 100644 index 00000000000..d17f1cc88c5 --- /dev/null +++ b/chromium/chrome/browser/resources/chromeos/chromevox/host/interface/abstract_tts.js @@ -0,0 +1,616 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +/** + * @fileoverview Base class for Text-to-Speech engines that actually transform + * text to speech. + * + */ + +goog.provide('cvox.AbstractTts'); + +goog.require('cvox.TtsInterface'); +goog.require('goog.i18n.MessageFormat'); + +/** + * Creates a new instance. + * @constructor + * @implements {cvox.TtsInterface} + */ +cvox.AbstractTts = function() { + this.ttsProperties = new Object(); + + /** + * Default value for TTS properties. + * Note that these as well as the subsequent properties might be different + * on different host platforms (like Chrome, Android, etc.). + * @type {{pitch : number, + * rate: number, + * volume: number}} + * @protected + */ + this.propertyDefault = { + 'rate': 0.5, + 'pitch': 0.5, + 'volume': 0.5 + }; + + /** + * Min value for TTS properties. + * @type {{pitch : number, + * rate: number, + * volume: number}} + * @protected + */ + this.propertyMin = { + 'rate': 0.0, + 'pitch': 0.0, + 'volume': 0.0 + }; + + /** + * Max value for TTS properties. + * @type {{pitch : number, + * rate: number, + * volume: number}} + * @protected + */ + this.propertyMax = { + 'rate': 1.0, + 'pitch': 1.0, + 'volume': 1.0 + }; + + /** + * Step value for TTS properties. + * @type {{pitch : number, + * rate: number, + * volume: number}} + * @protected + */ + this.propertyStep = { + 'rate': 0.1, + 'pitch': 0.1, + 'volume': 0.1 + }; + + + /** @private */ + + if (cvox.AbstractTts.pronunciationDictionaryRegexp_ == undefined) { + // Create an expression that matches all words in the pronunciation + // dictionary on word boundaries, ignoring case. + var words = []; + for (var word in cvox.AbstractTts.PRONUNCIATION_DICTIONARY) { + words.push(word); + } + var expr = '\\b(' + words.join('|') + ')\\b'; + cvox.AbstractTts.pronunciationDictionaryRegexp_ = new RegExp(expr, 'ig'); + } + + if (cvox.AbstractTts.substitutionDictionaryRegexp_ == undefined) { + // Create an expression that matches all words in the substitution + // dictionary. + var symbols = []; + for (var symbol in cvox.AbstractTts.SUBSTITUTION_DICTIONARY) { + symbols.push(symbol); + } + var expr = '(' + symbols.join('|') + ')'; + cvox.AbstractTts.substitutionDictionaryRegexp_ = new RegExp(expr, 'ig'); + } +}; + + +/** + * Default TTS properties for this TTS engine. + * @type {Object} + * @protected + */ +cvox.AbstractTts.prototype.ttsProperties; + + +/** @override */ +cvox.AbstractTts.prototype.speak = function(textString, queueMode, properties) { + return this; +}; + + +/** @override */ +cvox.AbstractTts.prototype.isSpeaking = function() { + return false; +}; + + +/** @override */ +cvox.AbstractTts.prototype.stop = function() { +}; + + +/** @override */ +cvox.AbstractTts.prototype.addCapturingEventListener = function(listener) { }; + + +/** @override */ +cvox.AbstractTts.prototype.increaseOrDecreaseProperty = + function(propertyName, increase) { + var min = this.propertyMin[propertyName]; + var max = this.propertyMax[propertyName]; + var step = this.propertyStep[propertyName]; + var current = this.ttsProperties[propertyName]; + current = increase ? current + step : current - step; + this.ttsProperties[propertyName] = Math.max(Math.min(current, max), min); + }; + + +/** + * Merges the given properties with the default ones. Always returns a + * new object, so that you can safely modify the result of mergeProperties + * without worrying that you're modifying an object used elsewhere. + * @param {Object=} properties The properties to merge with the current ones. + * @return {Object} The merged properties. + * @protected + */ +cvox.AbstractTts.prototype.mergeProperties = function(properties) { + var mergedProperties = new Object(); + var p; + if (this.ttsProperties) { + for (p in this.ttsProperties) { + mergedProperties[p] = this.ttsProperties[p]; + } + } + if (properties) { + var tts = cvox.AbstractTts; + if (typeof(properties[tts.VOLUME]) == 'number') { + mergedProperties[tts.VOLUME] = properties[tts.VOLUME]; + } + if (typeof(properties[tts.PITCH]) == 'number') { + mergedProperties[tts.PITCH] = properties[tts.PITCH]; + } + if (typeof(properties[tts.RATE]) == 'number') { + mergedProperties[tts.RATE] = properties[tts.RATE]; + } + if (typeof(properties[tts.LANG]) == 'string') { + mergedProperties[tts.LANG] = properties[tts.LANG]; + } + + var context = this; + var mergeRelativeProperty = function(abs, rel) { + if (typeof(properties[rel]) == 'number' && + typeof(mergedProperties[abs]) == 'number') { + mergedProperties[abs] += properties[rel]; + var min = context.propertyMin[abs]; + var max = context.propertyMax[abs]; + if (mergedProperties[abs] > max) { + mergedProperties[abs] = max; + } else if (mergedProperties[abs] < min) { + mergedProperties[abs] = min; + } + } + }; + + mergeRelativeProperty(tts.VOLUME, tts.RELATIVE_VOLUME); + mergeRelativeProperty(tts.PITCH, tts.RELATIVE_PITCH); + mergeRelativeProperty(tts.RATE, tts.RELATIVE_RATE); + } + + for (p in properties) { + if (!mergedProperties.hasOwnProperty(p)) { + mergedProperties[p] = properties[p]; + } + } + + return mergedProperties; +}; + + +/** + * Method to preprocess text to be spoken properly by a speech + * engine. + * + * 1. Replace any single character with a description of that character. + * + * 2. Convert all-caps words to lowercase if they don't look like an + * acronym / abbreviation. + * + * @param {string} text A text string to be spoken. + * @param {Object= } properties Out parameter populated with how to speak the + * string. + * @return {string} The text formatted in a way that will sound better by + * most speech engines. + * @protected + */ +cvox.AbstractTts.prototype.preprocess = function(text, properties) { + if (text.length == 1 && text >= 'A' && text <= 'Z') { + for (var prop in cvox.AbstractTts.PERSONALITY_CAPITAL) + properties[prop] = cvox.AbstractTts.PERSONALITY_CAPITAL[prop]; + } + + // Substitute all symbols in the substitution dictionary. This is pretty + // efficient because we use a single regexp that matches all symbols + // simultaneously. + text = text.replace( + cvox.AbstractTts.substitutionDictionaryRegexp_, + function(symbol) { + return ' ' + cvox.AbstractTts.SUBSTITUTION_DICTIONARY[symbol] + ' '; + }); + + // Handle single characters that we want to make sure we pronounce. + if (text.length == 1) { + return cvox.AbstractTts.CHARACTER_DICTIONARY[text] ? + (new goog.i18n.MessageFormat(cvox.ChromeVox.msgs.getMsg( + cvox.AbstractTts.CHARACTER_DICTIONARY[text]))) + .format({'COUNT': 1}) : + text.toUpperCase(); + } + + // Substitute all words in the pronunciation dictionary. This is pretty + // efficient because we use a single regexp that matches all words + // simultaneously, and it calls a function with each match, which we can + // use to look up the replacement in our dictionary. + text = text.replace( + cvox.AbstractTts.pronunciationDictionaryRegexp_, + function(word) { + return cvox.AbstractTts.PRONUNCIATION_DICTIONARY[word.toLowerCase()]; + }); + + // Special case for google+, where the punctuation must be pronounced. + text = text.replace(/google\+/ig, 'google plus'); + + // Expand all repeated characters. + text = text.replace( + cvox.AbstractTts.repetitionRegexp_, cvox.AbstractTts.repetitionReplace_); + + // If there's no lower case letters, and at least two spaces, skip spacing + // text. + var skipSpacing = false; + if (!text.match(/[a-z]+/) && text.indexOf(' ') != text.lastIndexOf(' ')) { + skipSpacing = true; + } + + // Convert all-caps words to lowercase if they don't look like acronyms, + // otherwise add a space before all-caps words so that all-caps words in + // the middle of camelCase will be separated. + text = text.replace(/[A-Z]+/g, function(word) { + // If a word contains vowels and is more than 3 letters long, it is + // probably a real word and not just an abbreviation. Convert it to lower + // case and speak it normally. + if ((word.length > 3) && word.match(/([AEIOUY])/g)) { + return word.toLowerCase(); + } else if (!skipSpacing) { + // Builds spaced-out camelCased/all CAPS words so they sound better when + // spoken by TTS engines. + return ' ' + word.split('').join(' '); + } else { + return word; + } + }); + + return text; +}; + + +/** TTS rate property. @type {string} */ +cvox.AbstractTts.RATE = 'rate'; +/** TTS pitch property. @type {string} */ +cvox.AbstractTts.PITCH = 'pitch'; +/** TTS volume property. @type {string} */ +cvox.AbstractTts.VOLUME = 'volume'; +/** TTS language property. @type {string} */ +cvox.AbstractTts.LANG = 'lang'; + +/** TTS relative rate property. @type {string} */ +cvox.AbstractTts.RELATIVE_RATE = 'relativeRate'; +/** TTS relative pitch property. @type {string} */ +cvox.AbstractTts.RELATIVE_PITCH = 'relativePitch'; +/** TTS relative volume property. @type {string} */ +cvox.AbstractTts.RELATIVE_VOLUME = 'relativeVolume'; + +/** TTS color property (for the lens display). @type {string} */ +cvox.AbstractTts.COLOR = 'color'; +/** TTS CSS font-weight property (for the lens display). @type {string} */ +cvox.AbstractTts.FONT_WEIGHT = 'fontWeight'; + +/** TTS punctuation-echo property. @type {string} */ +cvox.AbstractTts.PUNCTUATION_ECHO = 'punctuationEcho'; + +/** TTS pause property. @type {string} */ +cvox.AbstractTts.PAUSE = 'pause'; + +/** + * TTS personality for annotations - text spoken by ChromeVox that + * elaborates on a user interface element but isn't displayed on-screen. + * @type {Object} + */ +cvox.AbstractTts.PERSONALITY_ANNOTATION = { + 'relativePitch': -0.25, + // TODO:(rshearer) Added this color change for I/O presentation. + 'color': 'yellow', + 'punctuationEcho': 'none' +}; + + +/** + * TTS personality for announcements - text spoken by ChromeVox that + * isn't tied to any user interface elements. + * @type {Object} + */ +cvox.AbstractTts.PERSONALITY_ANNOUNCEMENT = { + 'punctuationEcho': 'none' +}; + +/** + * TTS personality for alerts from the system, such as battery level + * warnings. + * @type {Object} + */ +cvox.AbstractTts.PERSONALITY_SYSTEM_ALERT = { + 'punctuationEcho': 'none', + 'doNotInterrupt': true +}; + +/** + * TTS personality for an aside - text in parentheses. + * @type {Object} + */ +cvox.AbstractTts.PERSONALITY_ASIDE = { + 'relativePitch': -0.1, + 'color': '#669' +}; + + +/** + * TTS personality for capital letters. + * @type {Object} + */ +cvox.AbstractTts.PERSONALITY_CAPITAL = { + 'relativePitch': 0.6 +}; + + +/** + * TTS personality for deleted text. + * @type {Object} + */ +cvox.AbstractTts.PERSONALITY_DELETED = { + 'punctuationEcho': 'none', + 'relativePitch': -0.6 +}; + + +/** + * TTS personality for quoted text. + * @type {Object} + */ +cvox.AbstractTts.PERSONALITY_QUOTE = { + 'relativePitch': 0.1, + 'color': '#b6b', + 'fontWeight': 'bold' +}; + + +/** + * TTS personality for strong or bold text. + * @type {Object} + */ +cvox.AbstractTts.PERSONALITY_STRONG = { + 'relativePitch': 0.1, + 'color': '#b66', + 'fontWeight': 'bold' +}; + + +/** + * TTS personality for emphasis or italicized text. + * @type {Object} + */ +cvox.AbstractTts.PERSONALITY_EMPHASIS = { + 'relativeVolume': 0.1, + 'relativeRate': -0.1, + 'color': '#6bb', + 'fontWeight': 'bold' +}; + + +/** + * Flag indicating if the TTS is being debugged. + * @type {boolean} + */ +cvox.AbstractTts.DEBUG = true; + + +/** + * Speech queue mode that interrupts the current utterance. + * @type {number} + */ +cvox.AbstractTts.QUEUE_MODE_FLUSH = 0; + + +/** + * Speech queue mode that does not interrupt the current utterance. + * @type {number} + */ +cvox.AbstractTts.QUEUE_MODE_QUEUE = 1; + + +/** + * Speech queue mode that flushes all utterances of the same category + * (as set by properties['category']). + * @type {number} + */ +cvox.AbstractTts.QUEUE_MODE_CATEGORY_FLUSH = 2; + + +/** + * Character dictionary. These symbols are replaced with their human readable + * equivalents. This replacement only occurs for single character utterances. + * @type {Object.<string, string>} + */ +cvox.AbstractTts.CHARACTER_DICTIONARY = { + ' ': 'space', + '`': 'backtick', + '~': 'tilde', + '!': 'exclamation', + '@': 'at', + '#': 'pound', + '$': 'dollar', + '%': 'percent', + '^': 'caret', + '&': 'ampersand', + '*': 'asterisk', + '(': 'open_paren', + ')': 'close_paren', + '-': 'dash', + '_': 'underscore', + '=': 'equals', + '+': 'plus', + '[': 'left_bracket', + ']': 'right_bracket', + '{': 'left_brace', + '}': 'right_brace', + '|': 'pipe', + ';': 'semicolon', + ':': 'colon', + ',': 'comma', + '.': 'dot', + '<': 'less_than', + '>': 'greater_than', + '/': 'slash', + '?': 'question_mark', + '"': 'quote', + '\'': 'apostrophe', + '\t': 'tab', + '\r': 'return', + '\n': 'new_line', + '\\': 'backslash' +}; + + +/** + * Pronunciation dictionary. Each key must be lowercase, its replacement + * should be spelled out the way most TTS engines will pronounce it + * correctly. This particular dictionary only handles letters and numbers, + * no symbols. + * @type {Object.<string, string>} + */ +cvox.AbstractTts.PRONUNCIATION_DICTIONARY = { + 'admob': 'ad-mob', + 'adsense': 'ad-sense', + 'adwords': 'ad-words', + 'angularjs': 'angular j s', + 'bcc': 'B C C', + 'cc': 'C C', + 'chromevox': 'chrome vox', + 'cr48': 'C R 48', + 'ctrl': 'control', + 'doubleclick': 'double-click', + 'gmail': 'gee mail', + 'gtalk': 'gee talk', + 'http': 'H T T P', + 'https' : 'H T T P S', + 'igoogle': 'eye google', + 'pagerank': 'page-rank', + 'username': 'user-name', + 'www': 'W W W', + 'youtube': 'you tube' +}; + + +/** + * Pronunciation dictionary regexp. + * @type {RegExp}; + * @private + */ +cvox.AbstractTts.pronunciationDictionaryRegexp_; + + +/** + * Substitution dictionary. These symbols or patterns are ALWAYS substituted + * whenever they occur, so this should be reserved only for unicode characters + * and characters that never have any different meaning in context. + * + * For example, do not include '$' here because $2 should be read as + * "two dollars". + * @type {Object.<string, string>} + */ +cvox.AbstractTts.SUBSTITUTION_DICTIONARY = { + '://': 'colon slash slash', + '\u00bc': 'one fourth', + '\u00bd': 'one half', + '\u2190': 'left arrow', + '\u2191': 'up arrow', + '\u2192': 'right arrow', + '\u2193': 'down arrow', + '\u21d0': 'left double arrow', + '\u21d1': 'up double arrow', + '\u21d2': 'right double arrow', + '\u21d3': 'down double arrow', + '\u21e6': 'left arrow', + '\u21e7': 'up arrow', + '\u21e8': 'right arrow', + '\u21e9': 'down arrow', + '\u2303': 'control', + '\u2318': 'command', + '\u2325': 'option', + '\u25b2': 'up triangle', + '\u25b3': 'up triangle', + '\u25b4': 'up triangle', + '\u25b5': 'up triangle', + '\u25b6': 'right triangle', + '\u25b7': 'right triangle', + '\u25b8': 'right triangle', + '\u25b9': 'right triangle', + '\u25ba': 'right pointer', + '\u25bb': 'right pointer', + '\u25bc': 'down triangle', + '\u25bd': 'down triangle', + '\u25be': 'down triangle', + '\u25bf': 'down triangle', + '\u25c0': 'left triangle', + '\u25c1': 'left triangle', + '\u25c2': 'left triangle', + '\u25c3': 'left triangle', + '\u25c4': 'left pointer', + '\u25c5': 'left pointer', + '\uf8ff': 'apple' +}; + + +/** + * Substitution dictionary regexp. + * @type {RegExp}; + * @private + */ +cvox.AbstractTts.substitutionDictionaryRegexp_; + + +/** + * repetition filter regexp. + * @type {RegExp} + * @private + */ +cvox.AbstractTts.repetitionRegexp_ = + /([-\/\\|!@#$%^&*\(\)=_+\[\]\{\}.?;'":<>])\1{2,}/g; + + +/** + * Constructs a description of a repeated character. Use as a param to + * string.replace. + * @param {string} match The matching string. + * @return {string} The description. + * @private + */ +cvox.AbstractTts.repetitionReplace_ = function(match) { + var count = match.length; + return ' ' + (new goog.i18n.MessageFormat(cvox.ChromeVox.msgs.getMsg( + cvox.AbstractTts.CHARACTER_DICTIONARY[match[0]]))) + .format({'COUNT': count}) + ' '; +}; + + +/** + * @override + */ +cvox.AbstractTts.prototype.getDefaultProperty = function(property) { + return this.propertyDefault[property]; +}; |