Add new certificateProvider extension API.
[chromium-blink-merge.git] / chrome / browser / resources / chromeos / chromevox / host / interface / abstract_tts.js
blob41eee772d6f406316ba57601e0397a3b10248f94
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.
5 /**
6  * @fileoverview Base class for Text-to-Speech engines that actually transform
7  * text to speech.
8  *
9  */
11 goog.provide('cvox.AbstractTts');
13 goog.require('cvox.TtsInterface');
14 goog.require('goog.i18n.MessageFormat');
16 /**
17  * Creates a new instance.
18  * @constructor
19  * @implements {cvox.TtsInterface}
20  */
21 cvox.AbstractTts = function() {
22   this.ttsProperties = new Object();
24   /**
25    * Default value for TTS properties.
26    * Note that these as well as the subsequent properties might be different
27    * on different host platforms (like Chrome, Android, etc.).
28    * @type {{pitch : number,
29    *         rate: number,
30    *         volume: number}}
31    * @protected
32    */
33   this.propertyDefault = {
34     'rate': 0.5,
35     'pitch': 0.5,
36     'volume': 0.5
37   };
39   /**
40    * Min value for TTS properties.
41    * @type {{pitch : number,
42    *         rate: number,
43    *         volume: number}}
44    * @protected
45    */
46   this.propertyMin = {
47     'rate': 0.0,
48     'pitch': 0.0,
49     'volume': 0.0
50   };
52   /**
53    * Max value for TTS properties.
54    * @type {{pitch : number,
55    *         rate: number,
56    *         volume: number}}
57    * @protected
58    */
59   this.propertyMax = {
60     'rate': 1.0,
61     'pitch': 1.0,
62     'volume': 1.0
63   };
65   /**
66    * Step value for TTS properties.
67    * @type {{pitch : number,
68    *         rate: number,
69    *         volume: number}}
70    * @protected
71    */
72   this.propertyStep = {
73     'rate': 0.1,
74     'pitch': 0.1,
75     'volume': 0.1
76   };
79   /** @private */
81   if (cvox.AbstractTts.pronunciationDictionaryRegexp_ == undefined) {
82     // Create an expression that matches all words in the pronunciation
83     // dictionary on word boundaries, ignoring case.
84     var words = [];
85     for (var word in cvox.AbstractTts.PRONUNCIATION_DICTIONARY) {
86       words.push(word);
87     }
88     var expr = '\\b(' + words.join('|') + ')\\b';
89     cvox.AbstractTts.pronunciationDictionaryRegexp_ = new RegExp(expr, 'ig');
90   }
92   if (cvox.AbstractTts.substitutionDictionaryRegexp_ == undefined) {
93     // Create an expression that matches all words in the substitution
94     // dictionary.
95     var symbols = [];
96     for (var symbol in cvox.AbstractTts.SUBSTITUTION_DICTIONARY) {
97       symbols.push(symbol);
98     }
99     var expr = '(' + symbols.join('|') + ')';
100     cvox.AbstractTts.substitutionDictionaryRegexp_ = new RegExp(expr, 'ig');
101   }
106  * Default TTS properties for this TTS engine.
107  * @type {Object}
108  * @protected
109  */
110 cvox.AbstractTts.prototype.ttsProperties;
113 /** @override */
114 cvox.AbstractTts.prototype.speak = function(textString, queueMode, properties) {
115   return this;
119 /** @override */
120 cvox.AbstractTts.prototype.isSpeaking = function() {
121   return false;
125 /** @override */
126 cvox.AbstractTts.prototype.stop = function() {
130 /** @override */
131 cvox.AbstractTts.prototype.addCapturingEventListener = function(listener) { };
134 /** @override */
135 cvox.AbstractTts.prototype.increaseOrDecreaseProperty =
136     function(propertyName, increase) {
137       var min = this.propertyMin[propertyName];
138       var max = this.propertyMax[propertyName];
139       var step = this.propertyStep[propertyName];
140       var current = this.ttsProperties[propertyName];
141       current = increase ? current + step : current - step;
142       this.ttsProperties[propertyName] = Math.max(Math.min(current, max), min);
143     };
147  * Merges the given properties with the default ones. Always returns a
148  * new object, so that you can safely modify the result of mergeProperties
149  * without worrying that you're modifying an object used elsewhere.
150  * @param {Object=} properties The properties to merge with the current ones.
151  * @return {Object} The merged properties.
152  * @protected
153  */
154 cvox.AbstractTts.prototype.mergeProperties = function(properties) {
155   var mergedProperties = new Object();
156   var p;
157   if (this.ttsProperties) {
158     for (p in this.ttsProperties) {
159       mergedProperties[p] = this.ttsProperties[p];
160     }
161   }
162   if (properties) {
163     var tts = cvox.AbstractTts;
164     if (typeof(properties[tts.VOLUME]) == 'number') {
165       mergedProperties[tts.VOLUME] = properties[tts.VOLUME];
166     }
167     if (typeof(properties[tts.PITCH]) == 'number') {
168       mergedProperties[tts.PITCH] = properties[tts.PITCH];
169     }
170     if (typeof(properties[tts.RATE]) == 'number') {
171       mergedProperties[tts.RATE] = properties[tts.RATE];
172     }
173     if (typeof(properties[tts.LANG]) == 'string') {
174       mergedProperties[tts.LANG] = properties[tts.LANG];
175     }
177     var context = this;
178     var mergeRelativeProperty = function(abs, rel) {
179       if (typeof(properties[rel]) == 'number' &&
180           typeof(mergedProperties[abs]) == 'number') {
181         mergedProperties[abs] += properties[rel];
182         var min = context.propertyMin[abs];
183         var max = context.propertyMax[abs];
184         if (mergedProperties[abs] > max) {
185           mergedProperties[abs] = max;
186         } else if (mergedProperties[abs] < min) {
187           mergedProperties[abs] = min;
188         }
189       }
190     };
192     mergeRelativeProperty(tts.VOLUME, tts.RELATIVE_VOLUME);
193     mergeRelativeProperty(tts.PITCH, tts.RELATIVE_PITCH);
194     mergeRelativeProperty(tts.RATE, tts.RELATIVE_RATE);
195   }
197   for (p in properties) {
198     if (!mergedProperties.hasOwnProperty(p)) {
199       mergedProperties[p] = properties[p];
200     }
201   }
203   return mergedProperties;
208  * Method to preprocess text to be spoken properly by a speech
209  * engine.
211  * 1. Replace any single character with a description of that character.
213  * 2. Convert all-caps words to lowercase if they don't look like an
214  *    acronym / abbreviation.
216  * @param {string} text A text string to be spoken.
217  * @param {Object= } properties Out parameter populated with how to speak the
218  *     string.
219  * @return {string} The text formatted in a way that will sound better by
220  *     most speech engines.
221  * @protected
222  */
223 cvox.AbstractTts.prototype.preprocess = function(text, properties) {
224   if (text.length == 1 && text >= 'A' && text <= 'Z') {
225     for (var prop in cvox.AbstractTts.PERSONALITY_CAPITAL)
226     properties[prop] = cvox.AbstractTts.PERSONALITY_CAPITAL[prop];
227   }
229   // Substitute all symbols in the substitution dictionary. This is pretty
230   // efficient because we use a single regexp that matches all symbols
231   // simultaneously.
232   text = text.replace(
233       cvox.AbstractTts.substitutionDictionaryRegexp_,
234       function(symbol) {
235         return ' ' + cvox.AbstractTts.SUBSTITUTION_DICTIONARY[symbol] + ' ';
236       });
238   // Handle single characters that we want to make sure we pronounce.
239   if (text.length == 1) {
240     return cvox.AbstractTts.CHARACTER_DICTIONARY[text] ?
241         (new goog.i18n.MessageFormat(cvox.ChromeVox.msgs.getMsg(
242                 cvox.AbstractTts.CHARACTER_DICTIONARY[text])))
243             .format({'COUNT': 1}) :
244         text.toUpperCase();
245   }
247   // Substitute all words in the pronunciation dictionary. This is pretty
248   // efficient because we use a single regexp that matches all words
249   // simultaneously, and it calls a function with each match, which we can
250   // use to look up the replacement in our dictionary.
251   text = text.replace(
252       cvox.AbstractTts.pronunciationDictionaryRegexp_,
253       function(word) {
254         return cvox.AbstractTts.PRONUNCIATION_DICTIONARY[word.toLowerCase()];
255       });
257   // Special case for google+, where the punctuation must be pronounced.
258   text = text.replace(/google\+/ig, 'google plus');
260   // Expand all repeated characters.
261   text = text.replace(
262       cvox.AbstractTts.repetitionRegexp_, cvox.AbstractTts.repetitionReplace_);
264   // If there's no lower case letters, and at least two spaces, skip spacing
265   // text.
266   var skipSpacing = false;
267   if (!text.match(/[a-z]+/) && text.indexOf(' ') != text.lastIndexOf(' ')) {
268     skipSpacing = true;
269   }
271   // Convert all-caps words to lowercase if they don't look like acronyms,
272   // otherwise add a space before all-caps words so that all-caps words in
273   // the middle of camelCase will be separated.
274   text = text.replace(/[A-Z]+/g, function(word) {
275     // If a word contains vowels and is more than 3 letters long, it is
276     // probably a real word and not just an abbreviation. Convert it to lower
277     // case and speak it normally.
278     if ((word.length > 3) && word.match(/([AEIOUY])/g)) {
279       return word.toLowerCase();
280     } else if (!skipSpacing) {
281       // Builds spaced-out camelCased/all CAPS words so they sound better when
282       // spoken by TTS engines.
283       return ' ' + word.split('').join(' ');
284     } else {
285       return word;
286     }
287   });
289   return text;
293 /** TTS rate property. @type {string} */
294 cvox.AbstractTts.RATE = 'rate';
295 /** TTS pitch property. @type {string} */
296 cvox.AbstractTts.PITCH = 'pitch';
297 /** TTS volume property. @type {string} */
298 cvox.AbstractTts.VOLUME = 'volume';
299 /** TTS language property. @type {string} */
300 cvox.AbstractTts.LANG = 'lang';
302 /** TTS relative rate property. @type {string} */
303 cvox.AbstractTts.RELATIVE_RATE = 'relativeRate';
304 /** TTS relative pitch property. @type {string} */
305 cvox.AbstractTts.RELATIVE_PITCH = 'relativePitch';
306 /** TTS relative volume property. @type {string} */
307 cvox.AbstractTts.RELATIVE_VOLUME = 'relativeVolume';
309 /** TTS color property (for the lens display). @type {string} */
310 cvox.AbstractTts.COLOR = 'color';
311 /** TTS CSS font-weight property (for the lens display). @type {string} */
312 cvox.AbstractTts.FONT_WEIGHT = 'fontWeight';
314 /** TTS punctuation-echo property. @type {string} */
315 cvox.AbstractTts.PUNCTUATION_ECHO = 'punctuationEcho';
317 /** TTS pause property. @type {string} */
318 cvox.AbstractTts.PAUSE = 'pause';
321  * TTS personality for annotations - text spoken by ChromeVox that
322  * elaborates on a user interface element but isn't displayed on-screen.
323  * @type {Object}
324  */
325 cvox.AbstractTts.PERSONALITY_ANNOTATION = {
326   'relativePitch': -0.25,
327   // TODO:(rshearer) Added this color change for I/O presentation.
328   'color': 'yellow',
329   'punctuationEcho': 'none'
334  * TTS personality for announcements - text spoken by ChromeVox that
335  * isn't tied to any user interface elements.
336  * @type {Object}
337  */
338 cvox.AbstractTts.PERSONALITY_ANNOUNCEMENT = {
339   'punctuationEcho': 'none'
343  * TTS personality for alerts from the system, such as battery level
344  * warnings.
345  * @type {Object}
346  */
347 cvox.AbstractTts.PERSONALITY_SYSTEM_ALERT = {
348   'punctuationEcho': 'none',
349   'doNotInterrupt': true
353  * TTS personality for an aside - text in parentheses.
354  * @type {Object}
355  */
356 cvox.AbstractTts.PERSONALITY_ASIDE = {
357   'relativePitch': -0.1,
358   'color': '#669'
363  * TTS personality for capital letters.
364  * @type {Object}
365  */
366 cvox.AbstractTts.PERSONALITY_CAPITAL = {
367   'relativePitch': 0.6
372  * TTS personality for deleted text.
373  * @type {Object}
374  */
375 cvox.AbstractTts.PERSONALITY_DELETED = {
376   'punctuationEcho': 'none',
377   'relativePitch': -0.6
382  * TTS personality for quoted text.
383  * @type {Object}
384  */
385 cvox.AbstractTts.PERSONALITY_QUOTE = {
386   'relativePitch': 0.1,
387   'color': '#b6b',
388   'fontWeight': 'bold'
393  * TTS personality for strong or bold text.
394  * @type {Object}
395  */
396 cvox.AbstractTts.PERSONALITY_STRONG = {
397   'relativePitch': 0.1,
398   'color': '#b66',
399   'fontWeight': 'bold'
404  * TTS personality for emphasis or italicized text.
405  * @type {Object}
406  */
407 cvox.AbstractTts.PERSONALITY_EMPHASIS = {
408   'relativeVolume': 0.1,
409   'relativeRate': -0.1,
410   'color': '#6bb',
411   'fontWeight': 'bold'
416  * Flag indicating if the TTS is being debugged.
417  * @type {boolean}
418  */
419 cvox.AbstractTts.DEBUG = true;
423  * Character dictionary. These symbols are replaced with their human readable
424  * equivalents. This replacement only occurs for single character utterances.
425  * @type {Object<string>}
426  */
427 cvox.AbstractTts.CHARACTER_DICTIONARY = {
428   ' ': 'space',
429   '`': 'backtick',
430   '~': 'tilde',
431   '!': 'exclamation',
432   '@': 'at',
433   '#': 'pound',
434   '$': 'dollar',
435   '%': 'percent',
436   '^': 'caret',
437   '&': 'ampersand',
438   '*': 'asterisk',
439   '(': 'open_paren',
440   ')': 'close_paren',
441   '-': 'dash',
442   '_': 'underscore',
443   '=': 'equals',
444   '+': 'plus',
445   '[': 'left_bracket',
446   ']': 'right_bracket',
447   '{': 'left_brace',
448   '}': 'right_brace',
449   '|': 'pipe',
450   ';': 'semicolon',
451   ':': 'colon',
452   ',': 'comma',
453   '.': 'dot',
454   '<': 'less_than',
455   '>': 'greater_than',
456   '/': 'slash',
457   '?': 'question_mark',
458   '"': 'quote',
459   '\'': 'apostrophe',
460   '\t': 'tab',
461   '\r': 'return',
462   '\n': 'new_line',
463   '\\': 'backslash'
468  * Pronunciation dictionary. Each key must be lowercase, its replacement
469  * should be spelled out the way most TTS engines will pronounce it
470  * correctly. This particular dictionary only handles letters and numbers,
471  * no symbols.
472  * @type {Object<string>}
473  */
474 cvox.AbstractTts.PRONUNCIATION_DICTIONARY = {
475   'admob': 'ad-mob',
476   'adsense': 'ad-sense',
477   'adwords': 'ad-words',
478   'angularjs': 'angular j s',
479   'bcc': 'B C C',
480   'cc': 'C C',
481   'chromevox': 'chrome vox',
482   'cr48': 'C R 48',
483   'ctrl': 'control',
484   'doubleclick': 'double-click',
485   'gmail': 'gee mail',
486   'gtalk': 'gee talk',
487   'http': 'H T T P',
488   'https' : 'H T T P S',
489   'igoogle': 'eye google',
490   'pagerank': 'page-rank',
491   'username': 'user-name',
492   'www': 'W W W',
493   'youtube': 'you tube'
498  * Pronunciation dictionary regexp.
499  * @type {RegExp};
500  * @private
501  */
502 cvox.AbstractTts.pronunciationDictionaryRegexp_;
506  * Substitution dictionary. These symbols or patterns are ALWAYS substituted
507  * whenever they occur, so this should be reserved only for unicode characters
508  * and characters that never have any different meaning in context.
510  * For example, do not include '$' here because $2 should be read as
511  * "two dollars".
512  * @type {Object<string>}
513  */
514 cvox.AbstractTts.SUBSTITUTION_DICTIONARY = {
515   '://': 'colon slash slash',
516   '\u00bc': 'one fourth',
517   '\u00bd': 'one half',
518   '\u2190': 'left arrow',
519   '\u2191': 'up arrow',
520   '\u2192': 'right arrow',
521   '\u2193': 'down arrow',
522   '\u21d0': 'left double arrow',
523   '\u21d1': 'up double arrow',
524   '\u21d2': 'right double  arrow',
525   '\u21d3': 'down double arrow',
526   '\u21e6': 'left arrow',
527   '\u21e7': 'up arrow',
528   '\u21e8': 'right arrow',
529   '\u21e9': 'down arrow',
530   '\u2303': 'control',
531   '\u2318': 'command',
532   '\u2325': 'option',
533   '\u25b2': 'up triangle',
534   '\u25b3': 'up triangle',
535   '\u25b4': 'up triangle',
536   '\u25b5': 'up triangle',
537   '\u25b6': 'right triangle',
538   '\u25b7': 'right triangle',
539   '\u25b8': 'right triangle',
540   '\u25b9': 'right triangle',
541   '\u25ba': 'right pointer',
542   '\u25bb': 'right pointer',
543   '\u25bc': 'down triangle',
544   '\u25bd': 'down triangle',
545   '\u25be': 'down triangle',
546   '\u25bf': 'down triangle',
547   '\u25c0': 'left triangle',
548   '\u25c1': 'left triangle',
549   '\u25c2': 'left triangle',
550   '\u25c3': 'left triangle',
551   '\u25c4': 'left pointer',
552   '\u25c5': 'left pointer',
553   '\uf8ff': 'apple'
558  * Substitution dictionary regexp.
559  * @type {RegExp};
560  * @private
561  */
562 cvox.AbstractTts.substitutionDictionaryRegexp_;
566  * repetition filter regexp.
567  * @type {RegExp}
568  * @private
569  */
570 cvox.AbstractTts.repetitionRegexp_ =
571     /([-\/\\|!@#$%^&*\(\)=_+\[\]\{\}.?;'":<>])\1{2,}/g;
575  * Constructs a description of a repeated character. Use as a param to
576  * string.replace.
577  * @param {string} match The matching string.
578  * @return {string} The description.
579  * @private
580  */
581 cvox.AbstractTts.repetitionReplace_ = function(match) {
582   var count = match.length;
583   return ' ' + (new goog.i18n.MessageFormat(cvox.ChromeVox.msgs.getMsg(
584       cvox.AbstractTts.CHARACTER_DICTIONARY[match[0]])))
585           .format({'COUNT': count}) + ' ';
590  * @override
591  */
592 cvox.AbstractTts.prototype.getDefaultProperty = function(property) {
593   return this.propertyDefault[property];