Skip to content

Commit 76679ad

Browse files
committed
Changed the way the language spoken is done. It now picks up changes to the language in the text when "Auto" is set.
This only works in Firefox -- in Chrome, NVDA fails to set the appropriate info. See nvaccess/nvda#15188 Also added hack so that OneCore voices say the "long" A sound.
1 parent cea7dfa commit 76679ad

File tree

1 file changed

+65
-31
lines changed

1 file changed

+65
-31
lines changed

NVDA-addon/addon/globalPlugins/MathCAT/MathCAT.py

Lines changed: 65 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
# MathCAT add-on: generates speech, braille, and allows exploration of expressions written in MathML
2-
# The goal of this add-on is to replicate/improve upon the functionality of MathPlayer which has been discontinued.
1+
"""MathCAT add-on: generates speech, braille, and allows exploration of expressions written in MathML.
2+
The goal of this add-on is to replicate/improve upon the functionality of MathPlayer which has been discontinued."""
33
# Author: Neil Soiffer
44
# Copyright: this file is copyright GPL2
55
# The code additionally makes use of the MathCAT library (written in Rust) which is covered by the MIT license
@@ -41,7 +41,7 @@
4141
PhonemeCommand,
4242
)
4343

44-
RE_MP_SPEECH = re.compile(
44+
RE_MATHML_SPEECH = re.compile(
4545
# Break.
4646
r"<break time='(?P<break>\d+)ms'/> ?"
4747
# Pronunciation of characters.
@@ -62,11 +62,37 @@
6262
"volume": VolumeCommand,
6363
"rate": RateCommand,
6464
}
65-
66-
def ConvertSSMLTextForNVDA(text:str, language:str=""):
65+
RE_MATH_LANG = re.compile(r'''<math .*(xml:)?lang=["']([^'"]+)["'].*>''')
66+
def getLanguageToUse(mathMl:str) -> str:
67+
"""Get the language specified in a math tag if the language pref is Auto, else the language preference."""
68+
mathCATLanguageSetting = "Auto"
69+
try:
70+
# ignore regional differences if the MathCAT language setting doesn't have it.
71+
mathCATLanguageSetting = libmathcat.GetPreference("Language")
72+
except Exception as e:
73+
log.error(e)
74+
75+
if mathCATLanguageSetting != 'Auto':
76+
return mathCATLanguageSetting
77+
78+
languageMatch = RE_MATH_LANG.search(mathMl)
79+
language = languageMatch.group(2) if languageMatch else speech.getCurrentLanguage() # seems to be current voice's language
80+
language = language.lower().replace("_", "-")
81+
return language
82+
83+
def ConvertSSMLTextForNVDA(text:str, language:str="") -> list:
6784
# MathCAT's default rate is 180 wpm.
6885
# Assume that 0% is 80 wpm and 100% is 450 wpm and scale accordingly.
69-
# log.info("Speech str: '{}'".format(text))
86+
# log.info(f"Speech str: '{text}'")
87+
if language == "": # shouldn't happen
88+
language = "en" # fallback to what was being used
89+
90+
mathCATLanguageSetting = "en" # fallback in case GetPreference fails for unknown reasons
91+
try:
92+
mathCATLanguageSetting = libmathcat.GetPreference("Language")
93+
except Exception as e:
94+
log.error(e)
95+
7096
synth = getSynth()
7197
wpm = synth._percentToParam(synth.rate, 80, 450)
7298
breakMulti = 180.0 / wpm
@@ -77,12 +103,20 @@ def ConvertSSMLTextForNVDA(text:str, language:str=""):
77103
use_rate = RateCommand in supported_commands
78104
use_volume = VolumeCommand in supported_commands
79105
use_phoneme = PhonemeCommand in supported_commands
80-
use_character = CharacterModeCommand in supported_commands
106+
# as of 7/23, oneCore voices do not implement the CharacterModeCommand despite it being in supported_commands
107+
use_character = CharacterModeCommand in supported_commands and synth.name != 'oneCore'
81108
out = []
82-
if language:
83-
out.append(LangChangeCommand(language))
109+
if mathCATLanguageSetting != language:
110+
try:
111+
log.info(f"Setting language to {language}")
112+
libmathcat.SetPreference("Language", language)
113+
out.append(LangChangeCommand(language))
114+
except Exception as e:
115+
log.error(e)
116+
language = mathCATLanguageSetting # didn't do the 'append'
117+
84118
resetProsody = []
85-
for m in RE_MP_SPEECH.finditer(text):
119+
for m in RE_MATHML_SPEECH.finditer(text):
86120
if m.lastgroup == "break":
87121
if use_break:
88122
out.append(BreakCommand(time=int(int(m.group("break")) * breakMulti)))
@@ -91,7 +125,7 @@ def ConvertSSMLTextForNVDA(text:str, language:str=""):
91125
if use_character:
92126
out.extend((CharacterModeCommand(True), ch, CharacterModeCommand(False)))
93127
else:
94-
out.extend((" ", ch, " "))
128+
out.extend((" ", "eigh" if ch=="a" else ch, " "))
95129
elif m.lastgroup == "beep":
96130
out.append(BeepCommand(2000, 50))
97131
elif m.lastgroup == "pitch":
@@ -116,9 +150,14 @@ def ConvertSSMLTextForNVDA(text:str, language:str=""):
116150
# MathCAT puts out spaces between words, the speak command seems to want to glom the strings together at times,
117151
# so we need to add individual " "s to the output
118152
out.extend((" ", m.group(0), " "))
119-
if language:
120-
out.append(LangChangeCommand(None))
121-
# log.info("Speech commands: '{}'".format(out))
153+
if mathCATLanguageSetting != language:
154+
try:
155+
libmathcat.SetPreference("Language", mathCATLanguageSetting)
156+
out.append(LangChangeCommand(None))
157+
except Exception as e:
158+
log.error(e)
159+
160+
# log.info(f"Speech commands: '{out}'")
122161
return out
123162

124163
class MathCATInteraction(mathPres.MathInteractionNVDAObject):
@@ -131,17 +170,18 @@ class MathCATInteraction(mathPres.MathInteractionNVDAObject):
131170

132171
def __init__(self, provider=None, mathMl: Optional[str]=None):
133172
super(MathCATInteraction, self).__init__(provider=provider, mathMl=mathMl)
134-
provider._setSpeechLanguage(mathMl)
173+
self._language = getLanguageToUse(mathMl)
135174
self.init_mathml = mathMl
136175

176+
137177
def reportFocus(self):
138178
super(MathCATInteraction, self).reportFocus()
139179
try:
140180
text = libmathcat.DoNavigateCommand("ZoomIn")
141-
speech.speak(ConvertSSMLTextForNVDA(text, self.provider._language))
181+
speech.speak(ConvertSSMLTextForNVDA(text, self._language))
142182
except Exception as e:
143183
log.error(e)
144-
speech.speakMessage(_("Error in speaking math: see NVDA error log for details"))
184+
speech.speakMessage(_("Error in starting navigation of math: see NVDA error log for details"))
145185

146186

147187
def getBrailleRegions(self, review: bool = False):
@@ -181,7 +221,7 @@ def script_navigate(self, gesture: KeyboardInputGesture):
181221
modNames = gesture.modifierNames
182222
text = libmathcat.DoNavigateKeyPress(gesture.vkCode,
183223
"shift" in modNames, "control" in modNames, "alt" in modNames, False)
184-
speech.speak(ConvertSSMLTextForNVDA(text, self.provider._language))
224+
speech.speak(ConvertSSMLTextForNVDA(text, self._language))
185225

186226
# update the braille to reflect the nav position (might be excess code, but it works)
187227
nav_node = libmathcat.GetNavigationMathMLId()
@@ -295,21 +335,22 @@ def __init__(self):
295335
try:
296336
# IMPORTANT -- SetRulesDir must be the first call to libmathcat
297337
rules_dir = path.join( path.dirname(path.abspath(__file__)), "Rules")
298-
log.info("MathCAT Rules dir: %s" % rules_dir)
338+
log.info(f"MathCAT installed. Using rules dir: {rules_dir}")
299339
libmathcat.SetRulesDir(rules_dir)
300340
libmathcat.SetPreference("TTS", "SSML")
301-
302341
except Exception as e:
303342
log.error(e)
304343
speech.speakMessage(_("MathCAT initialization failed: see NVDA error log for details"))
344+
self._language = ""
305345

306346

307347
def getSpeechForMathMl(self, mathml: str):
308-
self._setSpeechLanguage(mathml)
309348
try:
349+
self._language = getLanguageToUse(mathml)
310350
libmathcat.SetMathML(mathml)
311351
except Exception as e:
312352
log.error(e)
353+
log.error(f"MathML is {mathml}")
313354
speech.speakMessage(_("Illegal MathML found: see NVDA error log for details"))
314355
libmathcat.SetMathML("<math></math>") # set it to something
315356
try:
@@ -322,9 +363,9 @@ def getSpeechForMathMl(self, mathml: str):
322363
if PitchCommand in supported_commands:
323364
libmathcat.SetPreference("CapitalLetters_Pitch", str(synthConfig["capPitchChange"]))
324365
if self._add_sounds():
325-
return [BeepCommand(800,25)] + ConvertSSMLTextForNVDA(libmathcat.GetSpokenText()) + [BeepCommand(600,15)]
366+
return [BeepCommand(800,25)] + ConvertSSMLTextForNVDA(libmathcat.GetSpokenText(), self._language) + [BeepCommand(600,15)]
326367
else:
327-
return ConvertSSMLTextForNVDA(libmathcat.GetSpokenText())
368+
return ConvertSSMLTextForNVDA(libmathcat.GetSpokenText(), self._language)
328369

329370
except Exception as e:
330371
log.error(e)
@@ -343,6 +384,7 @@ def getBrailleForMathMl(self, mathml: str):
343384
libmathcat.SetMathML(mathml)
344385
except Exception as e:
345386
log.error(e)
387+
log.error(f"MathML is {mathml}")
346388
speech.speakMessage(_("Illegal MathML found: see NVDA error log for details"))
347389
libmathcat.SetMathML("<math></math>") # set it to something
348390
try:
@@ -356,11 +398,3 @@ def getBrailleForMathMl(self, mathml: str):
356398
def interactWithMathMl(self, mathml: str):
357399
MathCATInteraction(provider=self, mathMl=mathml).setFocus()
358400
MathCATInteraction(provider=self, mathMl=mathml).script_navigate(None)
359-
360-
def _setSpeechLanguage(self, mathml: str):
361-
# NVDA inserts its notion of the current language into the math tag, so we can't use it
362-
# see nvda\source\mathPres\mathPlayer.py for original version of this code
363-
# lang = mathPres.getLanguageFromMath(mathml)
364-
365-
# it might have changed, so can't just set it in init()
366-
self._language = libmathcat.GetPreference("Language")

0 commit comments

Comments
 (0)