|
30 | 30 | import characterProcessing |
31 | 31 | import languageHandler |
32 | 32 | from .commands import * |
| 33 | +from typing import Optional, Callable, Any |
| 34 | +from functools import partial |
| 35 | +from types import GeneratorType |
33 | 36 |
|
34 | 37 | speechMode_off=0 |
35 | 38 | speechMode_beeps=1 |
@@ -766,8 +769,25 @@ def _speakTextInfo_addMath(speechSequence, info, field): |
766 | 769 | except (NotImplementedError, LookupError): |
767 | 770 | return |
768 | 771 |
|
769 | | -def speakTextInfo(info, useCache=True, formatConfig=None, unit=None, reason=controlTypes.REASON_QUERY, _prefixSpeechCommand=None, onlyInitialFields=False, suppressBlanks=False,priority=None): |
| 772 | +re_white_space = re.compile(r"(\s+|$)", re.DOTALL) |
| 773 | + |
| 774 | +class _TextCommand(str): |
| 775 | + """str subclass to distinguish normal text from field text when processing text info speech.""" |
| 776 | + |
| 777 | +def speakTextInfo( |
| 778 | + info, |
| 779 | + useCache=True, |
| 780 | + formatConfig=None, |
| 781 | + unit=None, |
| 782 | + reason=controlTypes.REASON_QUERY, |
| 783 | + _prefixSpeechCommand=None, |
| 784 | + _whiteSpaceReachedCallback: Optional[Callable[[Any], None]] = None, |
| 785 | + onlyInitialFields=False, |
| 786 | + suppressBlanks=False, |
| 787 | + priority=None |
| 788 | +): |
770 | 789 | onlyCache=reason==controlTypes.REASON_ONLYCACHE |
| 790 | + processWhiteSpace: bool = _whiteSpaceReachedCallback is not None |
771 | 791 | if isinstance(useCache,SpeakTextInfoState): |
772 | 792 | speakTextInfoState=useCache |
773 | 793 | elif useCache: |
@@ -943,9 +963,9 @@ def speakTextInfo(info, useCache=True, formatConfig=None, unit=None, reason=cont |
943 | 963 | indentationDone=True |
944 | 964 | if command: |
945 | 965 | if inTextChunk: |
946 | | - relativeSpeechSequence[-1]+=command |
| 966 | + relativeSpeechSequence[-1] = _TextCommand(relativeSpeechSequence[-1] + command) |
947 | 967 | else: |
948 | | - relativeSpeechSequence.append(command) |
| 968 | + relativeSpeechSequence.append(_TextCommand(command)) |
949 | 969 | inTextChunk=True |
950 | 970 | elif isinstance(command,textInfos.FieldCommand): |
951 | 971 | newLanguage=None |
@@ -1007,16 +1027,44 @@ def speakTextInfo(info, useCache=True, formatConfig=None, unit=None, reason=cont |
1007 | 1027 | else: |
1008 | 1028 | speechSequence.append(indentationSpeech) |
1009 | 1029 | if speakTextInfoState: speakTextInfoState.indentationCache=allIndentation |
1010 | | - # Don't add this text if it is blank. |
1011 | | - relativeBlank=True |
1012 | | - for x in relativeSpeechSequence: |
1013 | | - if isinstance(x,str) and not isBlank(x): |
1014 | | - relativeBlank=False |
1015 | | - break |
1016 | | - if not relativeBlank: |
1017 | | - speechSequence.extend(relativeSpeechSequence) |
| 1030 | + # only add this text if it is not blank. |
| 1031 | + notBlank = any( |
| 1032 | + not isBlank(x) for x in relativeSpeechSequence |
| 1033 | + if isinstance(x,str) |
| 1034 | + ) |
| 1035 | + if notBlank: |
1018 | 1036 | isTextBlank=False |
1019 | | - |
| 1037 | + if not processWhiteSpace: |
| 1038 | + speechSequence.extend(relativeSpeechSequence) |
| 1039 | + else: |
| 1040 | + # Add appropriate white space bookmarks |
| 1041 | + whiteSpaceTracker = info.copy() |
| 1042 | + whiteSpaceTracker.collapse() |
| 1043 | + for index, command in list(enumerate(relativeSpeechSequence)): |
| 1044 | + if not isinstance(command, _TextCommand): |
| 1045 | + continue |
| 1046 | + curCommandSequence = [] |
| 1047 | + endOfWhiteSpaceIndexes = [m.end() for m in re_white_space.finditer(command)] |
| 1048 | + start = 0 |
| 1049 | + for end in endOfWhiteSpaceIndexes: |
| 1050 | + text = command[start:end] |
| 1051 | + curCommandSequence.append(text) |
| 1052 | + whiteSpaceTracker.move(textInfos.UNIT_CHARACTER, len(text)) |
| 1053 | + if whiteSpaceTracker.compareEndPoints(info, "startToEnd") > 0: |
| 1054 | + break |
| 1055 | + bookmark = whiteSpaceTracker.bookmark |
| 1056 | + cb = partial(_whiteSpaceReachedCallback, bookmark=bookmark) |
| 1057 | + curCommandSequence.append(CallbackCommand(cb)) |
| 1058 | + # The whiteSpaceTracker shouldn't move past the end of the info we're speaking. |
| 1059 | + start = end |
| 1060 | + relativeSpeechSequence[index] = curCommandSequence |
| 1061 | + expandedRelativeSpeechSequence = [] |
| 1062 | + for x in relativeSpeechSequence: |
| 1063 | + if isinstance(x, list): |
| 1064 | + expandedRelativeSpeechSequence.extend(x) |
| 1065 | + else: |
| 1066 | + expandedRelativeSpeechSequence.append(x) |
| 1067 | + speechSequence.extend(expandedRelativeSpeechSequence) |
1020 | 1068 | #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 |
1021 | 1069 | if autoLanguageSwitching and lastLanguage is not None: |
1022 | 1070 | speechSequence.append(LangChangeCommand(None)) |
@@ -1763,7 +1811,7 @@ def getTableInfoSpeech(tableInfo,oldTableInfo,extraDetail=False): |
1763 | 1811 | textList.append(_("row %s")%rowNumber) |
1764 | 1812 | return " ".join(textList) |
1765 | 1813 |
|
1766 | | -re_last_pause=re.compile(r"^(.*(?<=[^\s.!?])[.!?][\"'”’)]?(?:\s+|$))(.*$)",re.DOTALL|re.UNICODE) |
| 1814 | +re_last_pause=re.compile(r"^(.*?(?<=[^\s.!?])[.!?][\"'”’)]?(?:\s+|$))(.*$)",re.DOTALL|re.UNICODE) |
1767 | 1815 |
|
1768 | 1816 | def speakWithoutPauses(speechSequence,detectBreaks=True): |
1769 | 1817 | """ |
|
0 commit comments