Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Unicode Normalization to speech and braille #16521

Merged
Merged
36 changes: 32 additions & 4 deletions source/braille.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@
import brailleViewer
from autoSettingsUtils.driverSetting import BooleanDriverSetting, NumericDriverSetting
from utils.security import objectBelowLockScreenAndWindowsIsLocked
from textUtils import isUnicodeNormalized, UnicodeNormalizationOffsetConverter
seanbudd marked this conversation as resolved.
Show resolved Hide resolved
import hwIo
from editableText import EditableText

Expand Down Expand Up @@ -496,13 +497,40 @@ def update(self):
mode = louis.dotsIO
if config.conf["braille"]["expandAtCursor"] and self.cursorPos is not None:
mode |= louis.compbrlAtCursor
self.brailleCells, self.brailleToRawPos, self.rawToBraillePos, self.brailleCursorPos = louisHelper.translate(

converter: UnicodeNormalizationOffsetConverter | None = None
seanbudd marked this conversation as resolved.
Show resolved Hide resolved
if config.conf["braille"]["unicodeNormalization"] and not isUnicodeNormalized(self.rawText):
converter = UnicodeNormalizationOffsetConverter(self.rawText)
textToTranslate = converter.encoded
# Typeforms must be adapted to represent normalized characters.
textToTranslateTypeforms = [
self.rawTextTypeforms[strOffset] for strOffset in converter.computedEncodedToStrOffsets
]
# Convert the cursor position to a normalized offset.
cursorPos = converter.strToEncodedOffsets(self.cursorPos)
else:
textToTranslate = self.rawText
textToTranslateTypeforms = self.rawTextTypeforms
cursorPos = self.cursorPos

self.brailleCells, brailleToRawPos, rawToBraillePos, self.brailleCursorPos = louisHelper.translate(
seanbudd marked this conversation as resolved.
Show resolved Hide resolved
[handler.table.fileName, "braille-patterns.cti"],
self.rawText,
typeform=self.rawTextTypeforms,
textToTranslate,
typeform=textToTranslateTypeforms,
mode=mode,
cursorPos=self.cursorPos
cursorPos=cursorPos
)

if converter:
seanbudd marked this conversation as resolved.
Show resolved Hide resolved
# The received brailleToRawPos contains braille to normalized positions.
# Process them to represent real raw positions by converting them from normalized ones.
brailleToRawPos = [converter.encodedToStrOffsets(i) for i in brailleToRawPos]
# The received rawToBraillePos contains normalized to braille positions.
# Create a new list based on real raw positions.
rawToBraillePos = [rawToBraillePos[i] for i in converter.computedStrToEncodedOffsets]
self.brailleToRawPos = brailleToRawPos
self.rawToBraillePos = rawToBraillePos
seanbudd marked this conversation as resolved.
Show resolved Hide resolved

if (
self.selectionStart is not None
and self.selectionEnd is not None
Expand Down
2 changes: 2 additions & 0 deletions source/config/configSpec.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
# symbolLevel: One of the characterProcessing.SymbolLevel values.
symbolLevel = integer(default=100)
trustVoiceLanguage = boolean(default=true)
unicodeNormalization = featureFlag(optionsEnum="BoolFlag", behaviorOfDefault="disabled")
includeCLDR = boolean(default=True)
beepSpeechModePitch = integer(default=10000,min=50,max=11025)
outputDevice = string(default=default)
Expand Down Expand Up @@ -82,6 +83,7 @@
optionsEnum="ReviewRoutingMovesSystemCaretFlag", behaviorOfDefault="NEVER")
readByParagraph = boolean(default=false)
wordWrap = boolean(default=true)
unicodeNormalization = featureFlag(optionsEnum="BoolFlag", behaviorOfDefault="disabled")
focusContextPresentation = option("changedContext", "fill", "scroll", default="changedContext")
interruptSpeechWhileScrolling = featureFlag(optionsEnum="BoolFlag", behaviorOfDefault="enabled")
showSelection = featureFlag(optionsEnum="BoolFlag", behaviorOfDefault="enabled")
Expand Down
24 changes: 24 additions & 0 deletions source/gui/settingsDialogs.py
Original file line number Diff line number Diff line change
Expand Up @@ -1589,6 +1589,17 @@ def makeSettings(self, settingsSizer):
self.bindHelpEvent("SpeechSettingsTrust", self.trustVoiceLanguageCheckbox)
self.trustVoiceLanguageCheckbox.SetValue(config.conf["speech"]["trustVoiceLanguage"])

self.unicodeNormalizationCombo: nvdaControls.FeatureFlagCombo = settingsSizerHelper.addLabeledControl(
labelText=_(
# Translators: This is a label for a combo-box in the Speech settings panel.
"Unicode normali&zation"
),
wxCtrlClass=nvdaControls.FeatureFlagCombo,
keyPath=["speech", "unicodeNormalization"],
conf=config.conf,
)
self.bindHelpEvent("SpeechUnicodeNormalization", self.unicodeNormalizationCombo)

includeCLDRText = _(
# Translators: This is the label for a checkbox in the
# voice settings panel (if checked, data from the unicode CLDR will be used
Expand Down Expand Up @@ -1701,6 +1712,7 @@ def onSave(self):
self.symbolLevelList.GetSelection()
].value
config.conf["speech"]["trustVoiceLanguage"] = self.trustVoiceLanguageCheckbox.IsChecked()
self.unicodeNormalizationCombo.saveCurrentValueToConf()
seanbudd marked this conversation as resolved.
Show resolved Hide resolved
currentIncludeCLDR = config.conf["speech"]["includeCLDR"]
config.conf["speech"]["includeCLDR"] = newIncludeCldr = self.includeCLDRCheckbox.IsChecked()
if currentIncludeCLDR is not newIncludeCldr:
Expand Down Expand Up @@ -4145,6 +4157,17 @@ def makeSettings(self, settingsSizer):
self.bindHelpEvent("BrailleSettingsWordWrap", self.wordWrapCheckBox)
self.wordWrapCheckBox.Value = config.conf["braille"]["wordWrap"]

self.unicodeNormalizationCombo: nvdaControls.FeatureFlagCombo = sHelper.addLabeledControl(
labelText=_(
# Translators: This is a label for a combo-box in the Braille settings panel.
"Unicode normali&zation"
),
wxCtrlClass=nvdaControls.FeatureFlagCombo,
keyPath=["braille", "unicodeNormalization"],
conf=config.conf,
)
self.bindHelpEvent("BrailleUnicodeNormalization", self.unicodeNormalizationCombo)

seanbudd marked this conversation as resolved.
Show resolved Hide resolved
self.brailleInterruptSpeechCombo: nvdaControls.FeatureFlagCombo = sHelper.addLabeledControl(
labelText=_(
# Translators: This is a label for a combo-box in the Braille settings panel.
Expand Down Expand Up @@ -4184,6 +4207,7 @@ def onSave(self):
self.brailleReviewRoutingMovesSystemCaretCombo.saveCurrentValueToConf()
config.conf["braille"]["readByParagraph"] = self.readByParagraphCheckBox.Value
config.conf["braille"]["wordWrap"] = self.wordWrapCheckBox.Value
self.unicodeNormalizationCombo.saveCurrentValueToConf()
seanbudd marked this conversation as resolved.
Show resolved Hide resolved
config.conf["braille"]["focusContextPresentation"] = self.focusContextPresentationValues[self.focusContextPresentationList.GetSelection()]
self.brailleInterruptSpeechCombo.saveCurrentValueToConf()
self.brailleShowSelectionCombo.saveCurrentValueToConf()
Expand Down
11 changes: 9 additions & 2 deletions source/speech/speech.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import speechDictHandler
import characterProcessing
import languageHandler
from textUtils import unicodeNormalize
from . import manager
from .extensions import speechCanceled, pre_speechCanceled, pre_speech
from .extensions import filter_speechSequence, speechCanceled
seanbudd marked this conversation as resolved.
Show resolved Hide resolved
Expand Down Expand Up @@ -1568,6 +1569,8 @@ def getTextInfoSpeech( # noqa: C901
# There was content after the indentation, so there is no more indentation.
indentationDone=True
if command:
if config.conf["speech"]["unicodeNormalization"]:
command = unicodeNormalize(command)
if inTextChunk:
relativeSpeechSequence[-1]+=command
else:
Expand Down Expand Up @@ -1775,7 +1778,7 @@ def getPropertiesSpeech( # noqa: C901
reason: OutputReason = OutputReason.QUERY,
**propertyValues
) -> SpeechSequence:
textList: List[str] = []
textList: SpeechSequence = []
name: Optional[str] = propertyValues.get('name')
if name:
textList.append(name)
Expand Down Expand Up @@ -1968,7 +1971,11 @@ def getPropertiesSpeech( # noqa: C901
errorMessage: str | None = propertyValues.get("errorMessage", None)
if errorMessage:
textList.append(errorMessage)

if config.conf["speech"]["unicodeNormalization"]:
textList = [
unicodeNormalize(t) if isinstance(t, str) else t
for t in textList
]
types.logBadSequenceTypes(textList)
return textList

Expand Down
Loading