Skip to content

Commit d9c0393

Browse files
authored
Report background and highlighted text in MS Word (#14610)
Fixes #5866, fixes #7396, fixes #12101 Summary of the issue: In these years, Microsoft Word users working without UIA had no feedback about background and highlighting colors, often used by colleagues, teachers and students. UIA has provided a partial solution when available, but its support for these features is far from perfect (see later). Description of user facing changes Assuming UIA is not available/forcibly enabled: when color reporting is enabled in formatting preferences, users will hear background colors too; when marked/highlighted text reporting is enabled in formatting preferences, users will hear highlighting colors ("highlighted in {color}" and "no highlighting"), regardless of color reporting. Description of development approach It was very difficult to find a way, so I'll add some details. Initially, I used VBA objects approach, querying Paragraph.Shading.BackgroundPatternColor and Range.HighlightColorIndex over a winwordDocument.Range to calculate each time, so it was very slow. Then I found this document and, after having understood what I needed and how to find the required wdDISPID_ constants, I was able to move all work on the C++ component of nvdaHelperRemote related to winword, that appears very much faster. In window/winword.py, to take advantage of winwordColorToNVDAColor work, I added also a dict that collects indexes from wdColorIndex and decimal values from wdColor. About speech, I had to introduce a new format field, highlight-color. Now, UIA: sure, it already covers background and highlight colors, but expose them as the same attribute, via UIA_BackgroundColorAttributeId (and no, I asked, even visually they are distinct). There is an AnnotationType_Highlighted identifier, but it seems to not be used in this scenario. Locally I have a possible workaround, based on the fact that, as you see in Word Home menu, highlighting color is applied to characters, background color to paragraphs; so, if textRange has a background-color, I compare it to that retrieved from paragraph and, if I receive IUnknown/MixedAttributeValue, I assume the initial one is an highlighting color and try to get the real background color from "\r" EOL. But I'm not totally convinced, so I not included it in this PR for now.
1 parent bf676b5 commit d9c0393

File tree

5 files changed

+114
-1
lines changed

5 files changed

+114
-1
lines changed

nvdaHelper/remote/WinWord/Constants.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -213,3 +213,8 @@ constexpr int wdDISPID_INLINESHAPE_CHART = 149;
213213
constexpr int wdDISPID_CHART_CHARTTITLE = 1610743811;
214214
constexpr int wdDISPID_CHART_HASTITLE = 1610743809;
215215
constexpr int wdDISPID_CHARTTITLE_TEXT = 1610743820;
216+
217+
// constants for colors
218+
constexpr int wdDISPID_PARAGRAPHFORMAT_SHADING = 1101;
219+
constexpr int wdDISPID_SHADING_BACKGROUNDPATTERNCOLOR = 5;
220+
constexpr int wdDISPID_RANGE_HIGHLIGHTCOLORINDEX = 301;

nvdaHelper/remote/winword.cpp

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ constexpr int formatConfig_includeLayoutTables = 0x20000;
5252
constexpr int formatConfig_reportLineSpacing = 0x40000;
5353
constexpr int formatConfig_reportSuperscriptsAndSubscripts = 0x80000;
5454
constexpr int formatConfig_reportGraphics = 0x100000;
55+
constexpr int formatConfig_reportHighlightColor = 0x200000;
5556

5657
constexpr int formatConfig_fontFlags =(formatConfig_reportFontName|formatConfig_reportFontSize|formatConfig_reportFontAttributes|formatConfig_reportColor|formatConfig_reportSuperscriptsAndSubscripts);
5758
constexpr int formatConfig_initialFormatFlags =(formatConfig_reportPage|formatConfig_reportLineNumber|formatConfig_reportTables|formatConfig_reportHeadings|formatConfig_includeLayoutTables);
@@ -525,9 +526,28 @@ void generateXMLAttribsForFormatting(IDispatch* pDispatchRange, int startOffset,
525526
if((formatConfig&formatConfig_reportLineNumber)&&(_com_dispatch_raw_method(pDispatchRange,wdDISPID_RANGE_INFORMATION,DISPATCH_PROPERTYGET,VT_I4,&iVal,L"\x0003",wdFirstCharacterLineNumber)==S_OK)) {
526527
formatAttribsStream<<L"line-number=\""<<iVal<<L"\" ";
527528
}
528-
if((formatConfig&formatConfig_reportAlignment)||(formatConfig&formatConfig_reportParagraphIndentation)||(formatConfig&formatConfig_reportLineSpacing)) {
529+
if (
530+
(formatConfig & formatConfig_reportColor)
531+
|| (formatConfig & formatConfig_reportAlignment)
532+
|| (formatConfig & formatConfig_reportParagraphIndentation)
533+
|| (formatConfig & formatConfig_reportLineSpacing)
534+
) {
529535
IDispatchPtr pDispatchParagraphFormat=NULL;
530536
if(_com_dispatch_raw_propget(pDispatchRange,wdDISPID_RANGE_PARAGRAPHFORMAT,VT_DISPATCH,&pDispatchParagraphFormat)==S_OK&&pDispatchParagraphFormat) {
537+
if (formatConfig & formatConfig_reportColor) {
538+
IDispatchPtr pDispatchParagraphFormatShading = NULL;
539+
if (
540+
_com_dispatch_raw_propget(pDispatchParagraphFormat, wdDISPID_PARAGRAPHFORMAT_SHADING, VT_DISPATCH, &pDispatchParagraphFormatShading) == S_OK
541+
&& pDispatchParagraphFormatShading
542+
) {
543+
int bgColor = 0;
544+
if (
545+
_com_dispatch_raw_propget(pDispatchParagraphFormatShading, wdDISPID_SHADING_BACKGROUNDPATTERNCOLOR, VT_I4, &bgColor) == S_OK
546+
) {
547+
formatAttribsStream << L"background-color=\"" << bgColor << L"\" ";
548+
}
549+
}
550+
}
531551
if(formatConfig&formatConfig_reportAlignment) {
532552
if(_com_dispatch_raw_propget(pDispatchParagraphFormat,wdDISPID_PARAGRAPHFORMAT_ALIGNMENT,VT_I4,&iVal)==S_OK) {
533553
switch(iVal) {
@@ -654,6 +674,14 @@ void generateXMLAttribsForFormatting(IDispatch* pDispatchRange, int startOffset,
654674
}
655675
}
656676
}
677+
int hlColorIndex = 0;
678+
if (
679+
(formatConfig & formatConfig_reportHighlightColor)
680+
&& (_com_dispatch_raw_propget(pDispatchRange, wdDISPID_RANGE_HIGHLIGHTCOLORINDEX, VT_I4, &hlColorIndex) == S_OK)
681+
&& (hlColorIndex > 0)
682+
) {
683+
formatAttribsStream << L"highlight-color-index=\"" << hlColorIndex << L"\" ";
684+
}
657685
if (formatConfig&formatConfig_reportLanguage) {
658686
int languageId = 0;
659687
if (_com_dispatch_raw_propget(pDispatchRange, wdDISPID_RANGE_LANGUAGEID, VT_I4, &languageId)==S_OK) {

source/NVDAObjects/window/winword.py

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
from ..behaviors import EditableTextWithoutAutoSelectDetection
4242
from . import _msOfficeChart
4343
import locationHelper
44+
from enum import IntEnum
4445

4546
#Word constants
4647

@@ -219,6 +220,56 @@
219220
wdThemeColorText2:msoThemeDark2,
220221
}
221222

223+
224+
# document useful values from:
225+
# https://learn.microsoft.com/en-us/office/vba/api/word.wdcolorindex
226+
class WinWordColorIndex(IntEnum):
227+
228+
wdBlack = 1
229+
wdBlue = 2
230+
wdBrightGreen = 4
231+
wdDarkBlue = 9
232+
wdDarkRed = 13
233+
wdDarkYellow = 14
234+
wdGray25 = 16
235+
wdGray50 = 15
236+
wdGreen = 11
237+
wdPink = 5
238+
wdRed = 6
239+
wdTeal = 10
240+
wdTurquoise = 3
241+
wdViolet = 12
242+
wdWhite = 8
243+
wdYellow = 7
244+
245+
246+
# document useful values from:
247+
# https://learn.microsoft.com/en-us/office/vba/api/word.wdcolor
248+
class WinWordColor(IntEnum):
249+
250+
wdBlack = 0
251+
wdBlue = 16711680
252+
wdBrightGreen = 65280
253+
wdDarkBlue = 8388608
254+
wdDarkRed = 128
255+
wdDarkYellow = 32896
256+
wdGray25 = 12632256
257+
wdGray50 = 8421504
258+
wdGreen = 32768
259+
wdPink = 16711935
260+
wdRed = 255
261+
wdTeal = 8421376
262+
wdTurquoise = 16776960
263+
wdViolet = 8388736
264+
wdWhite = 16777215
265+
wdYellow = 65535
266+
267+
268+
# map (highlighting) color index to color decimal value
269+
_colorIndexToColor: Dict[WinWordColorIndex, WinWordColor] = {
270+
colorIndex.value: WinWordColor[colorIndex.name].value for colorIndex in WinWordColorIndex
271+
}
272+
222273
wdRevisionTypeLabels={
223274
# Translators: a Microsoft Word revision type (inserted content)
224275
wdRevisionInsert:_("insertion"),
@@ -327,6 +378,7 @@
327378
"reportLineSpacing": 0x40000,
328379
"reportSuperscriptsAndSubscripts": 0x80000,
329380
"reportGraphics": 0x100000,
381+
"reportHighlight": 0x200000,
330382
}
331383
formatConfigFlag_includeLayoutTables = 0x20000
332384

@@ -876,6 +928,20 @@ def _normalizeFormatField(self,field,extraDetail=False):
876928
color=field.pop('color',None)
877929
if color is not None:
878930
field['color']=self.obj.winwordColorToNVDAColor(int(color))
931+
bgColor = field.pop('background-color', None)
932+
if bgColor is not None:
933+
field['background-color'] = self.obj.winwordColorToNVDAColor(int(bgColor))
934+
hlColorIndex = field.pop('highlight-color-index', None)
935+
if hlColorIndex is not None:
936+
hlColor = None
937+
try:
938+
val = _colorIndexToColor[int(hlColorIndex)]
939+
hlColor = self.obj.winwordColorToNVDAColor(val)
940+
except (KeyError, ValueError):
941+
log.debugWarning("highlight color error", exc_info=True)
942+
pass
943+
if hlColor is not None:
944+
field['highlight-color'] = hlColor
879945
try:
880946
languageId = int(field.pop('wdLanguageId',0))
881947
if languageId:

source/speech/speech.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2442,6 +2442,17 @@ def getFormatFieldSpeech( # noqa: C901
24422442
# Translators: Reported when text is no longer marked
24432443
else _("not marked"))
24442444
textList.append(text)
2445+
# color-highlighted text in Word
2446+
hlColor = attrs.get("highlight-color")
2447+
oldHlColor = attrsCache.get("highlight-color") if attrsCache is not None else None
2448+
if (hlColor or oldHlColor is not None) and hlColor != oldHlColor:
2449+
colorName = hlColor.name if isinstance(hlColor, colors.RGB) else hlColor
2450+
text = (
2451+
# Translators: Reported when text is color-highlighted
2452+
_("highlighted in {color}").format(color=colorName) if hlColor
2453+
# Translators: Reported when text is no longer marked
2454+
else _("not highlighted"))
2455+
textList.append(text)
24452456
if formatConfig["reportEmphasis"]:
24462457
# strong text
24472458
strong=attrs.get("strong")

user_docs/en/changes.t2t

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ What's New in NVDA
1414
- In Mozilla Firefox and Google Chrome, NVDA now reports when a control opens a dialog, grid, list or tree if the author has specified this using aria-haspopup. (#14709)
1515
- It is now possible to use system variables (such as ``%temp%`` or ``%homepath%``) in the path specification while creating portable copies of NVDA. (#14680)
1616
- Added support for the ``aria-brailleroledescription`` ARIA 1.3 attribute, allowing web authors to override the type of an element shown on the Braille display. (#14748)
17+
- When highlighted text is enabled Document Formatting, highlight colours are now reported in Microsoft Word. (#7396, #12101, #5866)
18+
- When colors are enabled Document Formatting, background colours are now reported in Microsoft Word. (#5866)
1719
-
1820

1921

@@ -70,6 +72,7 @@ If not provided, the default thread is used. (#14627)
7072
- ``hwIo.ioThread.IoThread`` now has a ``setWaitableTimer`` method to set a waitable timer using a python function.
7173
Similarly, the new ``getCompletionRoutine`` method allows you to convert a python method into a completion routine safely. (#14627)
7274
- ``offsets.OffsetsTextInfo._get_boundingRects`` should now always return ``List[locationHelper.rectLTWH]`` as expected for a subclass of ``textInfos.TextInfo``. (#12424)
75+
- ``highlight-color`` is now a format field attribute. (#14610)
7376
-
7477

7578
=== Deprecations ===

0 commit comments

Comments
 (0)