[ChromeVox] Convert AbstractTts to TypeScript
AX-Relnotes: n/a.
Bug: b/267329383
Change-Id: I09012c41d6adc4b5ef2aca309454f4ba4ff09616
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/5608978
Reviewed-by: Kyungjun Lee <kyungjunlee@google.com>
Auto-Submit: Anastasia Helfinstein <anastasi@google.com>
Commit-Queue: Kyungjun Lee <kyungjunlee@google.com>
Cr-Commit-Position: refs/heads/main@{#1329871}
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/BUILD.gn b/chrome/browser/resources/chromeos/accessibility/chromevox/BUILD.gn
index 92e6263e..5341c5b 100644
--- a/chrome/browser/resources/chromeos/accessibility/chromevox/BUILD.gn
+++ b/chrome/browser/resources/chromeos/accessibility/chromevox/BUILD.gn
@@ -43,6 +43,7 @@
#
# TS files to compile.
ts_modules = [
+ "background/abstract_tts.ts",
"background/background.ts",
"background/braille/braille_background.ts",
"background/braille/braille_captions_background.ts",
@@ -136,7 +137,6 @@
# ES6 modules.
js_modules = [
"background/abstract_earcons.js",
- "background/abstract_tts.js",
"background/automation_object_constructor_installer.js",
"background/auto_scroll_handler.js",
"background/braille/braille_input_handler.js",
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/background/abstract_tts.js b/chrome/browser/resources/chromeos/accessibility/chromevox/background/abstract_tts.js
deleted file mode 100644
index 7de0092..0000000
--- a/chrome/browser/resources/chromeos/accessibility/chromevox/background/abstract_tts.js
+++ /dev/null
@@ -1,343 +0,0 @@
-// Copyright 2014 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-import {Msgs} from '../common/msgs.js';
-import {SettingsManager} from '../common/settings_manager.js';
-import * as ttsTypes from '../common/tts_types.js';
-
-import {TtsInterface} from './tts_interface.js';
-
-/**
- * @typedef {{
- * pitch: number,
- * rate: number,
- * volume: number,
- * }}
- */
-let PropertyValues;
-
-/**
- * Base class for Text-to-Speech engines that actually transform
- * text to speech (as opposed to logging or other behaviors).
- * @implements {TtsInterface}
- */
-export class AbstractTts {
- constructor() {
- 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.).
- * @protected {PropertyValues}
- */
- this.propertyDefault;
-
- /**
- * Min value for TTS properties.
- * @protected {PropertyValues}
- */
- this.propertyMin;
-
- /**
- * Max value for TTS properties.
- * @protected {PropertyValues}
- */
- this.propertyMax;
-
- /**
- * Step value for TTS properties.
- * @protected {PropertyValues}
- */
- this.propertyStep;
-
- this.init_();
- }
-
- /** @private */
- init_() {
- const pitchDefault = 1;
- const pitchMin = 0.2;
- const pitchMax = 2.0;
- const pitchStep = 0.1;
-
- const rateDefault = 1;
- const rateMin = 0.2;
- const rateMax = 5.0;
- const rateStep = 0.1;
-
- const volumeDefault = 1;
- const volumeMin = 0.2;
- const volumeMax = 1.0;
- const volumeStep = 0.1;
-
- this.propertyDefault = {
- pitch: pitchDefault,
- rate: rateDefault,
- volume: volumeDefault,
- };
-
- this.propertyMin = {
- pitch: pitchMin,
- rate: rateMin,
- volume: volumeMin,
- };
-
- this.propertyMax = {
- pitch: pitchMax,
- rate: rateMax,
- volume: volumeMax,
- };
-
- this.propertyStep = {rate: rateStep, pitch: pitchStep, volume: volumeStep};
-
- if (AbstractTts.substitutionDictionaryRegexp_ === undefined) {
- // Create an expression that matches all words in the substitution
- // dictionary.
- const symbols = [];
- for (const symbol in ttsTypes.SubstitutionDictionary) {
- symbols.push(symbol);
- }
- const expr = '(' + symbols.join('|') + ')';
- AbstractTts.substitutionDictionaryRegexp_ = new RegExp(expr, 'ig');
- }
- }
-
- /**
- * @param {string} textString
- * @param {ttsTypes.QueueMode} queueMode
- * @param {ttsTypes.TtsSpeechProperties=} properties
- * @override
- */
- speak(textString, queueMode, properties) {
- return this;
- }
-
- /** @override */
- isSpeaking() {
- return false;
- }
-
- /** @override */
- stop() {}
-
- /** @override */
- addCapturingEventListener(listener) {}
-
- /** @override */
- removeCapturingEventListener(listener) {}
-
- /** @override */
- increaseOrDecreaseProperty(propertyName, increase) {
- const step = this.propertyStep[propertyName];
- let current = this.ttsProperties[propertyName];
- current = increase ? current + step : current - step;
- this.setProperty(propertyName, current);
- }
-
- /** @override */
- setProperty(propertyName, value) {
- const min = this.propertyMin[propertyName];
- const max = this.propertyMax[propertyName];
- this.ttsProperties[propertyName] = Math.max(Math.min(value, max), min);
- }
-
- /**
- * Converts an engine property value to a percentage from 0.00 to 1.00.
- * @param {string} property The property to convert.
- * @return {?number} The percentage of the property.
- */
- propertyToPercentage(property) {
- return (this.ttsProperties[property] - this.propertyMin[property]) /
- Math.abs(this.propertyMax[property] - this.propertyMin[property]);
- }
-
- /**
- * 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
- */
- mergeProperties(properties) {
- const mergedProperties = new Object();
- let p;
- if (this.ttsProperties) {
- for (p in this.ttsProperties) {
- mergedProperties[p] = this.ttsProperties[p];
- }
- }
- if (properties) {
- const tts = ttsTypes.TtsSettings;
- 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];
- }
-
- const context = this;
- const mergeRelativeProperty = function(abs, rel) {
- if (typeof (properties[rel]) === 'number' &&
- typeof (mergedProperties[abs]) === 'number') {
- mergedProperties[abs] += properties[rel];
- const min = context.propertyMin[abs];
- const 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
- */
- preprocess(text, properties) {
- if (text.length === 1 && text.toLowerCase() !== text) {
- // Describe capital letters according to user's setting.
- if (SettingsManager.getString('capitalStrategy') === 'increasePitch') {
- // Closure doesn't allow the use of for..in or [] with structs, so
- // convert to a pure JSON object.
- const CAPITAL = ttsTypes.Personality.CAPITAL.toJSON();
- for (const prop in CAPITAL) {
- if (properties[prop] === undefined) {
- properties[prop] = CAPITAL[prop];
- }
- }
- } else if (
- SettingsManager.getString('capitalStrategy') === 'announceCapitals') {
- text = Msgs.getMsg('announce_capital_letter', [text]);
- }
- }
-
- if (!SettingsManager.getBoolean('usePitchChanges')) {
- delete properties.relativePitch;
- }
-
- // Since dollar and sterling pound signs will be replaced with text, move
- // them to after the number if they stay between a negative sign and a
- // number.
- text = text.replace(AbstractTts.negativeCurrencyAmountRegexp_, match => {
- const minus = match[0];
- const number = match.substring(2);
- const currency = match[1];
-
- return minus + number + currency;
- });
-
- // 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(
- AbstractTts.substitutionDictionaryRegexp_, function(symbol) {
- return ' ' + ttsTypes.SubstitutionDictionary[symbol] + ' ';
- });
-
- // Handle single characters that we want to make sure we pronounce.
- if (text.length === 1) {
- return ttsTypes.CharacterDictionary[text] ?
- Msgs.getMsgWithCount(ttsTypes.CharacterDictionary[text], 1) :
- text.toUpperCase();
- }
-
- // Expand all repeated characters.
- text = text.replace(
- AbstractTts.repetitionRegexp_, AbstractTts.repetitionReplace_);
-
- return text;
- }
-
- /**
- * 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
- */
- static repetitionReplace_(match) {
- const count = match.length;
- return ' ' +
- Msgs.getMsgWithCount(ttsTypes.CharacterDictionary[match[0]], count) +
- ' ';
- }
-
- /** @override */
- getDefaultProperty(property) {
- return this.propertyDefault[property];
- }
-
- /** @override */
- toggleSpeechOnOrOff() {
- return true;
- }
-}
-
-/**
- * Default TTS properties for this TTS engine.
- * @type {Object}
- * @protected
- */
-AbstractTts.prototype.ttsProperties;
-
-/**
- * Pronunciation dictionary regexp.
- * @private {RegExp}
- */
-AbstractTts.pronunciationDictionaryRegexp_;
-
-/**
- * Substitution dictionary regexp.
- * @private {RegExp}
- */
-AbstractTts.substitutionDictionaryRegexp_;
-
-/**
- * repetition filter regexp.
- * @private {RegExp}
- */
-AbstractTts.repetitionRegexp_ =
- /([-\/\\|!@#$%^&*\(\)=_+\[\]\{\}.?;'":<>\u2022\u25e6\u25a0])\1{2,}/g;
-
-/**
- * Regexp filter for negative dollar and pound amounts.
- * @private {RegExp}
- */
-AbstractTts.negativeCurrencyAmountRegexp_ =
- /-[£\$](\d{1,3})(\d+|(,\d{3})*)(\.\d{1,})?/g;
diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/background/abstract_tts.ts b/chrome/browser/resources/chromeos/accessibility/chromevox/background/abstract_tts.ts
new file mode 100644
index 0000000..f3cf9eb
--- /dev/null
+++ b/chrome/browser/resources/chromeos/accessibility/chromevox/background/abstract_tts.ts
@@ -0,0 +1,301 @@
+// Copyright 2014 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import {Msgs} from '../common/msgs.js';
+import {SettingsManager} from '../common/settings_manager.js';
+import * as ttsTypes from '../common/tts_types.js';
+
+import {TtsCapturingEventListener, TtsInterface} from './tts_interface.js';
+
+interface PropertyValues {
+ pitch: number;
+ rate: number;
+ volume: number;
+
+ [key: string]: number | undefined;
+}
+
+interface Properties {
+ [key: string]: number | undefined;
+}
+
+/**
+ * Base class for Text-to-Speech engines that actually transform
+ * text to speech (as opposed to logging or other behaviors).
+ */
+export class AbstractTts implements TtsInterface {
+ /**
+ * 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.).
+ */
+ protected propertyDefault: PropertyValues;
+ /** Min value for TTS properties. */
+ protected propertyMin: PropertyValues;
+ /** Max value for TTS properties. */
+ protected propertyMax: PropertyValues;
+ /** Step value for TTS properties. */
+ protected propertyStep: PropertyValues;
+ /** Default TTS properties for this TTS engine. */
+ protected ttsProperties: Properties = {};
+
+ /** Substitution dictionary regexp. */
+ private static substitutionDictionaryRegexp_: RegExp;
+ /** Repetition filter regexp. */
+ private static repetitionRegexp_: RegExp =
+ /([-\/\\|!@#$%^&*\(\)=_+\[\]\{\}.?;'":<>\u2022\u25e6\u25a0])\1{2,}/g;
+ /** Regexp filter for negative dollar and pound amounts. */
+ private static negativeCurrencyAmountRegexp_: RegExp =
+ /-[£\$](\d{1,3})(\d+|(,\d{3})*)(\.\d{1,})?/g;
+
+ constructor() {
+ const pitchDefault = 1;
+ const pitchMin = 0.2;
+ const pitchMax = 2.0;
+ const pitchStep = 0.1;
+
+ const rateDefault = 1;
+ const rateMin = 0.2;
+ const rateMax = 5.0;
+ const rateStep = 0.1;
+
+ const volumeDefault = 1;
+ const volumeMin = 0.2;
+ const volumeMax = 1.0;
+ const volumeStep = 0.1;
+
+ this.propertyDefault = {
+ pitch: pitchDefault,
+ rate: rateDefault,
+ volume: volumeDefault,
+ };
+
+ this.propertyMin = {
+ pitch: pitchMin,
+ rate: rateMin,
+ volume: volumeMin,
+ };
+
+ this.propertyMax = {
+ pitch: pitchMax,
+ rate: rateMax,
+ volume: volumeMax,
+ };
+
+ this.propertyStep = {rate: rateStep, pitch: pitchStep, volume: volumeStep};
+
+ if (AbstractTts.substitutionDictionaryRegexp_ === undefined) {
+ // Create an expression that matches all words in the substitution
+ // dictionary.
+ const symbols: string[] = [];
+ for (const symbol in ttsTypes.SubstitutionDictionary) {
+ symbols.push(symbol);
+ }
+ const expr = '(' + symbols.join('|') + ')';
+ AbstractTts.substitutionDictionaryRegexp_ = new RegExp(expr, 'ig');
+ }
+ }
+
+ /** TtsInterface implementation. */
+ speak(
+ _textString: string, _queueMode: ttsTypes.QueueMode,
+ _properties?: ttsTypes.TtsSpeechProperties): AbstractTts {
+ return this;
+ }
+
+ /** TtsInterface implementation. */
+ isSpeaking(): boolean {
+ return false;
+ }
+
+ /** TtsInterface implementation. */
+ stop(): void {}
+
+ /** TtsInterface implementation. */
+ addCapturingEventListener(_listener: TtsCapturingEventListener): void {}
+
+ /** TtsInterface implementation. */
+ removeCapturingEventListener(_listener: TtsCapturingEventListener): void {}
+
+ /** TtsInterface implementation. */
+ increaseOrDecreaseProperty(propertyName: string, increase: boolean): void {
+ // TODO(b/314203187): Not null asserted, check that this is correct.
+ const step = this.propertyStep[propertyName]!;
+ let current = this.ttsProperties[propertyName]!;
+ current = increase ? current + step : current - step;
+ this.setProperty(propertyName, current);
+ }
+
+ /** TtsInterface implementation. */
+ setProperty(propertyName: string, value: number): void {
+ // TODO(b/314203187): Not null asserted, check that this is correct.
+ const min = this.propertyMin[propertyName]!;
+ const max = this.propertyMax[propertyName]!;
+ this.ttsProperties[propertyName] = Math.max(Math.min(value, max), min);
+ }
+
+ /**
+ * Converts an engine property value to a percentage from 0.00 to 1.00.
+ * @param property The property to convert.
+ * @return The percentage of the property.
+ */
+ propertyToPercentage(property: string): number|null {
+ // TODO(b/314203187): Not null asserted, check that this is correct.
+ return (this.ttsProperties[property]! - this.propertyMin[property]!) /
+ Math.abs(this.propertyMax[property]! - this.propertyMin[property]!);
+ }
+
+ /**
+ * 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 properties The properties to merge with the current ones.
+ * @return The merged properties.
+ */
+ protected mergeProperties(properties: Properties): Properties {
+ const mergedProperties: Properties = {};
+ let p;
+ if (this.ttsProperties) {
+ for (p in this.ttsProperties) {
+ mergedProperties[p] = this.ttsProperties[p];
+ }
+ }
+ if (properties) {
+ const tts = ttsTypes.TtsSettings;
+ 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];
+ }
+
+ const context = this;
+ const mergeRelativeProperty = function(abs: string, rel: string): void {
+ if (typeof (properties[rel]) === 'number' &&
+ typeof (mergedProperties[abs]) === 'number') {
+ mergedProperties[abs] += properties[rel];
+ // TODO(b/314203187): Not null asserted, check that this is correct.
+ const min = context.propertyMin[abs]!;
+ const 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 text A text string to be spoken.
+ * @param properties Out parameter populated with how to speak the string.
+ * @return The text formatted in a way that will sound better by most speech
+ * engines.
+ */
+ protected preprocess(text: string, properties: Properties = {}): string {
+ if (text.length === 1 && text.toLowerCase() !== text) {
+ // Describe capital letters according to user's setting.
+ if (SettingsManager.getString('capitalStrategy') === 'increasePitch') {
+ // Closure doesn't allow the use of for..in or [] with structs, so
+ // convert to a pure JSON object.
+ const CAPITAL = ttsTypes.Personality.CAPITAL.toJSON() as PropertyValues;
+ for (const prop in CAPITAL) {
+ if (properties[prop] === undefined) {
+ properties[prop] = CAPITAL[prop];
+ }
+ }
+ } else if (
+ SettingsManager.getString('capitalStrategy') === 'announceCapitals') {
+ text = Msgs.getMsg('announce_capital_letter', [text]);
+ }
+ }
+
+ if (!SettingsManager.getBoolean('usePitchChanges')) {
+ delete properties['relativePitch'];
+ }
+
+ // Since dollar and sterling pound signs will be replaced with text, move
+ // them to after the number if they stay between a negative sign and a
+ // number.
+ text = text.replace(AbstractTts.negativeCurrencyAmountRegexp_, match => {
+ const minus = match[0];
+ const number = match.substring(2);
+ const currency = match[1];
+
+ return minus + number + currency;
+ });
+
+ // 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(
+ AbstractTts.substitutionDictionaryRegexp_, function(symbol) {
+ return ' ' + ttsTypes.SubstitutionDictionary[symbol] + ' ';
+ });
+
+ // Handle single characters that we want to make sure we pronounce.
+ if (text.length === 1) {
+ return ttsTypes.CharacterDictionary[text] ?
+ Msgs.getMsgWithCount(ttsTypes.CharacterDictionary[text], 1) :
+ text.toUpperCase();
+ }
+
+ // Expand all repeated characters.
+ text = text.replace(
+ AbstractTts.repetitionRegexp_, AbstractTts.repetitionReplace_);
+
+ return text;
+ }
+
+ /**
+ * Constructs a description of a repeated character. Use as a param to
+ * string.replace.
+ * @param match The matching string.
+ * @return The description.
+ */
+ private static repetitionReplace_(match: string): string {
+ const count = match.length;
+ return ' ' +
+ Msgs.getMsgWithCount(ttsTypes.CharacterDictionary[match[0]], count) +
+ ' ';
+ }
+
+ /** TtsInterface implementation. */
+ getDefaultProperty(property: string): number {
+ // TODO(b/314203187): Not null asserted, check that this is correct.
+ return this.propertyDefault[property]!;
+ }
+
+ /** TtsInterface implementation. */
+ toggleSpeechOnOrOff(): boolean {
+ return true;
+ }
+}
\ No newline at end of file