5050 WaveFileCommand ,
5151 ConfigProfileTriggerCommand ,
5252)
53-
53+ from functools import partial
5454from . import types
5555from .types import SpeechSequence , SequenceItemT
5656from typing import Optional , Dict , List , Any , Generator , Union , Callable , Iterator , Tuple
@@ -1026,6 +1026,13 @@ def _extendSpeechSequence_addMathForTextInfo(
10261026 return
10271027
10281028
1029+ re_white_space = re .compile (r"(\s+|$)" , re .DOTALL )
1030+
1031+
1032+ class _TextChunk (str ):
1033+ """str subclass to distinguish normal text from field text when processing text info speech."""
1034+
1035+
10291036class GeneratorWithReturn :
10301037 """Helper class, used with generator functions to access the 'return' value after there are no more values
10311038 to iterate over.
@@ -1047,6 +1054,7 @@ def speakTextInfo(
10471054 unit : Optional [str ] = None ,
10481055 reason : OutputReason = controlTypes .REASON_QUERY ,
10491056 _prefixSpeechCommand : Optional [SpeechCommand ] = None ,
1057+ _whiteSpaceReachedCallback : Optional [Callable [[Any ], None ]] = None ,
10501058 onlyInitialFields : bool = False ,
10511059 suppressBlanks : bool = False ,
10521060 priority : Optional [Spri ] = None
@@ -1081,6 +1089,7 @@ def getTextInfoSpeech( # noqa: C901
10811089 suppressBlanks : bool = False
10821090) -> Generator [SpeechSequence , None , bool ]:
10831091 onlyCache = reason == controlTypes .REASON_ONLYCACHE
1092+ processWhiteSpace : bool = _whiteSpaceReachedCallback is not None
10841093 if isinstance (useCache ,SpeakTextInfoState ):
10851094 speakTextInfoState = useCache
10861095 elif useCache :
@@ -1300,9 +1309,9 @@ def isControlEndFieldCommand(x):
13001309 indentationDone = True
13011310 if command :
13021311 if inTextChunk :
1303- relativeSpeechSequence [- 1 ]+= command
1312+ relativeSpeechSequence [- 1 ] = _TextChunk ( relativeSpeechSequence [ - 1 ] + command )
13041313 else :
1305- relativeSpeechSequence .append (command )
1314+ relativeSpeechSequence .append (_TextChunk ( command ) )
13061315 inTextChunk = True
13071316 elif isinstance (command ,textInfos .FieldCommand ):
13081317 newLanguage = None
@@ -1384,16 +1393,44 @@ def isControlEndFieldCommand(x):
13841393 else :
13851394 speechSequence .extend (indentationSpeech )
13861395 if speakTextInfoState : speakTextInfoState .indentationCache = allIndentation
1387- # Don't add this text if it is blank.
1388- relativeBlank = True
1389- for x in relativeSpeechSequence :
1390- if isinstance (x ,str ) and not isBlank (x ):
1391- relativeBlank = False
1392- break
1393- if not relativeBlank :
1394- speechSequence .extend (relativeSpeechSequence )
1396+ # only add this text if it is not blank.
1397+ notBlank = any (
1398+ not isBlank (x ) for x in relativeSpeechSequence
1399+ if isinstance (x , str )
1400+ )
1401+ if notBlank :
13951402 shouldConsiderTextInfoBlank = False
1396-
1403+ if not processWhiteSpace :
1404+ speechSequence .extend (relativeSpeechSequence )
1405+ else :
1406+ # Add appropriate white space bookmarks
1407+ whiteSpaceTracker = info .copy ()
1408+ whiteSpaceTracker .collapse ()
1409+ for index , command in list (enumerate (relativeSpeechSequence )):
1410+ if not isinstance (command , _TextChunk ):
1411+ continue
1412+ curCommandSequence = []
1413+ endOfWhiteSpaceIndexes = [m .end () for m in re_white_space .finditer (command )]
1414+ start = 0
1415+ for end in endOfWhiteSpaceIndexes :
1416+ text = command [start :end ]
1417+ curCommandSequence .append (text )
1418+ whiteSpaceTracker .move (textInfos .UNIT_CHARACTER , len (text ))
1419+ if whiteSpaceTracker .compareEndPoints (info , "startToEnd" ) > 0 :
1420+ break
1421+ bookmark = whiteSpaceTracker .bookmark
1422+ callback = partial (_whiteSpaceReachedCallback , bookmark = bookmark )
1423+ curCommandSequence .append (CallbackCommand (callback ))
1424+ # The whiteSpaceTracker shouldn't move past the end of the info we're speaking.
1425+ start = end
1426+ relativeSpeechSequence [index ] = curCommandSequence
1427+ expandedRelativeSpeechSequence = []
1428+ for x in relativeSpeechSequence :
1429+ if isinstance (x , list ):
1430+ expandedRelativeSpeechSequence .extend (x )
1431+ else :
1432+ expandedRelativeSpeechSequence .append (x )
1433+ speechSequence .extend (expandedRelativeSpeechSequence )
13971434 #Finally get speech text for any fields left in new controlFieldStack that are common with the old controlFieldStack (for closing), if extra detail is not requested
13981435 if autoLanguageSwitching and lastLanguage is not None :
13991436 speechSequence .append (
@@ -2375,6 +2412,9 @@ def getTableInfoSpeech(
23752412 return textList
23762413
23772414
2415+ re_last_pause = re .compile (r"^(.*?(?<=[^\s.!?])[.!?][\"'”’)]?(?:\s+|$))(.*$)" , re .DOTALL )
2416+
2417+
23782418def _yieldIfNonEmpty (seq : SpeechSequence ):
23792419 """Helper method to yield the sequence if it is not None or empty."""
23802420 if seq :
0 commit comments